From f07f8461e350a5a0072e408f368f194ae3449424 Mon Sep 17 00:00:00 2001 From: caeyo Date: Thu, 13 Aug 2026 21:08:02 +1000 Subject: [PATCH 1/2] Add support for buttons in autosplitter settings --- asr-derive/src/lib.rs | 48 +++++- src/game_engine/unity/mono/offsets.rs | 232 +++++++++++++------------- src/runtime/settings/gui.rs | 214 ++++++++++++++++++++++++ src/runtime/settings/mod.rs | 31 ++++ src/runtime/sys.rs | 23 +++ 5 files changed, 435 insertions(+), 113 deletions(-) diff --git a/asr-derive/src/lib.rs b/asr-derive/src/lib.rs index 78485edd..3bc6c595 100644 --- a/asr-derive/src/lib.rs +++ b/asr-derive/src/lib.rs @@ -89,6 +89,17 @@ use syn::{ /// # } /// ``` /// +/// A settings button requires an `on_click` handler and the `alloc` feature. +/// You also need to call `asr::export_settings_buttons!()` so the runtime can +/// invoke it. Without that macro, the host ignores clicks. +/// +/// ```no_run +/// # struct Settings { +/// #[button(on_click = clear_counters)] +/// clear_counters: Button, +/// # } +/// ``` +/// /// # Choices /// /// You can derive `Gui` for an enum to create a choice widget. You can mark one @@ -132,7 +143,7 @@ use syn::{ /// use_game_time: Pair, /// } /// ``` -#[proc_macro_derive(Gui, attributes(default, heading_level, filter))] +#[proc_macro_derive(Gui, attributes(default, heading_level, filter, button))] pub fn settings_macro(input: TokenStream) -> TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); @@ -162,6 +173,17 @@ fn generate_struct_settings(struct_name: Ident, struct_data: DataStruct) -> Resu let ident = field.ident.clone().unwrap(); let ident_name = ident.to_string(); field_names.push(ident); + if is_button_type(&field.ty) + && !field + .attrs + .iter() + .any(|attr| attr.path().is_ident("button")) + { + return Err(Error::new( + field.ty.span(), + "Button fields require #[button(on_click = ...)]", + )); + } field_tys.push(field.ty); let mut doc_string = String::new(); let mut tooltip_string = String::new(); @@ -233,6 +255,8 @@ fn generate_struct_settings(struct_name: Ident, struct_data: DataStruct) -> Resu Meta::List(list) => { if list.path.is_ident("filter") { Some(parse_filter(list)) + } else if list.path.is_ident("button") { + Some(parse_button(list)) } else { None } @@ -384,6 +408,28 @@ fn generate_enum_settings(enum_name: Ident, enum_data: DataEnum) -> Result bool { + let syn::Type::Path(path) = ty else { + return false; + }; + path.path + .segments + .last() + .is_some_and(|segment| segment.ident == "Button" && segment.arguments.is_empty()) +} + +fn parse_button(list: &MetaList) -> Result { + let span = list.span(); + let nv: syn::MetaNameValue = list + .parse_args() + .map_err(|_| Error::new(span, "expected `#[button(on_click = ...)]`"))?; + if !nv.path.is_ident("on_click") { + return Err(Error::new(nv.path.span(), "expected `on_click`")); + } + let value = &nv.value; + Ok(quote_spanned! { span => args.on_click = #value; }) +} + fn parse_filter(list: &MetaList) -> Result { let span = list.span(); let mut filters = Vec::new(); diff --git a/src/game_engine/unity/mono/offsets.rs b/src/game_engine/unity/mono/offsets.rs index dbfb5e58..2a77ae4e 100644 --- a/src/game_engine/unity/mono/offsets.rs +++ b/src/game_engine/unity/mono/offsets.rs @@ -241,118 +241,126 @@ impl MonoOffsets { }, v_table: MonoVTableOffsets { vtable: 0x28 }, }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V3, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x60, - }, - image: ImageOffsets { class_cache: 0x4D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x54, - fields: 0x90, - runtime_info: 0xC8, - field_count: 0xF8, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V2, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x60, - }, - image: ImageOffsets { class_cache: 0x4C0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x54, - fields: 0x90, - runtime_info: 0xC8, - field_count: 0xF8, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x40 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1Cattrs, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x58, - }, - image: ImageOffsets { class_cache: 0x3D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x40, - name: 0x48, - namespace: 0x50, - vtable_size: 0x18, - fields: 0xA8, - runtime_info: 0xF8, - field_count: 0x94, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x58, - }, - image: ImageOffsets { class_cache: 0x3D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x18, - fields: 0xA0, - runtime_info: 0xF0, - field_count: 0x8C, - next_class_cache: 0xF8, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V3, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x60, + }, + image: ImageOffsets { class_cache: 0x4D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x54, + fields: 0x90, + runtime_info: 0xC8, + field_count: 0xF8, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V2, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x60, + }, + image: ImageOffsets { class_cache: 0x4C0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x54, + fields: 0x90, + runtime_info: 0xC8, + field_count: 0xF8, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x40 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1Cattrs, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x58, + }, + image: ImageOffsets { class_cache: 0x3D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x40, + name: 0x48, + namespace: 0x50, + vtable_size: 0x18, + fields: 0xA8, + runtime_info: 0xF8, + field_count: 0x94, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x58, + }, + image: ImageOffsets { class_cache: 0x3D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x18, + fields: 0xA0, + runtime_info: 0xF0, + field_count: 0x8C, + next_class_cache: 0xF8, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } _ => None, } } diff --git a/src/runtime/settings/gui.rs b/src/runtime/settings/gui.rs index 469560c5..b7f109a9 100644 --- a/src/runtime/settings/gui.rs +++ b/src/runtime/settings/gui.rs @@ -8,6 +8,9 @@ pub use asr_derive::Gui; use crate::{runtime::sys, watcher::Pair}; +#[cfg(feature = "alloc")] +use crate::sync::RacyCell; + use super::map::Map; /// Adds a new boolean setting widget to the settings GUI that the user can @@ -49,6 +52,152 @@ pub fn add_title(key: &str, description: &str, heading_level: u32) { } } +#[cfg(feature = "alloc")] +type ButtonHandler = (alloc::string::String, fn()); + +#[cfg(feature = "alloc")] +static BUTTON_HANDLERS: RacyCell> = + RacyCell::new(alloc::vec::Vec::new()); + +#[cfg(feature = "alloc")] +fn register_button_handler(key: &str, on_click: fn()) { + // SAFETY: The auto splitter runtime is single-threaded, so there are no + // other references to the handler list while we mutate it. + let handlers = unsafe { &mut *BUTTON_HANDLERS.get_mut() }; + if let Some((_, slot)) = handlers.iter_mut().find(|(k, _)| k == key) { + *slot = on_click; + return; + } + handlers.push((key.into(), on_click)); +} + +/// Adds a new button to the settings GUI. The key needs to be unique across +/// all types of settings and is not persisted in the settings +/// [`Map`](super::Map). The description is what's shown to the user. When the +/// user clicks the button, `on_click` is called. +/// +/// Click handlers are synchronous. They may load and store the settings +/// [`Map`](super::Map), but they cannot `.await`. +/// +/// Clicks are only delivered if you export the host entry point with +/// [`export_settings_buttons`](macro@crate::export_settings_buttons). Without that macro, the host ignores clicks. +/// +/// This requires the `alloc` feature. +/// +/// # Example +/// +/// ```ignore +/// fn clear_counters() { +/// let mut map = asr::settings::Map::load(); +/// map.insert("deaths", 0i64); +/// map.store(); +/// } +/// +/// asr::settings::gui::add_button("clear_counters", "Clear Counters", clear_counters); +/// +/// asr::export_settings_buttons!(); +/// ``` +#[cfg(feature = "alloc")] +#[inline] +pub fn add_button(key: &str, description: &str, on_click: fn()) { + // SAFETY: We provide valid pointers and lengths to key and description. + // They are also guaranteed to be valid UTF-8 strings. + unsafe { + sys::user_settings_add_button( + key.as_ptr(), + key.len(), + description.as_ptr(), + description.len(), + ) + } + register_button_handler(key, on_click); +} + +/// Dispatches a settings button click to the handler registered for `key`. +/// This is called by [`export_settings_buttons`](macro@crate::export_settings_buttons). +#[cfg(feature = "alloc")] +pub fn dispatch_button(key: &str) { + // SAFETY: The auto splitter runtime is single-threaded. The function + // pointer is copied out so the handler can run without aliasing the + // handler list, including if it registers further buttons. + let on_click = unsafe { + (*BUTTON_HANDLERS.get()) + .iter() + .find(|(k, _)| k == key) + .map(|(_, f)| *f) + }; + if let Some(on_click) = on_click { + on_click(); + } +} + +/// Reads the key of the settings button that is currently being invoked. +#[cfg(feature = "alloc")] +#[doc(hidden)] +pub fn get_button_key() -> Option { + // SAFETY: Calling with a null pointer and 0 length returns the required + // length. We then allocate a buffer and call again. After a successful + // call, the buffer contains `len` bytes of valid UTF-8. + unsafe { + let mut len = 0; + let success = sys::user_settings_get_button_key(core::ptr::null_mut(), &mut len); + if len == 0 && !success { + return None; + } + let mut buf = alloc::vec::Vec::with_capacity(len); + let success = sys::user_settings_get_button_key(buf.as_mut_ptr(), &mut len); + if !success { + return None; + } + buf.set_len(len); + Some(alloc::string::String::from_utf8_unchecked(buf)) + } +} + +/// Exports the `on_settings_button` entry point so the runtime can deliver +/// settings button clicks to handlers registered with [`add_button`] or +/// `#[button(on_click = ...)]`. +/// +/// The generated export takes no arguments. It pulls the clicked button's key +/// through `user_settings_get_button_key` and then calls [`dispatch_button`]. +/// Authors never see `key_ptr` / `key_len` on the export. This is the +/// import-pull ABI used by `livesplit-auto-splitting`. +/// +/// Without this macro, the host ignores button clicks. Requires the `alloc` +/// feature. +/// +/// # Example +/// +/// ```ignore +/// asr::export_settings_buttons!(); +/// +/// fn clear_counters() { +/// let mut map = asr::settings::Map::load(); +/// map.insert("deaths", 0i64); +/// map.store(); +/// } +/// +/// #[no_mangle] +/// pub extern "C" fn update() { +/// asr::settings::gui::add_button("clear_counters", "Clear Counters", clear_counters); +/// } +/// ``` +#[macro_export] +macro_rules! export_settings_buttons { + () => { + /// Called by the runtime when the user clicks a settings button. + /// + /// # Safety + /// This is invoked by the auto splitting runtime. + #[no_mangle] + pub unsafe extern "C" fn on_settings_button() { + if let Some(key) = $crate::settings::gui::get_button_key() { + $crate::settings::gui::dispatch_button(&key); + } + } + }; +} + /// Adds a new choice setting widget that the user can modify. This allows the /// user to choose between various options. The key is used to store the setting /// in the settings [`Map`](super::Map) and needs to be unique across all types @@ -264,6 +413,71 @@ impl Widget for Title { fn update_from(&mut self, _settings_map: &Map, _key: &str, _args: Self::Args) {} } +/// A button that the user can click. Buttons are not persisted in the settings +/// [`Map`](super::Map). +/// +/// Click handlers are synchronous. They may load and store the settings +/// [`Map`](super::Map), but they cannot `.await`. +/// +/// The field requires `#[button(on_click = ...)]`. Clicks are only delivered if +/// you also call [`export_settings_buttons`](macro@crate::export_settings_buttons). Without that macro, the host +/// ignores clicks. +/// +/// This requires the `alloc` feature. +/// +/// # Example +/// +/// ```ignore +/// fn clear_counters() { +/// let mut map = asr::settings::Map::load(); +/// map.insert("deaths", 0i64); +/// map.store(); +/// } +/// +/// #[derive(Gui)] +/// struct Settings { +/// /// Clear Counters +/// #[button(on_click = clear_counters)] +/// clear_counters: Button, +/// } +/// +/// asr::export_settings_buttons!(); +/// ``` +#[cfg(feature = "alloc")] +pub struct Button; + +/// The arguments that are needed to register a button. This is an internal type +/// that you don't need to worry about. +#[cfg(feature = "alloc")] +#[doc(hidden)] +#[non_exhaustive] +pub struct ButtonArgs { + /// The function to call when the user clicks the button. + pub on_click: fn(), +} + +#[cfg(feature = "alloc")] +impl Default for ButtonArgs { + #[inline] + fn default() -> Self { + Self { on_click: || {} } + } +} + +#[cfg(feature = "alloc")] +impl Widget for Button { + type Args = ButtonArgs; + + #[inline] + fn register(key: &str, description: &str, args: Self::Args) -> Self { + add_button(key, description, args.on_click); + Self + } + + #[inline] + fn update_from(&mut self, _settings_map: &Map, _key: &str, _args: Self::Args) {} +} + impl Widget for Pair { type Args = T::Args; diff --git a/src/runtime/settings/mod.rs b/src/runtime/settings/mod.rs index c61c8087..f759ec4d 100644 --- a/src/runtime/settings/mod.rs +++ b/src/runtime/settings/mod.rs @@ -43,6 +43,37 @@ //! Check the [`Gui`](macro@Gui) derive macro and the [`Gui`](trait@Gui) trait //! for more information. //! +//! # Settings buttons +//! +//! Buttons run a callback when clicked. They are not stored in the settings +//! [`Map`]. Register them with [`gui::add_button`] or with +//! `#[button(on_click = ...)]` on a [`gui::Button`] field: +//! +//! ```ignore +//! fn clear_counters() { +//! let mut map = asr::settings::Map::load(); +//! map.insert("deaths", 0i64); +//! map.store(); +//! } +//! +//! asr::settings::gui::add_button("clear_counters", "Clear Counters", clear_counters); +//! +//! // or: +//! #[derive(Gui)] +//! struct Settings { +//! /// Clear Counters +//! #[button(on_click = clear_counters)] +//! clear_counters: asr::settings::gui::Button, +//! } +//! +//! asr::export_settings_buttons!(); +//! ``` +//! +//! Click handlers are synchronous and may mutate the settings [`Map`], but they +//! cannot `.await`. The [`export_settings_buttons`](macro@crate::export_settings_buttons) +//! macro is required for clicks to run; without it, the host ignores clicks. +//! Buttons require the `alloc` feature. +//! //! # Modifying the global settings map //! //! ```no_run diff --git a/src/runtime/sys.rs b/src/runtime/sys.rs index 002e218d..3126cc5e 100644 --- a/src/runtime/sys.rs +++ b/src/runtime/sys.rs @@ -245,6 +245,19 @@ extern "C" { description_len: usize, heading_level: u32, ); + /// Adds a new button to the user settings. This is used to trigger an + /// action in the auto splitter. The key needs to be unique across all + /// types of settings and is not persisted in the settings map. The + /// pointers need to point to valid UTF-8 encoded text with the respective + /// given length. When the user clicks the button, the auto splitter's + /// `on_settings_button` export is called. + #[cfg(feature = "alloc")] + pub fn user_settings_add_button( + key_ptr: *const u8, + key_len: usize, + description_ptr: *const u8, + description_len: usize, + ); /// Adds a new choice setting that the user can modify. This allows the user /// to choose between various options. The key is used to store the setting /// in the settings map and needs to be unique across all types of settings. @@ -334,6 +347,16 @@ extern "C" { tooltip_ptr: *const u8, tooltip_len: usize, ); + /// Stores the key of the settings button that is currently being invoked + /// in the buffer given. Returns `false` if the buffer is too small or if + /// no settings button is currently being invoked. After this call, no + /// matter whether it was successful or not, the `buf_len_ptr` will be set + /// to the required buffer size. If `false` is returned and the + /// `buf_len_ptr` got set to 0, no settings button is currently being + /// invoked. The key is guaranteed to be valid UTF-8 and is not + /// nul-terminated. + #[cfg(feature = "alloc")] + pub fn user_settings_get_button_key(buf_ptr: *mut u8, buf_len_ptr: *mut usize) -> bool; /// Creates a new settings map. You own the settings map and are responsible /// for freeing it. From 86df64451ecb53e0f714f638daff469fbaa22e28 Mon Sep 17 00:00:00 2001 From: caeyo Date: Thu, 13 Aug 2026 21:16:01 +1000 Subject: [PATCH 2/2] Revert unrelated mono offsets formatting --- src/game_engine/unity/mono/offsets.rs | 232 +++++++++++++------------- 1 file changed, 112 insertions(+), 120 deletions(-) diff --git a/src/game_engine/unity/mono/offsets.rs b/src/game_engine/unity/mono/offsets.rs index 2a77ae4e..dbfb5e58 100644 --- a/src/game_engine/unity/mono/offsets.rs +++ b/src/game_engine/unity/mono/offsets.rs @@ -241,126 +241,118 @@ impl MonoOffsets { }, v_table: MonoVTableOffsets { vtable: 0x28 }, }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V3, PointerSize::Bit64) => { - Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x60, - }, - image: ImageOffsets { class_cache: 0x4D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x54, - fields: 0x90, - runtime_info: 0xC8, - field_count: 0xF8, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }) - } - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V2, PointerSize::Bit64) => { - Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x60, - }, - image: ImageOffsets { class_cache: 0x4C0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x54, - fields: 0x90, - runtime_info: 0xC8, - field_count: 0xF8, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x40 }, - }) - } - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1Cattrs, PointerSize::Bit64) => { - Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x58, - }, - image: ImageOffsets { class_cache: 0x3D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x40, - name: 0x48, - namespace: 0x50, - vtable_size: 0x18, - fields: 0xA8, - runtime_info: 0xF8, - field_count: 0x94, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }) - } - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1, PointerSize::Bit64) => { - Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x58, - }, - image: ImageOffsets { class_cache: 0x3D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x18, - fields: 0xA0, - runtime_info: 0xF0, - field_count: 0x8C, - next_class_cache: 0xF8, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }) - } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V3, PointerSize::Bit64) => Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x60, + }, + image: ImageOffsets { class_cache: 0x4D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x54, + fields: 0x90, + runtime_info: 0xC8, + field_count: 0xF8, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }), + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V2, PointerSize::Bit64) => Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x60, + }, + image: ImageOffsets { class_cache: 0x4C0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x54, + fields: 0x90, + runtime_info: 0xC8, + field_count: 0xF8, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x40 }, + }), + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1Cattrs, PointerSize::Bit64) => Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x58, + }, + image: ImageOffsets { class_cache: 0x3D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x40, + name: 0x48, + namespace: 0x50, + vtable_size: 0x18, + fields: 0xA8, + runtime_info: 0xF8, + field_count: 0x94, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }), + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1, PointerSize::Bit64) => Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x58, + }, + image: ImageOffsets { class_cache: 0x3D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x18, + fields: 0xA0, + runtime_info: 0xF0, + field_count: 0x8C, + next_class_cache: 0xF8, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }), _ => None, } }