use std::ffi::c_void;
use std::fmt::{self, Debug};
use std::marker::PhantomData;
use il2cpp_bridge_rs as bridge;
use crate::{Error, Il2CppClass, Il2CppValueType, Result};
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Il2CppList<T: Copy> {
pub klass: *mut c_void,
pub monitor: *mut c_void,
pub items: *mut c_void,
pub size: i32,
pub version: i32,
pub _phantom: PhantomData<T>,
}
unsafe impl<T: Copy + 'static> Il2CppValueType for Il2CppList<T> {
unsafe fn load_field(field: &il2cpp_bridge_rs::structs::Field) -> Result<Self> {
let ptr = unsafe { field.get_value::<*mut c_void>().map_err(Error::Bridge)? };
Ok(unsafe { std::ptr::read(ptr as *const Self) })
}
#[cold]
unsafe fn store_field(_field: &il2cpp_bridge_rs::structs::Field, _val: Self) -> Result<()> {
Err(Error::Bridge(
"Il2CppList mutation should be done through C# methods (Add/Remove)".into(),
))
}
unsafe fn invoke_result(
method: &il2cpp_bridge_rs::structs::Method,
args: &[*mut std::ffi::c_void],
) -> Result<Self> {
let ptr = unsafe { method.call::<*mut c_void>(args).map_err(Error::Bridge)? };
Ok(unsafe { std::ptr::read(ptr as *const Self) })
}
}
impl<T: Copy + 'static> Il2CppList<T> {
fn items_ptr(&self) -> *mut bridge::structs::collections::Il2cppArray<T> {
self.items as *mut bridge::structs::collections::Il2cppArray<T>
}
fn items_array(&self) -> Option<&bridge::structs::collections::Il2cppArray<T>> {
if self.items.is_null() {
None
} else {
unsafe { Some(&*self.items_ptr()) }
}
}
pub fn len(&self) -> usize {
self.size.max(0) as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: usize) -> Option<T> {
if index >= self.len() {
return None;
}
self.items_array()
.and_then(|arr| (index < arr.len()).then(|| arr.get(index)))
}
pub fn to_vec(&self) -> Vec<T> {
(0..self.len()).filter_map(|i| self.get(i)).collect()
}
pub fn iter(&self) -> impl Iterator<Item = T> + '_ {
let bound = self
.items_array()
.map(|arr| self.len().min(arr.len()))
.unwrap_or(0);
(0..bound).filter_map(|i| self.get(i))
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Il2CppString {
pub(crate) ptr: *mut bridge::structs::Il2cppString,
}
unsafe impl Il2CppValueType for Il2CppString {
unsafe fn load_field(field: &il2cpp_bridge_rs::structs::Field) -> Result<Self> {
let ptr = unsafe {
field
.get_value::<*mut bridge::structs::Il2cppString>()
.map_err(Error::Bridge)?
};
Ok(Il2CppString { ptr })
}
unsafe fn store_field(field: &il2cpp_bridge_rs::structs::Field, val: Self) -> Result<()> {
unsafe {
field
.set_value::<*mut bridge::structs::Il2cppString>(val.ptr)
.map_err(Error::Bridge)
}
}
}
impl Il2CppString {
pub fn new(s: &str) -> Self {
let ptr = bridge::structs::Il2cppString::new(s);
assert!(
!ptr.is_null(),
"Il2CppString::new returned null — is IL2CPP initialized?"
);
Self { ptr }
}
pub fn as_ptr(&self) -> *mut c_void {
self.ptr as *mut c_void
}
pub fn to_string_lossy(&self) -> String {
if self.ptr.is_null() {
return "<null>".into();
}
unsafe { (*self.ptr).to_string() }.unwrap_or_else(|| "<invalid UTF-16>".into())
}
pub fn len(&self) -> usize {
if self.ptr.is_null() {
return 0;
}
unsafe { (*self.ptr).length as usize }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl fmt::Display for Il2CppString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string_lossy())
}
}
impl Debug for Il2CppString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self, f)
}
}
impl Default for Il2CppString {
fn default() -> Self {
Self {
ptr: std::ptr::null_mut(),
}
}
}
impl From<&str> for Il2CppString {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl TryFrom<&Il2CppString> for String {
type Error = Error;
fn try_from(s: &Il2CppString) -> Result<Self> {
if s.ptr.is_null() {
return Err(Error::Bridge("Il2CppString pointer is null".into()));
}
unsafe { (*s.ptr).to_string() }
.ok_or_else(|| Error::Bridge("invalid UTF-16 in IL2CPP string".into()))
}
}
pub struct Il2CppArray<T> {
pub(crate) ptr: *mut bridge::structs::collections::Il2cppArray<T>,
}
impl<T: Copy + 'static> Il2CppArray<T> {
pub fn new(element_class: &Il2CppClass, size: usize) -> Result<Self> {
let ptr = bridge::structs::collections::Il2cppArray::<T>::new(&element_class.inner, size);
if ptr.is_null() {
return Err(Error::Bridge(
"Il2cppArray::new returned null — is IL2CPP initialized?".into(),
));
}
Ok(Self { ptr })
}
pub unsafe fn from_raw(ptr: *mut c_void) -> Self {
Self {
ptr: ptr as *mut bridge::structs::collections::Il2cppArray<T>,
}
}
pub fn as_ptr(&self) -> *mut c_void {
self.ptr as *mut c_void
}
pub fn get(&self, index: usize) -> T {
unsafe { (*self.ptr).get(index) }
}
pub fn at(&self, index: usize) -> Option<T> {
if index >= self.len() {
return None;
}
Some(self.get(index))
}
pub fn set(&mut self, index: usize, value: T) {
unsafe { (*self.ptr).set(index, value) }
}
pub fn len(&self) -> usize {
unsafe { (*self.ptr).len() }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_vec(&self) -> Vec<T> {
unsafe { (*self.ptr).to_vector() }
}
}