unity-helper 1.3.2__tar.gz → 1.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unity_helper
3
- Version: 1.3.2
3
+ Version: 1.3.4
4
4
  Summary: Runtime inspection toolkit for Unity IL2CPP applications — explore classes, methods, fields, and live objects in real time.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "unity_helper"
3
- version = "1.3.2"
3
+ version = "1.3.4"
4
4
  description = "Runtime inspection toolkit for Unity IL2CPP applications — explore classes, methods, fields, and live objects in real time."
5
5
  authors = ["Chasss"]
6
6
  readme = "README.md"
@@ -5,7 +5,6 @@ Reserved for internal use only.
5
5
 
6
6
  import ctypes
7
7
  from .structures import Vec3, Quaternion, Il2CppAssembly, Bounds, RaycastHit, Ray, Matrix4x4, Color, Vec2, Rect, Scene
8
- from .mono import MonoClass
9
8
 
10
9
 
11
10
  class Bindings():
@@ -62,9 +61,7 @@ class Bindings():
62
61
  print(f"Failed to find: {method_name} some built in features wont work")
63
62
  return 0
64
63
 
65
- return 0
66
-
67
- def _initialize(self):
64
+ def _initialize_internals(self):
68
65
  self._il2cpp_init = self.__DO_API(self.game_asm.il2cpp_init, [], ctypes.c_void_p)
69
66
  self._il2cpp_domain_get = self.__DO_API(self.game_asm.il2cpp_domain_get, [], ctypes.c_void_p)
70
67
  self._il2cpp_thread_attach = self.__DO_API(self.game_asm.il2cpp_thread_attach, [ctypes.c_void_p], ctypes.c_void_p)
@@ -90,6 +87,7 @@ class Bindings():
90
87
  self._il2cpp_class_get_namespace = self.__DO_API(self.game_asm.il2cpp_class_get_namespace, [ctypes.c_void_p], ctypes.c_char_p)
91
88
  self._il2cpp_class_get_name = self.__DO_API(self.game_asm.il2cpp_class_get_name, [ctypes.c_void_p], ctypes.c_char_p)
92
89
  self._il2cpp_field_static_get_value = self.__DO_API(self.game_asm.il2cpp_field_static_get_value, [ctypes.c_void_p, ctypes.c_void_p], None)
90
+ self._il2cpp_field_static_set_value = self.__DO_API(self.game_asm.il2cpp_field_static_set_value, [ctypes.c_void_p, ctypes.c_void_p], None)
93
91
  self._il2cpp_field_get_flags = self.__DO_API(self.game_asm.il2cpp_field_get_flags, [ctypes.c_void_p], ctypes.c_int)
94
92
  self._il2cpp_field_set_value = self.__DO_API(self.game_asm.il2cpp_field_set_value, [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p], None)
95
93
  self._il2cpp_object_unbox = self.__DO_API(self.game_asm.il2cpp_object_unbox, [ctypes.c_void_p], ctypes.c_void_p)
@@ -113,18 +111,14 @@ class Bindings():
113
111
  self._il2cpp_method_is_inflated = self.__DO_API(self.game_asm.il2cpp_class_is_abstract, [ctypes.c_void_p], ctypes.c_bool)
114
112
  self._il2cpp_method_is_generic = self.__DO_API(self.game_asm.il2cpp_class_is_generic, [ctypes.c_void_p], ctypes.c_bool)
115
113
  self._il2cpp_method_is_instance = self.__DO_API(self.game_asm.il2cpp_class_is_inflated, [ctypes.c_void_p], ctypes.c_bool)
116
-
117
- self._domain: int|None = None
118
- self._attached = False
119
- self._assembly_cache: dict[str, int] = {}
120
- self._image_cache: dict[int, int] = {}
121
- self._methodInfoData: dict[str, int] = {}
122
- self._class_cache: list[MonoClass] = []
123
-
114
+ self._il2cpp_object_new = self.__DO_API(self.game_asm.il2cpp_object_new, [ctypes.c_void_p], ctypes.c_void_p)
124
115
 
125
116
  if self.init_il2cpp:
126
117
  self._il2cpp_init()
127
118
 
119
+ def _initialize_class_bindings(self):
120
+ self._ensure_attached()
121
+
128
122
  self._component = self.get_class_from_name('UnityEngine.CoreModule.dll', 'UnityEngine.Component')
129
123
 
130
124
  self._UnityEngine_Component__GetComponent = ctypes.WINFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p)(self.__find_method_by_criteria('_UnityEngine_Component__GetComponent', self._component, 'GetComponent', 1, 'System.Type type'))
@@ -229,6 +223,8 @@ class Bindings():
229
223
  self._UnityEngine_Object__get_hideFlags = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p)(self.__find_method('_UnityEngine_Object__get_hideFlags', self._object, 'get_hideFlags'))
230
224
  self._UnityEngine_Object__set_hideFlags = ctypes.WINFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p)(self.__find_method('_UnityEngine_Object__set_hideFlags', self._object, 'set_hideFlags'))
231
225
  self._UnityEngine_Object__FindObjectFromInstanceID = ctypes.WINFUNCTYPE(ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p)(self.__find_method('_UnityEngine_Object__FindObjectFromInstanceID', self._object, 'FindObjectFromInstanceID'))
226
+ self._UnityEngine_Object__DontDestroyOnLoad = ctypes.WINFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p)(self.__find_method('_UnityEngine_Object__DontDestroyOnLoad', self._object, 'DontDestroyOnLoad'))
227
+
232
228
 
233
229
  self._transform = self.get_class_from_name('UnityEngine.CoreModule.dll', 'UnityEngine.Transform')
234
230
 
@@ -315,7 +311,7 @@ class Bindings():
315
311
  self._UnityEngine_SceneManager__LoadScene = ctypes.WINFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p)(self.__find_method('_UnityEngine_SceneManager__LoadScene', self._scene_manager, 'LoadScene', 1))
316
312
  self._UnityEngine_SceneManager__SetActiveScene = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)(self.__find_method('_UnityEngine_SceneManager__SetActiveScene', self._scene_manager, 'SetActiveScene'))
317
313
  self._UnityEngine_SceneManager__GetSceneAt = ctypes.WINFUNCTYPE(Scene, ctypes.c_int, ctypes.c_void_p)(self.__find_method('_UnityEngine_SceneManager__GetSceneAt', self._scene_manager, 'GetSceneAt', 1))
318
-
314
+ self._UnityEngine_SceneManager__MoveGameObjectToScene = ctypes.WINFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p)(self.__find_method('_UnityEngine_SceneManager__MoveGameObjectToScene', self._scene_manager, 'MoveGameObjectToScene'))
319
315
 
320
316
  self._physics = self.get_class_from_name('UnityEngine.PhysicsModule.dll', 'UnityEngine.Physics')
321
317
 
@@ -9,11 +9,11 @@ import ctypes
9
9
  from . import memory
10
10
  from contextlib import contextmanager
11
11
  from .mono import MonoClass, MonoMethod
12
- from .objects import Camera, Object
12
+ from .objects import Camera, GameObject
13
13
  from .bindings import Bindings
14
14
  from .structures import Il2CppArray, Vec3, Il2CppAssembly, Quaternion, Color, Vec2, Rect
15
15
  from .constants import TypeAttribute
16
-
16
+ import threading
17
17
 
18
18
  class Il2cpp(Bindings):
19
19
  """
@@ -21,20 +21,93 @@ class Il2cpp(Bindings):
21
21
 
22
22
  Args:
23
23
  warn_on_missing (bool): If True, emits a warning when a built in requested method is not found; if False, missing methods fail silently. Defaults to `True`.
24
- init_il2cpp (bool): If True it manually calls il2cpp_init to prevent crashing when attemping to call functions that rely on il2cpp to be initialized first (may break some games). Defaults to `True`.
24
+ init_il2cpp (bool): If True, manually calls il2cpp_init to prevent crashing when attemping to call functions that rely on il2cpp to be initialized first (may break some games). Defaults to `True`.
25
+ init_helpers (bool): If True, initializes helper functions like UnityEngine_Component__GetComponent which are used within ``objects.py`` module. Defaults to ``True``.
25
26
  """
26
27
  inst = None
27
- def __init__(self, warn_on_missing:bool=True, init_il2cpp:bool=True):
28
+ def __init__(self, warn_on_missing:bool=True, init_il2cpp:bool=True, init_helpers=True):
28
29
  self.game_asm = ctypes.WinDLL("GameAssembly.dll")
30
+
29
31
  self.memory = memory
30
- self.warn_on_missing = warn_on_missing
31
- self.init_il2cpp = init_il2cpp
32
+ self.warn_on_missing:bool = warn_on_missing
33
+ self.init_il2cpp:bool = init_il2cpp
34
+ self._tls = threading.local()
35
+ self._tls.attached = None
36
+ self._tls.thread_ptr = None
37
+ self._tls.external_attach = None
38
+
39
+ self._assembly_cache: dict[str, int] = {}
40
+ self._image_cache: dict[int, int] = {}
41
+ self._methodInfoData: dict[str, int] = {}
42
+ self._class_cache: list[MonoClass] = []
43
+
32
44
  Il2cpp.inst = self
33
- self._initialize()
45
+ self._initialize_internals()
46
+
47
+ if init_helpers:
48
+ self._initialize_class_bindings()
34
49
 
35
50
  def _get_domain_raw(self) -> int|None:
36
51
  dom = self._il2cpp_domain_get()
37
52
  return int(dom) if dom else None
53
+
54
+ def _ensure_attached(self):
55
+ """
56
+ Attach current thread once.
57
+ Safe to call repeatedly.
58
+ """
59
+
60
+ # already attached by us
61
+ if getattr(self._tls, "attached", False):
62
+ return self._tls.thread_ptr
63
+
64
+ # already attached externally (Unity/game thread)
65
+ current_thread = None
66
+
67
+ if self._il2cpp_thread_current:
68
+ try:
69
+ current_thread = self._il2cpp_thread_current()
70
+ except Exception:
71
+ current_thread = None
72
+
73
+ if current_thread:
74
+ self._tls.attached = True
75
+ self._tls.thread_ptr = current_thread
76
+ self._tls.external_attach = True
77
+ return current_thread
78
+
79
+ # attach ourselves
80
+ dom = self._get_domain_raw()
81
+ if not dom:
82
+ raise RuntimeError("il2cpp domain not available")
83
+
84
+ thread_ptr = self._il2cpp_thread_attach(ctypes.c_void_p(dom))
85
+
86
+ self._tls.attached = True
87
+ self._tls.thread_ptr = thread_ptr
88
+ self._tls.external_attach = False
89
+
90
+ return thread_ptr
91
+
92
+ def _detach_current_thread(self):
93
+ """
94
+ Detach only if WE attached it.
95
+ """
96
+
97
+ if not getattr(self._tls, "attached", False):
98
+ return
99
+
100
+ if getattr(self._tls, "external_attach", False):
101
+ return
102
+
103
+ try:
104
+ if self._il2cpp_thread_detach:
105
+ self._il2cpp_thread_detach(self._tls.thread_ptr)
106
+ except Exception:
107
+ pass
108
+
109
+ self._tls.attached = False
110
+ self._tls.thread_ptr = None
38
111
 
39
112
 
40
113
  @contextmanager
@@ -81,27 +154,25 @@ class Il2cpp(Bindings):
81
154
 
82
155
 
83
156
  def __open_assembly(self, assembly_name:str) -> int|None:
84
- with self._attached_context():
85
- if assembly_name in self._assembly_cache:
86
- return self._assembly_cache[assembly_name]
87
- dom = self._get_domain_raw()
88
- asm = self._il2cpp_domain_assembly_open(ctypes.c_void_p(dom), assembly_name.encode())
89
- if not asm:
90
- return None
91
- self._assembly_cache[assembly_name] = int(asm)
92
- return int(asm)
157
+ if assembly_name in self._assembly_cache:
158
+ return self._assembly_cache[assembly_name]
159
+ dom = self._get_domain_raw()
160
+ asm = self._il2cpp_domain_assembly_open(ctypes.c_void_p(dom), assembly_name.encode())
161
+ if not asm:
162
+ return None
163
+ self._assembly_cache[assembly_name] = int(asm)
164
+ return int(asm)
93
165
 
94
166
  def __get_image_from_assembly(self, assembly_ptr: int) -> int|None:
95
- with self._attached_context():
96
- if assembly_ptr in self._image_cache:
97
- return self._image_cache[assembly_ptr]
98
- try:
99
- ptr = ctypes.cast(ctypes.c_void_p(assembly_ptr), ctypes.POINTER(ctypes.c_void_p))
100
- img = int(ptr[0])
101
- except Exception:
102
- return None
103
- self._image_cache[assembly_ptr] = img
104
- return img
167
+ if assembly_ptr in self._image_cache:
168
+ return self._image_cache[assembly_ptr]
169
+ try:
170
+ ptr = ctypes.cast(ctypes.c_void_p(assembly_ptr), ctypes.POINTER(ctypes.c_void_p))
171
+ img = int(ptr[0])
172
+ except Exception:
173
+ return None
174
+ self._image_cache[assembly_ptr] = img
175
+ return img
105
176
 
106
177
 
107
178
  def _read_il2cpp_array(self, arr_ptr) -> int|None:
@@ -211,21 +282,21 @@ class Il2cpp(Bindings):
211
282
  if not img:
212
283
  return None
213
284
 
214
- with self._attached_context():
215
- cls = self._il2cpp_class_from_name(ctypes.c_void_p(img), namespace.encode(), klass.encode())
216
- if not cls:
217
- return None
218
-
219
- type_ = self._il2cpp_class_get_type(ctypes.c_void_p(cls))
220
- type_obj = self._il2cpp_type_get_object(type_)
221
- flags = TypeAttribute(self._il2cpp_class_get_flags(cls))
222
- is_static = (TypeAttribute.ABSTRACT in flags) and (TypeAttribute.SEALED in flags)
223
-
224
- monoclass = MonoClass(self, int(cls), klass, flags, type_obj, type_, is_static)
225
- if not any(i.name == monoclass.name and i.cls == monoclass.cls for i in self._class_cache):
226
- self._class_cache.append(monoclass)
285
+
286
+ cls = self._il2cpp_class_from_name(ctypes.c_void_p(img), namespace.encode(), klass.encode())
287
+ if not cls:
288
+ return None
289
+
290
+ type_ = self._il2cpp_class_get_type(ctypes.c_void_p(cls))
291
+ type_obj = self._il2cpp_type_get_object(type_)
292
+ flags = TypeAttribute(self._il2cpp_class_get_flags(cls))
293
+ is_static = (TypeAttribute.ABSTRACT in flags) and (TypeAttribute.SEALED in flags)
227
294
 
228
- return monoclass
295
+ monoclass = MonoClass(self, int(cls), klass, flags, type_obj, type_, is_static)
296
+ if not any(i.name == monoclass.name and i.cls == monoclass.cls for i in self._class_cache):
297
+ self._class_cache.append(monoclass)
298
+
299
+ return monoclass
229
300
 
230
301
  def find_method(self, assembly_name:str, klass:str, method_name:str, param_count:int|None = None, cache:bool = True) -> MonoMethod|None:
231
302
  """
@@ -316,56 +387,56 @@ class Il2cpp(Bindings):
316
387
  return None
317
388
 
318
389
 
319
- def find_object(self, object_str:str) -> Object|None:
390
+ def find_object(self, object_str:str) -> GameObject|None:
320
391
  """
321
- Retrieve an object by name.
392
+ Retrieve an game object by name.
322
393
 
323
394
  Args:
324
- object_str (str): Object name e.g., ``'Player'``.
395
+ object_str (str): GameObject name e.g., ``'Player'``.
325
396
 
326
397
  Returns:
327
- Object | None: Matching object if found, otherwise ``None``.
398
+ GameObject | None: Matching game object if found, otherwise ``None``.
328
399
  """
329
400
  try:
330
401
  obj = self._UnityEngine_GameObject__Find(self._il2cpp_string_new(object_str.encode()), self._methodInfoData['_UnityEngine_GameObject__Find'])
331
402
  if not obj:
332
403
  return None
333
- return Object(obj)
404
+ return GameObject(obj)
334
405
  except:
335
406
  return None
336
407
 
337
- def find_object_with_tag(self, tag_str:str) -> Object|None:
408
+ def find_object_with_tag(self, tag_str:str) -> GameObject|None:
338
409
  """
339
410
  Retreives a object by tag
340
411
 
341
412
  Args:
342
- tag_str (str): Object name e.g., ``'Player'``.
413
+ tag_str (str): GameObject name e.g., ``'Player'``.
343
414
 
344
415
  Returns:
345
- Object | None: Matching object if found, otherwise ``None``.
416
+ GameObject | None: Matching object if found, otherwise ``None``.
346
417
  """
347
418
  try:
348
419
  obj = self._UnityEngine_GameObject__FindGameObjectWithTag(self._il2cpp_string_new(tag_str.encode()), self._methodInfoData['_UnityEngine_GameObject__FindGameObjectWithTag'])
349
420
  if not obj:
350
421
  return None
351
- return Object(obj)
422
+ return GameObject(obj)
352
423
  except:
353
424
  return None
354
425
 
355
426
 
356
- def find_objects_with_tag(self, tag_str:str) -> list[Object]:
427
+ def find_objects_with_tag(self, tag_str:str) -> list[GameObject]:
357
428
  """
358
429
  Retreives a list of objects based on the given name
359
430
 
360
431
  Args:
361
- tag_str (str): Object name e.g., 'Player'
432
+ tag_str (str): GameObject name e.g., 'Player'
362
433
 
363
434
  Returns:
364
- List[Object]: A list containing object objects if found otherwise ``None``.
435
+ List[Ga,eObject]: A list containing object objects if found otherwise ``None``.
365
436
  """
366
437
  try:
367
438
  arr = self._UnityEngine_GameObject__FindGameObjectsWithTag(self._il2cpp_string_new(tag_str.encode()), self._methodInfoData['_UnityEngine_GameObject__FindGameObjectsWithTag'])
368
- objs = [Object(i) for i in self._read_il2cpp_array(arr) if i]
439
+ objs = [GameObject(i) for i in self._read_il2cpp_array(arr) if i]
369
440
  return objs
370
441
  except:
371
442
  return None
@@ -389,29 +460,28 @@ class Il2cpp(Bindings):
389
460
  return []
390
461
 
391
462
  classes = []
392
- with self._attached_context():
393
- class_count = self._il2cpp_image_get_class_count(ctypes.c_void_p(img_ptr))
394
- for i in range(class_count):
395
- cls_ptr = self._il2cpp_image_get_class(ctypes.c_void_p(img_ptr), i)
396
- if not cls_ptr:
397
- continue
398
- cls_namespace = self._il2cpp_class_get_namespace(ctypes.c_void_p(cls_ptr))
399
- cls_namespace = cls_namespace.decode() if cls_namespace else ""
400
- cls_name = self._il2cpp_class_get_name(ctypes.c_void_p(cls_ptr))
401
- cls_name = cls_name.decode() if cls_name else ""
402
- type_ = self._il2cpp_class_get_type(ctypes.c_void_p(cls_ptr))
403
- type_obj = self._il2cpp_type_get_object(type_)
404
-
405
- full_name = ".".join(filter(None, [cls_namespace, cls_name]))
406
- flags = TypeAttribute(self._il2cpp_class_get_flags(cls_ptr))
407
- is_static = (TypeAttribute.ABSTRACT in flags) and (TypeAttribute.SEALED in flags)
408
-
409
- cls = MonoClass(self, cls_ptr, full_name, flags, type_obj, type_, is_static)
410
-
411
- if not any(i.name == cls.name and i.object == i.object for i in self._class_cache):
412
- self._class_cache.append(cls)
413
-
414
- classes.append(cls)
463
+ class_count = self._il2cpp_image_get_class_count(ctypes.c_void_p(img_ptr))
464
+ for i in range(class_count):
465
+ cls_ptr = self._il2cpp_image_get_class(ctypes.c_void_p(img_ptr), i)
466
+ if not cls_ptr:
467
+ continue
468
+ cls_namespace = self._il2cpp_class_get_namespace(ctypes.c_void_p(cls_ptr))
469
+ cls_namespace = cls_namespace.decode() if cls_namespace else ""
470
+ cls_name = self._il2cpp_class_get_name(ctypes.c_void_p(cls_ptr))
471
+ cls_name = cls_name.decode() if cls_name else ""
472
+ type_ = self._il2cpp_class_get_type(ctypes.c_void_p(cls_ptr))
473
+ type_obj = self._il2cpp_type_get_object(type_)
474
+
475
+ full_name = ".".join(filter(None, [cls_namespace, cls_name]))
476
+ flags = TypeAttribute(self._il2cpp_class_get_flags(cls_ptr))
477
+ is_static = (TypeAttribute.ABSTRACT in flags) and (TypeAttribute.SEALED in flags)
478
+
479
+ cls = MonoClass(self, cls_ptr, full_name, flags, type_obj, type_, is_static)
480
+
481
+ if not any(i.name == cls.name and i.object == i.object for i in self._class_cache):
482
+ self._class_cache.append(cls)
483
+
484
+ classes.append(cls)
415
485
 
416
486
  return classes
417
487
 
@@ -424,36 +494,35 @@ class Il2cpp(Bindings):
424
494
  """
425
495
  assemblies = []
426
496
 
427
- with self._attached_context():
428
- domain = self._get_domain_raw()
497
+ domain = self._get_domain_raw()
429
498
 
430
- size = ctypes.c_size_t()
431
- assembly_array_ptr = self._il2cpp_domain_get_assemblies(
432
- ctypes.c_void_p(domain),
433
- ctypes.byref(size)
434
- )
499
+ size = ctypes.c_size_t()
500
+ assembly_array_ptr = self._il2cpp_domain_get_assemblies(
501
+ ctypes.c_void_p(domain),
502
+ ctypes.byref(size)
503
+ )
435
504
 
436
- assembly_array = ctypes.cast(
437
- assembly_array_ptr,
438
- ctypes.POINTER(ctypes.POINTER(Il2CppAssembly))
439
- )
505
+ assembly_array = ctypes.cast(
506
+ assembly_array_ptr,
507
+ ctypes.POINTER(ctypes.POINTER(Il2CppAssembly))
508
+ )
440
509
 
441
- for i in range(size.value):
442
- assembly_ptr = assembly_array[i]
443
- if not assembly_ptr:
444
- continue
510
+ for i in range(size.value):
511
+ assembly_ptr = assembly_array[i]
512
+ if not assembly_ptr:
513
+ continue
445
514
 
446
- assembly = assembly_ptr.contents
447
- if not assembly.image:
448
- continue
515
+ assembly = assembly_ptr.contents
516
+ if not assembly.image:
517
+ continue
449
518
 
450
- image = assembly.image.contents
451
- if not image.name:
452
- continue
519
+ image = assembly.image.contents
520
+ if not image.name:
521
+ continue
453
522
 
454
- name = image.name.decode("utf-8", errors="ignore")
523
+ name = image.name.decode("utf-8", errors="ignore")
455
524
 
456
- assemblies.append(name)
525
+ assemblies.append(name)
457
526
 
458
527
  return assemblies
459
528