#ifndef IL2CPP_H

#define IL2CPP_H

#include <_mingw.h>
#include <malloc.h>
#include <string.h>

#undef __fastcall
#define __fastcall __attribute__((__fastcall__))

#ifdef _WIN64
	#define IL2CPP_API __fastcall
#else
	#define IL2CPP_API __cdecl
#endif

#ifndef bool
	#define bool _Bool
	#define true 1
	#define false 0
#endif

#if 0
	#undef __fastcall
	#undef bool
	#define __fastcall
	#define bool unsigned char
#endif

struct MonitorData;
typedef void (*Il2CppMethodPointer)();

typedef struct Il2CppImage
{
	const char *name;
	const char *nameNoExt;
} Il2CppImage;

typedef struct Il2CppAssemblyName
{
	const char *m_pName;
	const char *m_pCulture;
	const char *m_pHash;
	const char *m_pPublicKey;
	unsigned int m_uHash;
	int m_iHashLength;
	unsigned int m_uFlags;
	int m_iMajor;
	int m_iMinor;
	int m_iBuild;
	int m_bRevision;
	unsigned __int8 m_uPublicKeyToken[8];
} Il2CppAssemblyName;

typedef struct Il2CppAssembly
{
	Il2CppImage *m_pImage;
	unsigned int m_uToken;
	int m_ReferencedAssemblyStart;
	int m_ReferencedAssemblyCount;
	Il2CppAssemblyName m_aName;
} Il2CppAssembly;

#ifdef UNITY_VERSION_2022_3_8F1
typedef struct Il2CppType
{
	void *data;
	unsigned int bits;
} Il2CppType;
#else
typedef struct Il2CppType
{
    union
    {
        void* m_pDummy;
        unsigned int m_uClassIndex;
        struct Il2CppType* m_pType;
        void* m_pArray;
        unsigned int m_uGenericParameterIndex;
        void* m_pGenericClass;
    };
    unsigned int m_uAttributes : 16;
    unsigned int m_uType : 8;
    unsigned int m_uMods : 6;
    unsigned int m_uByref : 1;
    unsigned int m_uPinned : 1;
} Il2CppType;
#endif

struct Il2CppClass;

typedef struct Il2CppFieldInfo
{
	const char *m_pName;
	Il2CppType *m_pType;
	struct Il2CppClass *m_pParentClass;
	int m_iOffset;
	int m_iAttributeIndex;
	unsigned int m_uToken;
} Il2CppFieldInfo;

typedef struct Il2CppParameterInfo
{
	const char *m_pName;
	int m_iPosition;
	unsigned int m_uToken;
	Il2CppType *m_pParameterType;
} Il2CppParameterInfo;

typedef struct Il2CppMethodInfo
{
	Il2CppMethodPointer m_pMethodPointer;
#ifdef UNITY_VERSION_2022_3_8F1
    void* m_pVirtualMethodPointer;
#endif
	void *m_pInvokerMethod;
	const char *m_pName;
	struct Il2CppClass *m_pClass;
	Il2CppType *m_pReturnType;
#ifdef UNITY_VERSION_2022_3_8F1
    struct Il2CppType** m_pParameters;
#else
    struct Il2CppParameterInfo* m_pParameters;
#endif
	union
	{
		void *m_pRGCTX;
		void *m_pMethodDefinition;
	};
	union
	{
		void *m_pGenericMethod;
		void *m_pGenericContainer;
	};
	unsigned int m_uToken;
	unsigned __int16 m_uFlags;
	unsigned __int16 m_uFlags2;
	unsigned __int16 m_uSlot;
	unsigned __int8 m_uArgsCount;
	unsigned __int8 m_uGeneric : 1;
	unsigned __int8 m_uInflated : 1;
	unsigned __int8 m_uWrapperType : 1;
	unsigned __int8 m_uMarshaledFromNative : 1;
} Il2CppMethodInfo;

typedef struct Il2CppPropertyInfo
{
	struct Il2CppClass *m_pParentClass;
	const char *m_pName;
	Il2CppMethodInfo *m_pGet;
	Il2CppMethodInfo *m_pSet;
	unsigned int m_uAttributes;
	unsigned int m_uToken;
} Il2CppPropertyInfo;

typedef struct Il2CppArrayBounds
{
	uintptr_t m_uLength;
  	int m_iLowerBound;
} Il2CppArrayBounds;

typedef struct Il2CppClass
{
	void *m_pImage;
	void *m_pGC;
	const char *m_pName;
	const char *m_pNamespace;
	void *m_pValue;
	void *m_pArgs;
	struct Il2CppClass *m_pElementClass;
	struct Il2CppClass *m_pCastClass;
	struct Il2CppClass *m_pDeclareClass;
	struct Il2CppClass *m_pParentClass;
	void *m_pGenericClass;
	void *m_pTypeDefinition;
	void *m_pInteropData;
	void *m_pFields;
	void *m_pEvents;
	void *m_pProperties;
	void **m_pMethods;
	struct Il2CppClass **m_pNestedTypes;
	struct Il2CppClass **m_ImplementedInterfaces;
	void *m_pInterfaceOffsets;
	void *m_pStaticFields;
	void *m_pRGCTX;
} Il2CppClass;

typedef struct Il2CppObject
{
	Il2CppClass *klass;
	struct MonitorData *monitor;
} Il2CppObject;

typedef struct Il2CppArray
{
	Il2CppObject base;

	Il2CppArrayBounds *bounds;
	unsigned int max_length;

	void *elems; // This is not actually a pointer, but the start of the array data. The actual size of this struct is sizeof(Il2CppArray) + (element_size * max_length)
} Il2CppArray;

typedef struct Il2CppString
{
	Il2CppObject base;

	int length;
	wchar_t chars[32]; // This is not actually a pointer, but the start of the string data. The actual size of this struct is sizeof(Il2CppString) + (length * sizeof(char))
} Il2CppString;

void IL2CPP_API il2cpp_init(const char *domain_name);
void IL2CPP_API il2cpp_init_utf16(const wchar_t *domain_name);
void IL2CPP_API il2cpp_shutdown(void);
void IL2CPP_API il2cpp_dumping_memory(bool dump);
void IL2CPP_API il2cpp_set_config_dir(const char *config_path);
void IL2CPP_API il2cpp_set_data_dir(const char *data_path);
void IL2CPP_API il2cpp_set_temp_dir(const char *temp_path);
void IL2CPP_API il2cpp_set_commandline_arguments(int argc, const char** argv, const char *basedir);
void IL2CPP_API il2cpp_set_commandline_arguments_utf16(int argc, const wchar_t** argv, const char *basedir);
void IL2CPP_API il2cpp_set_config_utf16(const wchar_t *executablePath);
void IL2CPP_API il2cpp_set_config(const char *executablePath);

void IL2CPP_API il2cpp_set_memory_callbacks(void **callbacks);
void IL2CPP_API il2cpp_add_internal_call(const char* name, Il2CppMethodPointer method);

Il2CppImage *IL2CPP_API il2cpp_get_corlib(void);

Il2CppMethodPointer IL2CPP_API il2cpp_resolve_icall(const char *name);

void *IL2CPP_API il2cpp_alloc(size_t size); // Just a method pointing to malloc
void *IL2CPP_API il2cpp_allocation_granularity(void);

void IL2CPP_API il2cpp_free(void *ptr); // Just a method pointing to free

// assembly
Il2CppImage *IL2CPP_API il2cpp_assembly_get_image(Il2CppAssembly *assembly);

// domain
Il2CppAssembly *IL2CPP_API il2cpp_domain_assembly_open(void *domain, const char *name);
Il2CppAssembly **IL2CPP_API il2cpp_domain_get_assemblies(void *domain, size_t *size);

void *IL2CPP_API il2cpp_domain_get(void);

// image
Il2CppAssembly *IL2CPP_API il2cpp_image_get_assembly(const Il2CppImage *image);
Il2CppMethodInfo *IL2CPP_API il2cpp_image_get_entry_point(const Il2CppImage *image);

Il2CppClass *IL2CPP_API il2cpp_image_get_class(const Il2CppImage *image, size_t index);

const char *IL2CPP_API il2cpp_image_get_name(const Il2CppImage *image);
const char *IL2CPP_API il2cpp_image_get_filename(const Il2CppImage *image);

size_t IL2CPP_API il2cpp_image_get_class_count(const Il2CppImage *image);

// array
Il2CppClass *IL2CPP_API il2cpp_array_class_get(Il2CppClass *klass, unsigned int rank);

Il2CppArray *IL2CPP_API il2cpp_array_new(Il2CppClass *klass, unsigned int length);
Il2CppArray *IL2CPP_API il2cpp_array_new_full(Il2CppClass *klass, unsigned int *lengths, unsigned int *lower_bounds);
Il2CppArray *IL2CPP_API il2cpp_array_new_specific(Il2CppClass *array_class, unsigned int length);
Il2CppArray *IL2CPP_API il2cpp_bounded_array_class_get(Il2CppClass *element_class, unsigned int rank, bool bounded);

int IL2CPP_API il2cpp_array_element_size(Il2CppClass *klass);

unsigned int IL2CPP_API il2cpp_array_get_byte_length(Il2CppArray *array);
unsigned int IL2CPP_API il2cpp_array_length(Il2CppArray *array);
unsigned int IL2CPP_API il2cpp_array_object_header_size(void);

// class
Il2CppType *IL2CPP_API il2cpp_class_enum_basetype(Il2CppClass *klass);
Il2CppType *IL2CPP_API il2cpp_class_get_type(Il2CppClass *klass);

Il2CppClass *IL2CPP_API il2cpp_class_from_il2cpp_type(Il2CppType *type);
Il2CppClass *IL2CPP_API il2cpp_class_from_name(Il2CppImage *image, const char *namespaze, const char *name);
Il2CppClass *IL2CPP_API il2cpp_class_from_system_type(void *reflection_type);
Il2CppClass *IL2CPP_API il2cpp_class_from_type(Il2CppType *type);
Il2CppClass *IL2CPP_API il2cpp_class_get_element_class(Il2CppClass *klass);
Il2CppClass *IL2CPP_API il2cpp_class_get_nested_types(Il2CppClass *klass, void **iter);
Il2CppClass *IL2CPP_API il2cpp_class_get_interfaces(Il2CppClass *klass, void **iter);
Il2CppClass *IL2CPP_API il2cpp_class_get_parent(Il2CppClass *klass);
Il2CppClass *IL2CPP_API il2cpp_class_get_declaring_type(Il2CppClass *klass);

Il2CppFieldInfo *IL2CPP_API il2cpp_class_get_fields(Il2CppClass *klass, void **iter);
Il2CppFieldInfo *IL2CPP_API il2cpp_class_get_field_from_name(Il2CppClass *klass, const char *name);

Il2CppMethodInfo *IL2CPP_API il2cpp_class_get_methods(Il2CppClass *klass, void **iter);
Il2CppMethodInfo *IL2CPP_API il2cpp_class_get_method_from_name(Il2CppClass *klass, const char *name, int args_count);

Il2CppPropertyInfo *IL2CPP_API il2cpp_class_get_properties(Il2CppClass *klass, void **iter);
Il2CppPropertyInfo *IL2CPP_API il2cpp_class_get_property_from_name(Il2CppClass *klass, const char *name);

Il2CppImage *IL2CPP_API il2cpp_class_get_image(Il2CppClass *klass);

void IL2CPP_API il2cpp_class_for_each(void (*action)(Il2CppClass *klass, void *context), void *context); // Goes through all classes and calls the action callback for each one
void IL2CPP_API il2cpp_class_get_bitmap(Il2CppClass *klass, size_t *bitmap);

bool IL2CPP_API il2cpp_class_is_generic(Il2CppClass *klass);
bool IL2CPP_API il2cpp_class_is_inflated(Il2CppClass *klass);
bool IL2CPP_API il2cpp_class_is_assignable_from(Il2CppClass *klass, Il2CppClass *oklass);
bool IL2CPP_API il2cpp_class_is_subclass_of(Il2CppClass *klass, Il2CppClass *oklass, bool check_interfaces);
bool IL2CPP_API il2cpp_class_is_valuetype(Il2CppClass *klass);
bool IL2CPP_API il2cpp_class_is_blittable(Il2CppClass *klass);
bool IL2CPP_API il2cpp_class_is_abstract(Il2CppClass *klass);
bool IL2CPP_API il2cpp_class_is_interface(Il2CppClass *klass);
bool IL2CPP_API il2cpp_class_is_enum(Il2CppClass *klass);
bool IL2CPP_API il2cpp_class_has_parent(Il2CppClass *klass, Il2CppClass *parent);
bool IL2CPP_API il2cpp_class_has_attribute(Il2CppClass *klass, Il2CppClass *attr_klass);
bool IL2CPP_API il2cpp_class_has_references(Il2CppClass *klass);

unsigned int IL2CPP_API il2cpp_class_instance_size(Il2CppClass *klass);
unsigned int IL2CPP_API il2cpp_class_get_type_token(Il2CppClass *klass);

int IL2CPP_API il2cpp_class_array_element_size(Il2CppClass *klass);
int IL2CPP_API il2cpp_class_get_flags(Il2CppClass *klass);
int IL2CPP_API il2cpp_class_get_rank(Il2CppClass *klass);
int IL2CPP_API il2cpp_class_value_size(Il2CppClass *klass, int *align);

size_t IL2CPP_API il2cpp_class_num_fields(Il2CppClass *klass);
size_t IL2CPP_API il2cpp_class_get_bitmap_size(Il2CppClass *klass);

void *IL2CPP_API il2cpp_class_get_events(Il2CppClass *klass, void **iter);

const char *IL2CPP_API il2cpp_class_get_assemblyname(Il2CppClass *klass);
const char *IL2CPP_API il2cpp_class_get_name(Il2CppClass *klass);
const char *IL2CPP_API il2cpp_class_get_namespace(Il2CppClass *klass);

// field
Il2CppClass *IL2CPP_API il2cpp_field_get_parent(Il2CppFieldInfo *field);

Il2CppType *IL2CPP_API il2cpp_field_get_type(Il2CppFieldInfo *field);

Il2CppObject* IL2CPP_API il2cpp_field_get_value_object(Il2CppFieldInfo *field, Il2CppObject* obj);

void IL2CPP_API il2cpp_field_static_get_value(Il2CppFieldInfo *field, void **value);
void IL2CPP_API il2cpp_field_get_value(Il2CppObject* obj, Il2CppFieldInfo *field, void **value);
void IL2CPP_API il2cpp_field_static_set_value(Il2CppFieldInfo *field, void *value);
void IL2CPP_API il2cpp_field_set_value(Il2CppObject* obj, Il2CppFieldInfo *field, void *value);
void IL2CPP_API il2cpp_field_set_value_object(Il2CppObject* obj, Il2CppFieldInfo *field, Il2CppObject* value);

size_t IL2CPP_API il2cpp_field_get_offset(Il2CppFieldInfo *field);

int IL2CPP_API il2cpp_field_get_flags(Il2CppFieldInfo *field);

const char *IL2CPP_API il2cpp_field_get_name(Il2CppFieldInfo *field);

bool IL2CPP_API il2cpp_field_has_attribute(Il2CppFieldInfo *field, Il2CppClass *attr_klass);

// method
Il2CppType *IL2CPP_API il2cpp_method_get_return_type(Il2CppMethodInfo *method);
Il2CppType *IL2CPP_API il2cpp_method_get_param(Il2CppMethodInfo *method, unsigned int index);

Il2CppClass *IL2CPP_API il2cpp_method_get_class(Il2CppMethodInfo *method);
Il2CppClass *IL2CPP_API il2cpp_method_get_declaring_type(Il2CppMethodInfo *method);

bool IL2CPP_API il2cpp_method_is_generic(Il2CppMethodInfo *method);
bool IL2CPP_API il2cpp_method_is_inflated(Il2CppMethodInfo *method);
bool IL2CPP_API il2cpp_method_is_instance(Il2CppMethodInfo *method);
bool IL2CPP_API il2cpp_method_has_attribute(Il2CppMethodInfo *method, Il2CppClass *attr_klass);

unsigned int IL2CPP_API il2cpp_method_get_param_count(Il2CppMethodInfo *method);
unsigned int IL2CPP_API il2cpp_method_get_flags(Il2CppMethodInfo *method);
unsigned int IL2CPP_API il2cpp_method_get_token(Il2CppMethodInfo *method);

const char *IL2CPP_API il2cpp_method_get_name(Il2CppMethodInfo *method);
const char *IL2CPP_API il2cpp_method_get_param_name(Il2CppMethodInfo *method, unsigned int index);

// GC
void IL2CPP_API il2cpp_gc_collect(int maxGenerations);
void IL2CPP_API il2cpp_gc_disable(void);
void IL2CPP_API il2cpp_gc_enable(void);
void IL2CPP_API il2cpp_gc_wbarrier_set_field(Il2CppObject *obj, void **target, void *value);

int IL2CPP_API il2cpp_gc_collect_a_little(void);

bool IL2CPP_API il2cpp_gc_is_disabled(void);

unsigned long long IL2CPP_API il2cpp_gc_get_used_size(void);
unsigned long long IL2CPP_API il2cpp_gc_get_heap_size(void);

// GC Handle
void IL2CPP_API il2cpp_gchandle_free(unsigned int gchandle);

unsigned int IL2CPP_API il2cpp_gchandle_new(Il2CppObject *obj, bool pinned);
unsigned int IL2CPP_API il2cpp_gchandle_new_weakref(Il2CppObject *obj, bool track_resurrection);

Il2CppObject* IL2CPP_API il2cpp_gchandle_get_target(unsigned int gchandle);

// object
Il2CppClass *IL2CPP_API il2cpp_object_get_class(Il2CppObject *obj);

Il2CppObject *IL2CPP_API il2cpp_object_new(Il2CppClass *klass);

Il2CppMethodInfo *IL2CPP_API il2cpp_object_get_virtual_method(Il2CppObject *obj, Il2CppMethodInfo *method);

unsigned int IL2CPP_API il2cpp_object_get_size(Il2CppObject *obj);

void *IL2CPP_API il2cpp_object_unbox(Il2CppObject *obj);

// property
Il2CppMethodInfo *IL2CPP_API il2cpp_property_get_get_method(Il2CppPropertyInfo *prop);
Il2CppMethodInfo *IL2CPP_API il2cpp_property_get_set_method(Il2CppPropertyInfo *prop);

Il2CppClass *IL2CPP_API il2cpp_property_get_parent(Il2CppPropertyInfo *prop);

const char *IL2CPP_API il2cpp_property_get_name(Il2CppPropertyInfo *prop);

unsigned int IL2CPP_API il2cpp_property_get_flags(Il2CppPropertyInfo *prop);

// runtime
Il2CppObject *IL2CPP_API il2cpp_runtime_invoke(Il2CppMethodInfo *method, void *obj, void **params, void **exc);
Il2CppObject *IL2CPP_API il2cpp_runtime_invoke_convert_args(Il2CppMethodInfo *method, void *obj, Il2CppObject **params, int paramCount, void **exc);

void IL2CPP_API il2cpp_runtime_class_init(Il2CppClass *klass);
void IL2CPP_API il2cpp_runtime_object_init(Il2CppObject *obj);

void IL2CPP_API il2cpp_runtime_object_init_exception(Il2CppObject *obj, void** exc);

// type
Il2CppObject *IL2CPP_API il2cpp_type_get_object(const Il2CppType *type);
Il2CppClass *IL2CPP_API il2cpp_type_get_class_or_element_class(const Il2CppType *type);

const char *IL2CPP_API il2cpp_type_get_name(const Il2CppType *type);
const char *IL2CPP_API il2cpp_type_get_assembly_qualified_name(const Il2CppType *type);

bool IL2CPP_API il2cpp_type_is_byref(const Il2CppType *type);
bool IL2CPP_API il2cpp_type_equals(const Il2CppType *type, const Il2CppType *otherType);

unsigned int IL2CPP_API il2cpp_type_get_attrs(const Il2CppType *type);

int IL2CPP_API il2cpp_type_get_type(const Il2CppType *type);
// String
Il2CppString *IL2CPP_API il2cpp_string_new(const char *str);
Il2CppString *IL2CPP_API il2cpp_string_new_utf16(const wchar_t *str, int length);
Il2CppString *IL2CPP_API il2cpp_string_new_len(const char *str, int length);
Il2CppString *IL2CPP_API il2cpp_string_new_wrapper(const char *str);
Il2CppString *IL2CPP_API il2cpp_string_intern(Il2CppString *str);

int IL2CPP_API il2cpp_string_length(Il2CppString *str);

const char *IL2CPP_API il2cpp_string_chars(Il2CppString *str);

// thread
void IL2CPP_API il2cpp_thread_detach(void *thread);

void* IL2CPP_API il2cpp_thread_current(void);
void *IL2CPP_API il2cpp_thread_attach(void *domain);

// Utils for working with il2cpp
Il2CppClass* FindClass(const char *namespaze, const char *name)
{
	void *domain = il2cpp_domain_get();
	size_t assembly_count;
	if (!domain)
		return NULL;

	Il2CppAssembly **assemblies = il2cpp_domain_get_assemblies(domain, &assembly_count);
	for (size_t i = 0; i < assembly_count; i++)
	{
		Il2CppImage *image = il2cpp_assembly_get_image(assemblies[i]);
		if (!image)
			continue;

		Il2CppClass *klass = il2cpp_class_from_name(image, namespaze, name);
		if (klass)
			return klass;
	}
	return NULL;
}

Il2CppMethodInfo *Class_GetMethod(Il2CppClass *klass, const char *method_name, int args_count)
{
	void *iter = NULL;
	Il2CppMethodInfo *method;
	while ((method = il2cpp_class_get_methods(klass, &iter)) != NULL)
	{
		if ((method->m_uArgsCount == args_count || args_count == -1) && strcmp(method->m_pName, method_name) == 0)
			return method;
	}
	return NULL;
}

Il2CppMethodInfo *GetMethod(const char *namespaze, const char *class_name, const char *method_name, int args_count)
{
	Il2CppClass *klass = FindClass(namespaze, class_name);
	if (!klass)
		return NULL;

	return Class_GetMethod(klass, method_name, args_count);
}

void *Array_GetItem(Il2CppArray *array, unsigned int index)
{
	if (index >= array->max_length)
		return NULL;

	return (unsigned long long)&array->elems + (index * il2cpp_array_element_size(il2cpp_class_get_element_class((Il2CppClass*)array->base.klass)));
}

void *Array_Begin(Il2CppArray *array)
{
	return (void*)&array->elems;
}

void *Array_End(Il2CppArray *array)
{
	return (void*)((unsigned long long)&array->elems + (array->max_length * il2cpp_array_element_size(il2cpp_class_get_element_class((Il2CppClass*)array->base.klass))));
}

Il2CppString* StringUTF16_NewLen(const wchar_t *str, int length)
{
	return il2cpp_string_new_utf16(str, length);
}

Il2CppString* String_NewLen(const char *str, int length)
{
	return il2cpp_string_new_len(str, length);
}

Il2CppString* String_New(const char *str)
{
	return il2cpp_string_new(str);
}

Il2CppString* StringUTF16_New(const wchar_t *str)
{
	return il2cpp_string_new_utf16(str, wcslen(str));
}

Il2CppString *StringNoGC_New(const char* str)
{
	size_t length = strlen(str);
	Il2CppString *il2cpp_str = (Il2CppString *)il2cpp_alloc(sizeof(Il2CppString) + (length + 1 * sizeof(char))); // +1 for null terminator
	memset(il2cpp_str, 0, sizeof(Il2CppString) + (length + 1 * sizeof(char))); // clear to avoid garbage data in string

	il2cpp_str->base.klass = FindClass("System", "String"); // Need to set the klass pointer manually since we're not using il2cpp_string_new, and to avoid crashes when using the string later on
	il2cpp_str->length = length;
	
	memcpy(il2cpp_str->chars, str, length);
	return il2cpp_str;
}

Il2CppString *StringUTF16_NoGC_New(const wchar_t* str)
{
	size_t length = wcslen(str);
	Il2CppString *il2cpp_str = (Il2CppString *)il2cpp_alloc(sizeof(Il2CppString) + (length + 1 * sizeof(wchar_t))); // +1 for null terminator
	memset(il2cpp_str, 0, sizeof(Il2CppString) + (length + 1 * sizeof(wchar_t))); // clear to avoid garbage data in string

	il2cpp_str->base.klass = FindClass("System", "String"); // Need to set the klass pointer manually since we're not using il2cpp_string_new, and to avoid crashes when using the string later on
	il2cpp_str->length = length;

	memcpy(il2cpp_str->chars, str, length * sizeof(wchar_t));
	return il2cpp_str;
}

#endif