Skip to main content

yog_api/nanite/
inject.rs

1//! Injections (`#[inject]`, `docs/design/nanites.md`): what a mod hooks,
2//! known before the game's classes load — the runtime asks the mod's
3//! library for them (`yog_mod_nanites`) — and its handlers, subscribed when
4//! the mod registers to the point's keyed event.
5
6use yog_event::nanite::{Inject, Injected, Nanites, point};
7use yog_sync::Mutex;
8
9use super::{Java, Member, Nanite};
10use crate::Registry;
11
12/// A declared nanite: its kind, its member, where in it, its handler.
13pub struct InjectEntry {
14    /// `yog_ids::nanite_kind`.
15    pub kind: &'static str,
16    pub member: &'static Member,
17    /// `yog_ids::mixin_at`.
18    pub at: &'static str,
19    /// What an `INVOKE`/`FIELD`/`NEW` at names, else empty.
20    pub target: &'static str,
21    pub ordinal: i32,
22    /// `yog_ids::mixin_shift`.
23    pub shift: &'static str,
24    pub cancellable: bool,
25    /// Among the point's handlers (higher first).
26    pub priority: i32,
27    /// A `modify_arg`'s argument, a `modify_variable`'s local (-1: by type).
28    pub index: i32,
29    /// A `modify_constant`'s constant (`int:5`).
30    pub constant: &'static str,
31    /// A `modify_variable`'s local's type (a descriptor, Mojang's).
32    pub value_desc: &'static str,
33    /// A `FIELD` redirect's write, else its read.
34    pub write: bool,
35    /// The locals an inject asks for (`"F, &I@1"`, `yog_ids::nanite_local`).
36    pub locals: &'static str,
37    /// None for what has no code of the mod's (an added field, an interface).
38    pub handler: Option<fn(&mut Hook)>,
39}
40
41impl InjectEntry {
42    /// What the host makes of it.
43    pub fn spec(&self) -> Inject {
44        let (method, desc) = self.member.signature();
45        let class = self.member.class();
46        let mut spec = Inject {
47            point: String::new(),
48            kind: self.kind.to_owned(),
49            class: class.to_owned(),
50            method: method.to_owned(),
51            desc,
52            at: self.at.to_owned(),
53            target: self.target.to_owned(),
54            ordinal: self.ordinal,
55            shift: self.shift.to_owned(),
56            cancellable: self.cancellable,
57            index: self.index,
58            constant: self.constant.to_owned(),
59            value_desc: self.value_desc.to_owned(),
60            write: self.write,
61            locals: self
62                .locals
63                .split(yog_ids::nanite_local::LIST)
64                .map(str::trim)
65                .filter(|l| !l.is_empty())
66                .map(str::to_owned)
67                .collect(),
68        };
69        spec.point = point(&spec);
70        spec
71    }
72}
73
74/// What a redirect's original call is: a `Function` of its arguments.
75const ORIGINAL_CLASS: &str = "java.util.function.Function";
76const ORIGINAL_METHOD: &str = "apply";
77
78fn original_desc() -> String {
79    let object = super::java::class_desc("java.lang.Object");
80    super::method_desc(std::slice::from_ref(&object), &object)
81}
82
83/// This mod's injections (each `#[inject]`'s `ctor` adds its own).
84#[doc(hidden)]
85pub fn __yog_nanite_registry() -> &'static Mutex<Vec<InjectEntry>> {
86    static REG: Mutex<Vec<InjectEntry>> = Mutex::new(Vec::new());
87    &REG
88}
89
90/// A declaration with no handler (an added field, an interface): its kind,
91/// member, target.
92#[doc(hidden)]
93pub fn __declare(kind: &'static str, member: &'static Member, target: &'static str) {
94    __yog_nanite_registry().lock().push(InjectEntry {
95        kind,
96        member,
97        at: "",
98        target,
99        ordinal: -1,
100        shift: "",
101        cancellable: false,
102        priority: 0,
103        index: -1,
104        constant: "",
105        value_desc: "",
106        write: false,
107        locals: "",
108        handler: None,
109    });
110}
111
112/// Every injection of this mod, as the runtime asks before the game loads.
113#[doc(hidden)]
114pub fn __yog_nanites() -> Vec<u8> {
115    let injects = __yog_nanite_registry()
116        .lock()
117        .iter()
118        .map(InjectEntry::spec)
119        .collect();
120    yog_data::to_bytes(&Nanites { injects })
121}
122
123/// A handler to subscribe: its point, priority, function.
124type Subscription = (String, i32, fn(&mut Hook));
125
126/// Each injection's handler on its point (when the mod registers).
127pub fn subscribe(registry: &mut Registry) {
128    let entries: Vec<Subscription> = __yog_nanite_registry()
129        .lock()
130        .iter()
131        .filter_map(|e| Some((e.spec().point, e.priority, e.handler?)))
132        .collect();
133    for (key, priority, handler) in entries {
134        registry.change_key_at::<Injected>(&key, priority, move |e, _| handler(&mut Hook(e)));
135    }
136}
137
138/// An injection point reached, as a handler sees it.
139pub struct Hook<'a>(&'a mut Injected);
140
141impl<'a> Hook<'a> {
142    /// The point reached, to be changed.
143    pub fn new(e: &'a mut Injected) -> Self {
144        Hook(e)
145    }
146
147    /// The object the method runs on (none in a static one).
148    pub fn this<T: Nanite>(&self) -> Option<T> {
149        T::from_value(self.0.this.clone())
150    }
151
152    /// Argument `i`.
153    pub fn arg<T: Java>(&self, i: usize) -> Option<T> {
154        T::from_value(self.0.args.get(i)?.clone())
155    }
156
157    /// How many arguments there are.
158    pub fn args(&self) -> usize {
159        self.0.args.len()
160    }
161
162    /// The value returned, at a `RETURN`.
163    pub fn value<T: Java>(&self) -> Option<T> {
164        T::from_value(self.0.value.clone())
165    }
166
167    /// Stops the method (a cancellable injection's).
168    pub fn cancel(&mut self) {
169        self.0.cancelled = true;
170    }
171
172    /// Stops the method returning `value`.
173    pub fn set_return<T: Java>(&mut self, value: T) {
174        self.0.value = value.to_value();
175        self.0.cancelled = true;
176        self.0.returned = true;
177    }
178
179    /// Makes `value` the answer: a modified value, a redirected call's result.
180    pub fn set_value<T: Java>(&mut self, value: T) {
181        self.0.value = value.to_value();
182        self.0.returned = true;
183    }
184
185    /// Runs a redirected call as it was, with `args` (the receiver first): its result.
186    pub fn call_original(&self, args: Vec<yog_data::Value>) -> Option<yog_data::Value> {
187        static APPLY: Member = Member::new(ORIGINAL_CLASS, ORIGINAL_METHOD, original_desc);
188        APPLY.call(self.0.original.clone(), vec![yog_data::Value::List(args)])
189    }
190
191    /// Local `i` of those the inject asked for.
192    pub fn local<T: Java>(&self, i: usize) -> Option<T> {
193        T::from_value(self.0.locals.get(i)?.clone())
194    }
195
196    /// Sets local `i` (one the inject asked to write: `&` in `locals`).
197    pub fn set_local<T: Java>(&mut self, i: usize, value: T) {
198        if let Some(slot) = self.0.locals.get_mut(i) {
199            *slot = value.to_value();
200        }
201    }
202
203    /// Whether a handler before this one stopped it.
204    pub fn cancelled(&self) -> bool {
205        self.0.cancelled
206    }
207}