source2_demo_macros/lib.rs
1#![doc = include_str!("../README.md")]
2//!
3//! # Procedural Macros for Source 2 Replay Parser
4//!
5//! This crate provides procedural macros that simplify implementing the `Observer`
6//! and `DemoRewriter` traits.
7//!
8//! ## Overview
9//!
10//! The macros automate much of the boilerplate required for handling replay events:
11//! - Automatically implement the Observer trait
12//! - Generate protobuf message decoding
13//! - Set up interest flags based on used methods
14//! - Handle optional `Context` parameters
15//!
16//! Handler attributes may be imported and used as single-segment attributes,
17//! such as `#[on_message]`, or written with a path, such as
18//! `#[source2_demo_macros::on_message]`.
19//!
20//! ## Main Macros
21//!
22//! ### `#[observer]` - Main implementation macro
23//!
24//! Automatically implements the Observer trait with all necessary methods.
25//! Takes an impl block with event handlers and generates the full implementation.
26//!
27//! ```no_run
28//! # use source2_demo::prelude::*;
29//! #[derive(Default)]
30//! struct MyObserver;
31//!
32//! #[observer]
33//! impl MyObserver {
34//! #[on_tick_start]
35//! fn on_tick(&mut self, ctx: &Context) -> ObserverResult {
36//! println!("Tick: {}", ctx.tick());
37//! Ok(())
38//! }
39//! }
40//! ```
41//!
42//! ### `#[rewriter]` - Demo writer implementation macro
43//!
44//! Automatically implements the `DemoRewriter` trait for replay rewriting.
45//! Use it when you want to drop, replace, or mutate demo messages while the
46//! writer emits a new replay. Packet message handlers can receive either the raw
47//! packet payload or a decoded protobuf message. The protobuf form infers the
48//! packet id from the message type, just like `#[on_message]`.
49//!
50//! #### Drop a decoded packet message
51//!
52//! ```no_run
53//! # use source2_demo::prelude::*;
54//! # use source2_demo::proto::CDotaUserMsgChatMessage;
55//! # use source2_demo::writer::*;
56//! struct RemoveChat;
57//!
58//! #[rewriter]
59//! impl RemoveChat {
60//! #[rewrite_packet_message]
61//! fn remove_chat(
62//! &mut self,
63//! _message: CDotaUserMsgChatMessage,
64//! ) -> Result<MessageRewrite, ParserError> {
65//! Ok(MessageRewrite::Drop)
66//! }
67//! }
68//! ```
69//!
70//! #### Mutate and re-encode a decoded packet message
71//!
72//! ```no_run
73//! # use source2_demo::prelude::*;
74//! # use source2_demo::proto::CDotaUserMsgChatMessage;
75//! # use source2_demo::writer::*;
76//! struct RedactChat;
77//!
78//! #[rewriter]
79//! impl RedactChat {
80//! #[rewrite_packet_message]
81//! fn redact_chat(
82//! &mut self,
83//! message: &mut CDotaUserMsgChatMessage,
84//! ) -> Result<MessageRewrite, ParserError> {
85//! message.message_text = Some("[redacted]".to_string());
86//! Ok(MessageRewrite::Rewrite)
87//! }
88//! }
89//! ```
90//!
91//! #### Rewrite a string table entry
92//!
93//! ```no_run
94//! # use source2_demo::prelude::*;
95//! # use source2_demo::proto::CMsgPlayerInfo;
96//! # use source2_demo::writer::*;
97//! struct AnonymizeUserInfo;
98//!
99//! #[rewriter]
100//! impl AnonymizeUserInfo {
101//! #[rewrite_string_table_entry]
102//! fn rewrite_userinfo(
103//! &mut self,
104//! table_name: &str,
105//! entry: &mut StringTableEntryUpdate,
106//! ) -> Result<(), ParserError> {
107//! if table_name == "userinfo" {
108//! if let Some(value) = entry.value_mut() {
109//! let mut player = CMsgPlayerInfo::decode(value.as_slice())?;
110//! player.name = Some("Anonymous".to_string());
111//! *value = player.encode_to_vec();
112//! }
113//! }
114//! Ok(())
115//! }
116//! }
117//! ```
118//!
119//! #### Rewrite decoded entity fields
120//!
121//! ```no_run
122//! # use source2_demo::prelude::*;
123//! # use source2_demo::writer::*;
124//! struct RemoveSteamIds;
125//!
126//! #[rewriter]
127//! impl RemoveSteamIds {
128//! #[rewrite_field(class = "CDOTA_PlayerResource", field = ends_with("m_iPlayerSteamID"))]
129//! fn remove_steam_id(&mut self, _value: u64) -> u64 {
130//! 0
131//! }
132//! }
133//! ```
134//!
135//! ### Event Handler Macros
136//!
137//! These attributes mark methods as event handlers within an #[observer] impl:
138//!
139//! - `#[on_message]` - Handles protobuf messages
140//! - `#[on_tick_start]` - Called at tick start
141//! - `#[on_tick_end]` - Called at tick end
142//! - `#[on_entity]` - Called for entity changes
143//! - `#[on_game_event]` - Called for game events
144//! - `#[on_string_table]` - Called for string table updates
145//! - `#[on_stop]` - Called when replay ends
146//! - `#[on_combat_log]` - Called for combat log entries (Dota 2 only)
147//!
148//! ### Trait Markers
149//!
150//! These mark impl blocks with which data types to track:
151//!
152//! - `#[uses_all]` - Track all supported data
153//! - `#[uses_entities]` - Track entities
154//! - `#[uses_string_tables]` - Track string tables
155//! - `#[uses_game_events]` - Track game events
156//! - `#[uses_combat_log]` - Track combat log (Dota 2 only)
157//!
158//! ## How the Macros Work
159//!
160//! The `#[observer]` macro:
161//!
162//! 1. Scans all methods for event handler attributes
163//! 2. Collects parameter types to generate interest flags
164//! 3. Creates decoding logic for protobuf messages
165//! 4. Generates the full `Observer` trait implementation
166//! 5. Handles optional parameters (Context is optional)
167//!
168//! ## Complete Example
169//!
170//! ```no_run
171//! use source2_demo::prelude::*;
172//!
173//! #[derive(Default)]
174//! struct GameAnalyzer {
175//! tick_count: u32,
176//! entity_count: u32,
177//! }
178//!
179//! #[observer]
180//! #[uses_entities]
181//! impl GameAnalyzer {
182//! #[on_tick_start]
183//! fn on_tick(&mut self, ctx: &Context) -> ObserverResult {
184//! self.tick_count += 1;
185//! Ok(())
186//! }
187//!
188//! #[on_entity]
189//! fn on_entity_create(&mut self, event: EntityEvents, entity: &Entity) -> ObserverResult {
190//! if event == EntityEvents::Created {
191//! self.entity_count += 1;
192//! }
193//! Ok(())
194//! }
195//!
196//! #[on_message]
197//! fn on_chat(&mut self, msg: CDotaUserMsgChatMessage) -> ObserverResult {
198//! println!("Chat: {}", msg.message_text());
199//! Ok(())
200//! }
201//! }
202//! ```
203//!
204//! ## Key Features
205//!
206//! - **Automatic Interest Management**: Macro automatically determines which interests
207//! to set based on which handler methods are defined
208//! - **Optional Context**: Pass `ctx: &Context` to any handler or omit it
209//! - **Automatic Message Decoding**: `#[on_message]` handlers automatically decode
210//! protobuf messages based on parameter type
211//! - **Filtering Attributes**: Filter events with string arguments:
212//! - `#[on_entity("CDOTA_Unit_Hero_Axe")]` - Only heroes named Axe
213//! - `#[on_game_event("player_death")]` - Only death events
214//! - `#[on_string_table("userinfo")]` - Only userinfo table updates
215//! - **Game-Specific Messages**: Use `#[on_message]` with Dota 2, Deadlock, or
216//! CS2 protobuf message types
217
218mod observer_impl;
219mod protobuf_map;
220mod rewriter_impl;
221mod type_utils;
222
223use proc_macro::TokenStream;
224
225#[allow(unused_mut)]
226#[proc_macro_attribute]
227/// Implements the Observer trait for your struct.
228///
229/// This is the main macro that ties everything together. Apply it to an `impl` block
230/// that contains event handler methods marked with `#[on_*]` attributes.
231///
232/// # What It Does
233///
234/// - Automatically implements the Observer trait
235/// - Generates code to call all handler methods at appropriate times
236/// - Sets up interest flags based on which handlers are defined
237/// - Decodes protobuf messages and passes them to handlers
238/// - Filters events based on optional string arguments
239///
240/// # Modes
241///
242/// Use `#[observer]` to infer interests from handlers and `#[uses_*]` markers.
243/// Use `#[observer(all)]` or `#[uses_all]` to enable every interest.
244///
245/// # Handler Attributes
246///
247/// Use these attributes inside the impl block to mark event handlers:
248///
249/// - `#[on_tick_start]` - Called at the start of each tick
250/// - `#[on_tick_end]` - Called at the end of each tick
251/// - `#[on_entity]` - Called when entities change
252/// - `#[on_entity("ClassName")]` - Only for specific entity classes
253/// - `#[on_message]` - Called for protobuf messages (type inferred from param)
254/// - `#[on_game_event]` - Called for all game events
255/// - `#[on_game_event("event_name")]` - Only for specific events
256/// - `#[on_string_table]` - Called when string tables update
257/// - `#[on_string_table("table_name")]` - Only for specific tables
258/// - `#[on_stop]` - Called when replay ends
259/// - `#[on_combat_log]` - Called for combat log entries (Dota 2 only)
260///
261/// # Trait Attributes
262///
263/// Apply these to the `impl` block or individual methods to enable tracking:
264///
265/// - `#[uses_all]` - Enable all tracking
266/// - `#[uses_entities]` - Enable entity tracking
267/// - `#[uses_string_tables]` - Enable string table tracking
268/// - `#[uses_game_events]` - Enable game event tracking
269/// - `#[uses_combat_log]` - Enable combat log tracking (Dota 2 only)
270///
271/// # Parameter Guidelines
272///
273/// Handlers can receive these parameters. Most are optional; `#[on_message]`
274/// requires either one protobuf message or a supported message enum plus
275/// `&[u8]` payload.
276/// - `ctx: &Context` - Current replay state (always optional)
277/// - Specific parameters depending on the handler:
278/// - `event: EntityEvents` or `event: &EntityEvents` (on_entity)
279/// - `entity: &Entity` (on_entity)
280/// - `ge: &GameEvent` (on_game_event)
281/// - `table: &StringTable` (on_string_table)
282/// - `modified: &[i32]` (on_string_table)
283/// - `cle: &CombatLogEntry` (on_combat_log)
284/// - A protobuf message, or a supported message enum plus `&[u8]` payload
285/// (on_message)
286///
287/// Handler parameters can appear in any order. Handlers return
288/// `ObserverResult`.
289///
290/// # Examples
291///
292/// ## Basic observer
293///
294/// ```no_run
295/// # use source2_demo::prelude::*;
296/// #[derive(Default)]
297/// struct BasicObserver;
298///
299/// #[observer]
300/// impl BasicObserver {
301/// #[on_tick_start]
302/// fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
303/// println!("Tick: {}", ctx.tick());
304/// Ok(())
305/// }
306/// }
307/// ```
308///
309/// ## With entity tracking
310///
311/// ```no_run
312/// # use source2_demo::prelude::*;
313/// #[derive(Default)]
314/// struct EntityTracker;
315///
316/// #[observer]
317/// #[uses_entities]
318/// impl EntityTracker {
319/// #[on_entity]
320/// fn on_hero_created(&mut self, event: EntityEvents, entity: &Entity) -> ObserverResult {
321/// if event == EntityEvents::Created && entity.class().name().starts_with("CDOTA_Unit_Hero_") {
322/// println!("Hero created: {}", entity.class().name());
323/// }
324/// Ok(())
325/// }
326/// }
327/// ```
328///
329/// ## With multiple handlers
330///
331/// ```no_run
332/// # use source2_demo::prelude::*;
333/// #[derive(Default)]
334/// struct ComplexObserver {
335/// ticks: u32,
336/// messages: u32,
337/// }
338///
339/// #[observer]
340/// impl ComplexObserver {
341/// #[on_tick_start]
342/// fn on_tick(&mut self, ctx: &Context) -> ObserverResult {
343/// self.ticks += 1;
344/// Ok(())
345/// }
346///
347/// #[on_message]
348/// fn on_chat(&mut self, msg: CDotaUserMsgChatMessage) -> ObserverResult {
349/// self.messages += 1;
350/// println!("Message: {}", msg.message_text());
351/// Ok(())
352/// }
353///
354/// #[on_stop]
355/// fn on_replay_end(&mut self) -> ObserverResult {
356/// println!("Total ticks: {}, messages: {}", self.ticks, self.messages);
357/// Ok(())
358/// }
359/// }
360/// ```
361///
362/// ## With game event filtering
363///
364/// ```no_run
365/// # use source2_demo::prelude::*;
366/// #[derive(Default)]
367/// struct DeathTracker {
368/// deaths: u32,
369/// }
370///
371/// #[observer]
372/// impl DeathTracker {
373/// #[on_game_event("player_death")]
374/// fn on_death(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
375/// self.deaths += 1;
376/// Ok(())
377/// }
378/// }
379/// ```
380///
381/// # Interest Flags
382///
383/// The macro automatically determines which interest flags to set based on which
384/// handlers are defined. For example:
385/// - If you have `#[on_tick_start]`, `Interests::TICK_START` is added
386/// - If you have `#[on_entity]`, both `Interests::ENTITY_STATE` and
387/// `Interests::ENTITY_EVENTS` are added
388/// - If you have `#[on_message]` handlers, appropriate message interest flags are added
389///
390/// You can also use trait attributes like `#[uses_entities]` to manually ensure
391/// certain interests are set.
392pub fn observer(attr: TokenStream, item: TokenStream) -> TokenStream {
393 observer_impl::expand_observer(attr, item)
394}
395
396/// Implements the `DemoRewriter` trait for your struct.
397///
398/// Apply this to an inherent `impl` block and mark methods with writer callback
399/// attributes. The macro builds the `RewriteInterests` mask from those methods
400/// so the writer only decodes the parts of the replay your rewrite needs.
401///
402/// # Callback Types
403///
404/// - `#[rewrite_demo_message]` sees outer `EDemoCommands` payloads before
405/// packet-level decoding.
406/// - `#[rewrite_packet_message]` sees messages inside demo packets. It can take
407/// raw `(msg_type, payload)` arguments or a decoded protobuf message.
408/// - `#[rewrite_packet_messages]` can mutate the final decoded packet message
409/// list after individual packet-message callbacks run.
410/// - `#[rewrite_demo_string_tables]`, `#[rewrite_string_table_entry]`,
411/// `#[rewrite_svc_create_string_table]`, and
412/// `#[rewrite_svc_update_string_table]` target string-table data at different
413/// levels of decoding.
414/// - `#[rewrite_field]` and `#[replace_entity_field]` replace decoded entity
415/// field values, while `#[should_rewrite_entity]` filters which entities enter
416/// that path.
417///
418/// # Return Values
419///
420/// Message callbacks return `Result<MessageRewrite, ParserError>`.
421/// `Keep` leaves the current payload alone, `Drop` removes it, `Replace(bytes)`
422/// writes explicit bytes, and `Rewrite` re-encodes a mutable decoded protobuf
423/// argument when that callback supports it. List, entry, and filter callbacks
424/// use the return type of the matching `DemoRewriter` method.
425///
426/// # Examples
427///
428/// ## Drop a decoded packet message
429///
430/// ```no_run
431/// # use source2_demo::prelude::*;
432/// # use source2_demo::proto::CDotaUserMsgChatMessage;
433/// # use source2_demo::writer::*;
434/// struct RemoveChat;
435///
436/// #[rewriter]
437/// impl RemoveChat {
438/// #[rewrite_packet_message]
439/// fn remove_chat(
440/// &mut self,
441/// _msg: CDotaUserMsgChatMessage,
442/// ) -> Result<MessageRewrite, ParserError> {
443/// Ok(MessageRewrite::Drop)
444/// }
445/// }
446/// ```
447///
448/// ## Mutate and re-encode a decoded packet message
449///
450/// ```no_run
451/// # use source2_demo::prelude::*;
452/// # use source2_demo::proto::CDotaUserMsgChatMessage;
453/// # use source2_demo::writer::*;
454/// struct RedactChat;
455///
456/// #[rewriter]
457/// impl RedactChat {
458/// #[rewrite_packet_message]
459/// fn redact_chat(
460/// &mut self,
461/// message: &mut CDotaUserMsgChatMessage,
462/// ) -> Result<MessageRewrite, ParserError> {
463/// message.message_text = Some("[redacted]".to_string());
464/// Ok(MessageRewrite::Rewrite)
465/// }
466/// }
467/// ```
468///
469/// ## Rewrite a string table entry
470///
471/// ```no_run
472/// # use source2_demo::prelude::*;
473/// # use source2_demo::proto::CMsgPlayerInfo;
474/// # use source2_demo::writer::*;
475/// struct AnonymizeUserInfo;
476///
477/// #[rewriter]
478/// impl AnonymizeUserInfo {
479/// #[rewrite_string_table_entry]
480/// fn rewrite_userinfo(
481/// &mut self,
482/// table_name: &str,
483/// entry: &mut StringTableEntryUpdate,
484/// ) -> Result<(), ParserError> {
485/// if table_name == "userinfo" {
486/// if let Some(value) = entry.value_mut() {
487/// let mut player = CMsgPlayerInfo::decode(value.as_slice())?;
488/// player.name = Some("Anonymous".to_string());
489/// *value = player.encode_to_vec();
490/// }
491/// }
492/// Ok(())
493/// }
494/// }
495/// ```
496///
497/// ## Rewrite decoded entity fields
498///
499/// ```no_run
500/// # use source2_demo::prelude::*;
501/// # use source2_demo::writer::*;
502/// struct RemoveSteamIds;
503///
504/// #[rewriter]
505/// impl RemoveSteamIds {
506/// #[rewrite_field(class = "CDOTA_PlayerResource", field = ends_with("m_iPlayerSteamID"))]
507/// fn remove_steam_id(&mut self, _value: u64) -> u64 {
508/// 0
509/// }
510/// }
511/// ```
512#[proc_macro_attribute]
513pub fn rewriter(_attr: TokenStream, item: TokenStream) -> TokenStream {
514 rewriter_impl::expand_rewriter(item)
515}
516
517/// Marks a method as a protobuf message handler.
518///
519/// Use this to handle specific protobuf message types. The message type is inferred
520/// from the method's parameter type, which should be a mapped protobuf message
521/// struct.
522///
523/// The method will automatically decode binary message data and call your handler
524/// with the decoded message object.
525///
526/// # Parameters
527///
528/// A decoded handler can receive:
529///
530/// - `ctx: &Context` (optional)
531/// - One protobuf message by value or shared reference
532///
533/// A raw handler can instead receive:
534///
535/// - `ctx: &Context` (optional)
536/// - One supported message enum by value or shared reference
537/// - `payload: &[u8]`
538///
539/// Parameters can appear in any order.
540/// Return `ObserverResult`.
541///
542/// # Supported Message Types
543///
544/// - `CDotaUserMsgChatMessage` and other Dota 2 messages
545/// - `CCitadelUserMsgChatMsg` and other Deadlock messages
546/// - `CCsUsrMsgVguiMenu` and other CS2 messages
547/// - Other mapped protobuf message types
548///
549/// # Examples
550///
551/// ## Handle Dota 2 chat messages
552///
553/// ```no_run
554/// # use source2_demo::prelude::*;
555/// # struct MyObs;
556/// # impl MyObs {
557/// #[on_message]
558/// fn on_chat(&mut self, ctx: &Context, msg: CDotaUserMsgChatMessage) -> ObserverResult {
559/// println!("[{}] {}", ctx.tick(), msg.message_text());
560/// Ok(())
561/// }
562/// # }
563/// ```
564///
565/// ## Handle without context
566///
567/// ```no_run
568/// # use source2_demo::prelude::*;
569/// # struct MyObs;
570/// # impl MyObs {
571/// #[on_message]
572/// fn on_chat(&mut self, msg: CDotaUserMsgChatMessage) -> ObserverResult {
573/// println!("Message: {}", msg.message_text());
574/// Ok(())
575/// }
576/// # }
577/// ```
578///
579/// ## Handle by reference
580///
581/// ```no_run
582/// # use source2_demo::prelude::*;
583/// # struct MyObs;
584/// # impl MyObs {
585/// #[on_message]
586/// fn on_chat(&mut self, msg: &CDotaUserMsgChatMessage) -> ObserverResult {
587/// println!("Message: {}", msg.message_text());
588/// Ok(())
589/// }
590/// # }
591/// ```
592///
593/// ## Handle raw messages
594///
595/// ```no_run
596/// # use source2_demo::prelude::*;
597/// # struct MyObs;
598/// # impl MyObs {
599/// #[on_message]
600/// fn on_raw(&mut self, msg_type: SvcMessages, payload: &[u8]) -> ObserverResult {
601/// println!("{msg_type:?}: {} bytes", payload.len());
602/// Ok(())
603/// }
604/// # }
605/// ```
606#[proc_macro_attribute]
607pub fn on_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
608 item
609}
610
611/// Rewrites an outer demo command payload.
612///
613/// Use this for callbacks that operate before packet-level decoding, such as
614/// dropping or replacing whole `EDemoCommands` messages. Raw handlers can
615/// receive `ctx: &Context`, `tick: u32`, `msg_type: EDemoCommands`, and
616/// `payload: &[u8]`, in any supported callback-argument order.
617///
618/// Return `Result<MessageRewrite, ParserError>`.
619#[proc_macro_attribute]
620pub fn rewrite_demo_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
621 item
622}
623
624/// Rewrites an individual message inside a demo packet.
625///
626/// A raw handler can receive packet metadata and payload bytes. A typed handler
627/// can receive a protobuf message; the macro infers the packet message id from
628/// that protobuf type. Mutable protobuf arguments may return
629/// `MessageRewrite::Rewrite` to re-encode the modified message.
630///
631/// # Parameters
632///
633/// A raw handler can receive `ctx: &Context`, `tick: u32`, `msg_type: i32`,
634/// and `payload: &[u8]`. A typed handler can receive `ctx: &Context`,
635/// `tick: u32`, and one protobuf message by value, shared reference, or mutable
636/// reference. Parameters can appear in any order.
637///
638/// Return `Result<MessageRewrite, ParserError>`.
639///
640/// # Examples
641///
642/// ## Drop a decoded protobuf message
643///
644/// ```no_run
645/// # use source2_demo::prelude::*;
646/// # use source2_demo::proto::CDotaUserMsgChatMessage;
647/// # use source2_demo::writer::*;
648/// # struct MyRewriter;
649/// # impl MyRewriter {
650/// #[rewrite_packet_message]
651/// fn remove_chat(&mut self, _msg: CDotaUserMsgChatMessage) -> Result<MessageRewrite, ParserError> {
652/// Ok(MessageRewrite::Drop)
653/// }
654/// # }
655/// ```
656///
657/// ## Mutate a decoded protobuf message
658///
659/// ```no_run
660/// # use source2_demo::prelude::*;
661/// # use source2_demo::proto::CDotaUserMsgChatMessage;
662/// # use source2_demo::writer::*;
663/// # struct MyRewriter;
664/// # impl MyRewriter {
665/// #[rewrite_packet_message]
666/// fn redact_chat(
667/// &mut self,
668/// msg: &mut CDotaUserMsgChatMessage,
669/// ) -> Result<MessageRewrite, ParserError> {
670/// msg.message_text = Some("[redacted]".to_string());
671/// Ok(MessageRewrite::Rewrite)
672/// }
673/// # }
674/// ```
675#[proc_macro_attribute]
676pub fn rewrite_packet_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
677 item
678}
679
680/// Mutates the decoded packet message list after per-message rewrites.
681///
682/// Use this when the rewrite needs packet-wide context, such as appending a new
683/// `PacketMessage`, removing several messages together, or reordering messages
684/// within one packet.
685///
686/// The handler can receive `ctx: &Context`, `tick: u32`, and
687/// `messages: &mut Vec<PacketMessage>` in any order. Return
688/// `Result<(), ParserError>`.
689#[proc_macro_attribute]
690pub fn rewrite_packet_messages(_attr: TokenStream, item: TokenStream) -> TokenStream {
691 item
692}
693
694/// Rewrites a decoded `CDemoStringTables` outer demo message.
695///
696/// This callback sees the full demo string-table message. Return
697/// `MessageRewrite::Rewrite` after mutating the decoded message, or
698/// `MessageRewrite::Replace(bytes)` to provide encoded bytes directly.
699///
700/// The handler can receive `ctx: &Context`, `tick: u32`, and
701/// `message: &mut CDemoStringTables` in any order. Return
702/// `Result<MessageRewrite, ParserError>`.
703#[proc_macro_attribute]
704pub fn rewrite_demo_string_tables(_attr: TokenStream, item: TokenStream) -> TokenStream {
705 item
706}
707
708/// Rewrites one decoded string table entry update.
709///
710/// Use this for targeted string-table edits such as rewriting `userinfo` rows.
711/// The handler can receive `ctx: &Context`, `tick: u32`, `table_name: &str`,
712/// and `entry: &mut StringTableEntryUpdate` in any order. Return
713/// `Result<(), ParserError>`.
714///
715/// # Examples
716///
717/// ```no_run
718/// # use source2_demo::prelude::*;
719/// # use source2_demo::proto::CMsgPlayerInfo;
720/// # use source2_demo::writer::*;
721/// # struct MyRewriter;
722/// # impl MyRewriter {
723/// #[rewrite_string_table_entry]
724/// fn anonymize_userinfo(
725/// &mut self,
726/// table_name: &str,
727/// entry: &mut StringTableEntryUpdate,
728/// ) -> Result<(), ParserError> {
729/// if table_name == "userinfo" {
730/// if let Some(value) = entry.value_mut() {
731/// let mut player = CMsgPlayerInfo::decode(value.as_slice())?;
732/// player.name = Some("Anonymous".to_string());
733/// *value = player.encode_to_vec();
734/// }
735/// }
736/// Ok(())
737/// }
738/// # }
739/// ```
740#[proc_macro_attribute]
741pub fn rewrite_string_table_entry(_attr: TokenStream, item: TokenStream) -> TokenStream {
742 item
743}
744
745/// Rewrites a decoded `svc_CreateStringTable` packet message.
746///
747/// Return `MessageRewrite::Rewrite` after mutating the decoded
748/// `CSvcMsgCreateStringTable`, or return `Replace` / `Drop` for explicit output
749/// control.
750///
751/// The handler can receive `ctx: &Context`, `tick: u32`, and
752/// `message: &mut CSvcMsgCreateStringTable` in any order. Return
753/// `Result<MessageRewrite, ParserError>`.
754#[proc_macro_attribute]
755pub fn rewrite_svc_create_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
756 item
757}
758
759/// Rewrites a decoded `svc_UpdateStringTable` packet message.
760///
761/// This is useful for incremental string-table updates that arrive inside
762/// packet data. Return `MessageRewrite::Rewrite` after mutating the decoded
763/// `CSvcMsgUpdateStringTable`.
764///
765/// The handler can receive `ctx: &Context`, `tick: u32`, and
766/// `message: &mut CSvcMsgUpdateStringTable` in any order. Return
767/// `Result<MessageRewrite, ParserError>`.
768#[proc_macro_attribute]
769pub fn rewrite_svc_update_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
770 item
771}
772
773/// Replaces decoded entity field values with custom logic.
774///
775/// This low-level form receives the current field as `&FieldValue` and returns
776/// `Option<FieldValue>`. Return `Some` to replace the value; return `None` to
777/// leave it unchanged or let later generated checks continue.
778///
779/// The handler can receive `ctx: &Context`, `event: EntityEvents`,
780/// `entity: &Entity`, `field_name: &str`, and `value: &FieldValue` in any
781/// order.
782#[proc_macro_attribute]
783pub fn replace_entity_field(_attr: TokenStream, item: TokenStream) -> TokenStream {
784 item
785}
786
787/// Replaces decoded entity field values with class and field filters.
788///
789/// # Attribute Arguments
790///
791/// - `class = ...` is required.
792/// - `field = ...` is optional.
793///
794/// Both filters accept an exact string literal or the `exact`, `starts_with`,
795/// `ends_with`, `contains`, `any`, `all`, and `not` predicates.
796///
797/// Field-specific handlers may use typed values such as `u64`, `bool`, or
798/// `String`. Class-only handlers must use `FieldValue` or `&FieldValue`
799/// because there is no field predicate to determine one concrete value type.
800///
801/// # Handler Parameters
802///
803/// A handler must receive one field value parameter. Field-specific handlers
804/// support `&str`, `String`, `&String`, `bool`, `f32`, `i8`, `i16`, `i32`,
805/// `i64`, `u8`, `u16`, `u32`, `u64`, `[f32; 2]`, `[f32; 3]`, `[f32; 4]`,
806/// `FieldValue`, and `&FieldValue`. Class-only handlers must use `FieldValue`
807/// or `&FieldValue`.
808///
809/// A handler can also receive any of these optional parameters:
810///
811/// - `ctx: &Context`
812/// - `event: EntityEvents`
813/// - `entity: &Entity`
814/// - `field_name: &str`
815///
816/// Parameters can appear in any order except that `field_name: &str` must come
817/// before the field value parameter. This matters because `&str` can itself be
818/// the field value type.
819///
820/// # Return Values
821///
822/// Return a replacement value directly to always rewrite the field. Return
823/// `Option<T>` to rewrite with `Some(value)` or leave the field unchanged with
824/// `None`. The replacement must convert into `FieldValue`.
825///
826/// # Examples
827///
828/// ```no_run
829/// # use source2_demo::prelude::*;
830/// # use source2_demo::writer::*;
831/// # struct MyRewriter;
832/// # impl MyRewriter {
833/// #[rewrite_field(class = "CDOTA_PlayerResource", field = ends_with("m_iPlayerSteamID"))]
834/// fn remove_steam_id(&mut self, _value: u64) -> u64 {
835/// 0
836/// }
837/// # }
838/// ```
839#[proc_macro_attribute]
840pub fn rewrite_field(_attr: TokenStream, item: TokenStream) -> TokenStream {
841 item
842}
843
844/// Filters which entities enter the entity field rewrite path.
845///
846/// Return `false` to skip all entity-field replacement callbacks for that
847/// entity. Use this to keep broad `#[rewrite_field]` handlers from forcing the
848/// writer to decode and re-encode unrelated entity classes.
849///
850/// The handler can receive `ctx: &Context`, `event: EntityEvents`, and
851/// `entity: &Entity` in any order. Return `bool`.
852#[proc_macro_attribute]
853pub fn should_rewrite_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
854 item
855}
856
857/// Marks a method as a tick-start handler.
858///
859/// This handler is called at the beginning of each game tick. Use it for
860/// per-tick logic like updating game state, tracking changes, or generating output.
861///
862/// # Parameters
863///
864/// The handler can receive:
865/// - `ctx: &Context` (optional) - Current replay state including tick number
866///
867/// Return `ObserverResult`.
868///
869/// # When It's Called
870///
871/// Called right after the tick number is incremented in the Context, but before
872/// any other tick-specific events are processed.
873///
874/// # Examples
875///
876/// ## Track entity state every tick
877///
878/// ```no_run
879/// # use source2_demo::prelude::*;
880/// # struct MyObs;
881/// # impl MyObs {
882/// #[on_tick_start]
883/// fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
884/// if ctx.tick() % 30 == 0 { // Every second (30 ticks/sec)
885/// println!("Ticks processed: {}", ctx.tick());
886/// }
887/// Ok(())
888/// }
889/// # }
890/// ```
891///
892/// ## Without context
893///
894/// ```no_run
895/// # use source2_demo::prelude::*;
896/// # struct MyObs;
897/// # impl MyObs {
898/// #[on_tick_start]
899/// fn on_tick_start(&mut self) -> ObserverResult {
900/// // Do something without needing context
901/// Ok(())
902/// }
903/// # }
904/// ```
905#[proc_macro_attribute]
906pub fn on_tick_start(_attr: TokenStream, item: TokenStream) -> TokenStream {
907 item
908}
909
910/// Marks a method as a tick-end handler.
911///
912/// This handler is called at the end of each game tick. Use it to finalize
913/// per-tick calculations, output results, or reset temporary state.
914///
915/// # Parameters
916///
917/// The handler can receive:
918/// - `ctx: &Context` (optional) - Current replay state
919///
920/// Return `ObserverResult`.
921///
922/// # When It's Called
923///
924/// Called after all events for the current tick have been processed.
925///
926/// # Examples
927///
928/// ## Flush buffered data at end of tick
929///
930/// ```no_run
931/// # use source2_demo::prelude::*;
932/// # struct MyObs {
933/// # buffer: Vec<String>,
934/// # }
935/// # impl MyObs {
936/// #[on_tick_end]
937/// fn on_tick_end(&mut self, ctx: &Context) -> ObserverResult {
938/// // Output buffered data
939/// for item in self.buffer.drain(..) {
940/// println!("[{}] {}", ctx.tick(), item);
941/// }
942/// Ok(())
943/// }
944/// # }
945/// ```
946#[proc_macro_attribute]
947pub fn on_tick_end(_attr: TokenStream, item: TokenStream) -> TokenStream {
948 item
949}
950
951/// Marks a method as an entity event handler.
952///
953/// This handler is called when entities are created, updated, or deleted.
954/// Optionally filter to specific entity class names.
955///
956/// # Parameters
957///
958/// The handler can receive:
959/// - `ctx: &Context` (optional) - Current replay state
960/// - `event: EntityEvents` or `event: &EntityEvents` (optional) - Type of entity event
961/// - `entity: &Entity` (optional) - The entity that changed
962///
963/// Parameters can appear in any order. Return `ObserverResult`.
964///
965/// # Filtering
966///
967/// Pass a class name to only handle entities of that type:
968/// ```no_run
969/// # use source2_demo::prelude::*;
970/// # struct MyObs;
971/// # impl MyObs {
972/// #[on_entity("CDOTA_Unit_Hero_Axe")]
973/// fn on_axe(&mut self, entity: &Entity) -> ObserverResult {
974/// // Only called for Axe hero entity
975/// Ok(())
976/// }
977/// # }
978/// ```
979///
980/// # Examples
981///
982/// ## Track all entity changes
983///
984/// ```no_run
985/// # use source2_demo::prelude::*;
986/// # struct MyObs {
987/// # created: usize,
988/// # }
989/// # impl MyObs {
990/// #[on_entity]
991/// fn on_entity(&mut self, event: EntityEvents, entity: &Entity) -> ObserverResult {
992/// if event == EntityEvents::Created {
993/// self.created += 1;
994/// }
995/// Ok(())
996/// }
997/// # }
998/// ```
999///
1000/// ## Track specific entity type
1001///
1002/// ```no_run
1003/// # use source2_demo::prelude::*;
1004/// # struct MyObs;
1005/// # impl MyObs {
1006/// #[on_entity("CDOTA_PlayerResource")]
1007/// fn on_player_resource(&mut self, ctx: &Context, entity: &Entity) -> ObserverResult {
1008/// // Only called when player resource entity updates
1009/// Ok(())
1010/// }
1011/// # }
1012/// ```
1013#[proc_macro_attribute]
1014pub fn on_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
1015 item
1016}
1017
1018/// Marks a method as a game event handler.
1019///
1020/// This handler is called when game events occur (kills, deaths, item purchases, etc.).
1021/// Optionally filter to specific event names.
1022///
1023/// # Parameters
1024///
1025/// The handler can receive:
1026/// - `ctx: &Context` (optional) - Current replay state
1027/// - `ge: &GameEvent` (optional) - The game event
1028///
1029/// Parameters can appear in any order. Return `ObserverResult`.
1030///
1031/// # Filtering
1032///
1033/// Pass an event name to only handle that event type:
1034/// ```no_run
1035/// # use source2_demo::prelude::*;
1036/// # struct MyObs;
1037/// # impl MyObs {
1038/// #[on_game_event("player_death")]
1039/// fn on_death(&mut self, ge: &GameEvent) -> ObserverResult {
1040/// // Only called for player_death events
1041/// Ok(())
1042/// }
1043/// # }
1044/// ```
1045///
1046/// # Examples
1047///
1048/// ## Handle all game events
1049///
1050/// ```no_run
1051/// # use source2_demo::prelude::*;
1052/// # struct MyObs;
1053/// # impl MyObs {
1054/// #[on_game_event]
1055/// fn on_event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
1056/// println!("[{}] Event: {}", ctx.tick(), ge.name());
1057/// Ok(())
1058/// }
1059/// # }
1060/// ```
1061///
1062/// ## Handle specific event type
1063///
1064/// ```no_run
1065/// # use source2_demo::prelude::*;
1066/// # struct MyObs {
1067/// # kill_count: u32,
1068/// # }
1069/// # impl MyObs {
1070/// #[on_game_event("dota_player_kill")]
1071/// fn on_kill(&mut self, ge: &GameEvent) -> ObserverResult {
1072/// self.kill_count += 1;
1073/// Ok(())
1074/// }
1075/// # }
1076/// ```
1077#[proc_macro_attribute]
1078pub fn on_game_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
1079 item
1080}
1081
1082/// Marks a method as a string table update handler.
1083///
1084/// This handler is called when string tables are updated. String tables contain
1085/// game data like player names, modifiers, effects, etc.
1086/// Optionally filter to specific table names.
1087///
1088/// # Parameters
1089///
1090/// The handler can receive:
1091/// - `ctx: &Context` (optional) - Current replay state
1092/// - `table: &StringTable` (optional) - The updated string table
1093/// - `modified: &[i32]` (optional) - Indices of rows that were modified
1094///
1095/// Parameters can appear in any order. Return `ObserverResult`.
1096///
1097/// # Filtering
1098///
1099/// Pass a table name to only handle that table:
1100/// ```no_run
1101/// # use source2_demo::prelude::*;
1102/// # struct MyObs;
1103/// # impl MyObs {
1104/// #[on_string_table("userinfo")]
1105/// fn on_userinfo(&mut self, table: &StringTable, modified: &[i32]) -> ObserverResult {
1106/// // Only called when userinfo table updates
1107/// Ok(())
1108/// }
1109/// # }
1110/// ```
1111///
1112/// # Examples
1113///
1114/// ## Track all string table updates
1115///
1116/// ```no_run
1117/// # use source2_demo::prelude::*;
1118/// # struct MyObs;
1119/// # impl MyObs {
1120/// #[on_string_table]
1121/// fn on_table_update(&mut self, ctx: &Context, table: &StringTable, modified: &[i32]) -> ObserverResult {
1122/// println!("[{}] Table {} updated: {} rows", ctx.tick(), table.name(), modified.len());
1123/// Ok(())
1124/// }
1125/// # }
1126/// ```
1127///
1128/// ## Monitor specific table
1129///
1130/// ```no_run
1131/// # use source2_demo::prelude::*;
1132/// # struct MyObs;
1133/// # impl MyObs {
1134/// #[on_string_table("ActiveModifiers")]
1135/// fn on_modifiers(&mut self, table: &StringTable, modified: &[i32]) -> ObserverResult {
1136/// println!("Active modifiers changed: {} rows", modified.len());
1137/// Ok(())
1138/// }
1139/// # }
1140/// ```
1141#[proc_macro_attribute]
1142pub fn on_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
1143 item
1144}
1145
1146/// Marks a method as a replay stop handler.
1147///
1148/// This handler is called when the replay ends (CDemoStop message).
1149/// Use it to finalize results, output statistics, or clean up resources.
1150///
1151/// # Parameters
1152///
1153/// The handler can receive:
1154/// - `ctx: &Context` (optional) - Final replay state
1155///
1156/// Return `ObserverResult`.
1157///
1158/// # Examples
1159///
1160/// ## Output final statistics
1161///
1162/// ```no_run
1163/// # use source2_demo::prelude::*;
1164/// # struct MyObs {
1165/// # ticks: u32,
1166/// # }
1167/// # impl MyObs {
1168/// #[on_stop]
1169/// fn on_stop(&mut self, ctx: &Context) -> ObserverResult {
1170/// println!("Replay ended at tick {}", ctx.tick());
1171/// println!("Total ticks processed: {}", self.ticks);
1172/// Ok(())
1173/// }
1174/// # }
1175/// ```
1176///
1177/// ## Without context
1178///
1179/// ```no_run
1180/// # use source2_demo::prelude::*;
1181/// # struct MyObs;
1182/// # impl MyObs {
1183/// #[on_stop]
1184/// fn on_stop(&mut self) -> ObserverResult {
1185/// println!("Replay complete");
1186/// Ok(())
1187/// }
1188/// # }
1189/// ```
1190#[proc_macro_attribute]
1191pub fn on_stop(_attr: TokenStream, item: TokenStream) -> TokenStream {
1192 item
1193}
1194
1195/// Marks a method as a combat log handler (Dota 2 only).
1196///
1197/// This handler is called whenever a combat log entry is generated.
1198/// Combat log entries include damage, healing, kills, abilities, items, etc.
1199///
1200/// # Parameters
1201///
1202/// The handler can receive:
1203/// - `ctx: &Context` (optional) - Current replay state
1204/// - `cle: &CombatLogEntry` (optional) - The combat log entry
1205///
1206/// Parameters can appear in any order. Return `ObserverResult`.
1207///
1208/// # Requires Feature
1209///
1210/// Only available when the `dota` feature is enabled.
1211///
1212/// # Examples
1213///
1214/// ## Track damage in real-time
1215///
1216/// ```no_run
1217/// # use source2_demo::prelude::*;
1218/// # use source2_demo::proto::DotaCombatlogTypes;
1219/// # struct MyObs {
1220/// # total_damage: u32,
1221/// # }
1222/// # impl MyObs {
1223/// #[on_combat_log]
1224/// fn on_damage(&mut self, ctx: &Context, cle: &CombatLogEntry) -> ObserverResult {
1225/// if cle.r#type() == DotaCombatlogTypes::DotaCombatlogDamage {
1226/// if let Ok(damage) = cle.value() {
1227/// self.total_damage += damage;
1228/// }
1229/// }
1230/// Ok(())
1231/// }
1232/// # }
1233/// ```
1234///
1235/// ## Track kills
1236///
1237/// ```no_run
1238/// # use source2_demo::prelude::*;
1239/// # use source2_demo::proto::DotaCombatlogTypes;
1240/// # struct MyObs {
1241/// # kills: u32,
1242/// # }
1243/// # impl MyObs {
1244/// #[on_combat_log]
1245/// fn on_kill(&mut self, cle: &CombatLogEntry) -> ObserverResult {
1246/// if cle.r#type() == DotaCombatlogTypes::DotaCombatlogDeath {
1247/// self.kills += 1;
1248/// }
1249/// Ok(())
1250/// }
1251/// # }
1252/// ```
1253#[cfg(feature = "dota")]
1254#[proc_macro_attribute]
1255pub fn on_combat_log(_attr: TokenStream, item: TokenStream) -> TokenStream {
1256 item
1257}
1258
1259/// Marks the impl block or individual method to enable all tracking.
1260///
1261/// This is equivalent to `#[observer(all)]`.
1262///
1263/// # Examples
1264///
1265/// ```no_run
1266/// # use source2_demo::prelude::*;
1267/// #[observer]
1268/// #[uses_all]
1269/// impl MyObs {
1270/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1271/// }
1272/// ```
1273#[proc_macro_attribute]
1274pub fn uses_all(_attr: TokenStream, item: TokenStream) -> TokenStream {
1275 item
1276}
1277
1278/// Marks the impl block to enable entity tracking.
1279///
1280/// When applied to an impl block or individual method, automatically enables
1281/// the `ENTITY_STATE` interest flag so entities are tracked during parsing.
1282///
1283/// # Examples
1284///
1285/// ```no_run
1286/// # use source2_demo::prelude::*;
1287/// #[observer]
1288/// #[uses_entities]
1289/// impl MyObs {
1290/// // Now you can use #[on_entity] handlers
1291/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1292/// }
1293/// ```
1294#[proc_macro_attribute]
1295pub fn uses_entities(_attr: TokenStream, item: TokenStream) -> TokenStream {
1296 item
1297}
1298
1299/// Marks the impl block to enable string table tracking.
1300///
1301/// When applied to an impl block or individual method, automatically enables
1302/// the `STRING_TABLE_STATE` interest flag so string tables are tracked during parsing.
1303///
1304/// # Examples
1305///
1306/// ```no_run
1307/// # use source2_demo::prelude::*;
1308/// #[observer]
1309/// #[uses_string_tables]
1310/// impl MyObs {
1311/// // Now you can use #[on_string_table] handlers
1312/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1313/// }
1314/// ```
1315#[proc_macro_attribute]
1316pub fn uses_string_tables(_attr: TokenStream, item: TokenStream) -> TokenStream {
1317 item
1318}
1319
1320/// Marks the impl block to enable game event tracking.
1321///
1322/// When applied to an impl block or individual method, automatically enables
1323/// the `BASE_GAME_EVENT` interest flag so game events are tracked during parsing.
1324///
1325/// # Examples
1326///
1327/// ```no_run
1328/// # use source2_demo::prelude::*;
1329/// #[observer]
1330/// #[uses_game_events]
1331/// impl MyObs {
1332/// // Now you can use #[on_game_event] handlers
1333/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1334/// }
1335/// ```
1336#[proc_macro_attribute]
1337pub fn uses_game_events(_attr: TokenStream, item: TokenStream) -> TokenStream {
1338 item
1339}
1340
1341/// Marks the impl block to enable combat log tracking (Dota 2 only).
1342///
1343/// When applied to an impl block or individual method, automatically enables
1344/// the `COMBAT_LOG_ENTRIES` and `STRING_TABLE_STATE` interest flags for combat
1345/// log parsing.
1346///
1347/// # Requires Feature
1348///
1349/// Only available when the `dota` feature is enabled.
1350///
1351/// # Examples
1352///
1353/// ```no_run
1354/// # use source2_demo::prelude::*;
1355/// #[observer]
1356/// #[uses_combat_log]
1357/// impl MyObs {
1358/// // Now you can use #[on_combat_log] handlers
1359/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1360/// }
1361/// ```
1362#[cfg(feature = "dota")]
1363#[proc_macro_attribute]
1364pub fn uses_combat_log(_attr: TokenStream, item: TokenStream) -> TokenStream {
1365 item
1366}