#include "UnityExplorer.h" #include "helpers.h" #include #include #include #include #include namespace { struct Il2CppString { void* klass; void* monitor; int32_t length; wchar_t chars[1]; }; struct Il2CppClassLite { void* image; void* gc_desc; const char* name; const char* namespaze; }; using FnCameraMain = void* (*)(void*); using FnSceneGetActive = void* (*)(void*); using FnSceneGetName = void* (*)(void*); using FnGameObjectGetTransform = void* (*)(void*); using FnGameObjectSetActive = void (*)(void*, bool); using FnGameObjectGetActiveInHierarchy = bool (*)(void*); using FnComponentGetTransform = void* (*)(void*); using FnGameObjectGetComponentByIndex = void* (*)(void*, int32_t); using FnObjectGetName = void* (*)(void*); using FnIl2CppObjectGetClass = void* (*)(void*); using FnGetVec3 = Vec3* (*)(void*); using FnSetVec3 = void (*)(void*, Vec3*); using FnGetQuat = Quat* (*)(void*); using FnSetQuat = void (*)(void*, Quat*); bool ContainsNoCase(const std::string& text, const char* needle_cstr) { const std::string needle = needle_cstr ? needle_cstr : ""; if (needle.empty()) { return true; } auto lower = [](const std::string& v) { std::string out = v; std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return out; }; return lower(text).find(lower(needle)) != std::string::npos; } std::string Il2CppStringToUtf8(void* string_ptr) { if (!string_ptr) { return {}; } const Il2CppString* str = reinterpret_cast(string_ptr); if (!str || str->length <= 0 || str->length > 4096) { return {}; } const wchar_t* begin = str->chars; const wchar_t* end = begin + str->length; return WideToUtf8(std::wstring(begin, end)); } bool TryGetClassName(void* object_ptr, std::string* out_ns, std::string* out_name) { if (!object_ptr) { return false; } auto object_get_class = GetIl2cppFn(IL2CPP::il2cpp_object_get_class); if (!object_get_class) { return false; } void* class_ptr = object_get_class(object_ptr); if (!class_ptr) { return false; } const Il2CppClassLite* klass = reinterpret_cast(class_ptr); if (!klass) { return false; } const char* ns = klass->namespaze ? klass->namespaze : ""; const char* name = klass->name ? klass->name : ""; if (name[0] == '\0') { return false; } if (out_ns) { *out_ns = ns; } if (out_name) { *out_name = name; } return true; } std::string FormatClassName(const std::string& ns, const std::string& name) { if (name.empty()) { return {}; } if (ns.empty()) { return name; } return ns + "." + name; } } // namespace UnityExplorerModule::UnityExplorerModule() : BaseModule("UnityExplorerModule", "Unity Explorer", ModuleCategory::Visual) { search_filter_.fill('\0'); } void UnityExplorerModule::RegisterTab() { active_window_ = true; auto_refresh_ = false; refresh_interval_ = 3.0f; last_refresh_time_ = static_cast(GetTickCount64()) / 1000.0; selected_scene_ = 0; selected_object_ = -1; inspector_active_ = false; inspected_obj_ptr_ = nullptr; inspected_obj_name_.clear(); inspector_ = InspectorState{}; SetEnabled(true); scenes_.clear(); objects_.clear(); scenes_.push_back({"DontDestroyOnLoad"}); } void UnityExplorerModule::RefreshScenes() { scenes_.clear(); scenes_.push_back({"DontDestroyOnLoad"}); scenes_.push_back({"ActiveScene"}); if (selected_scene_ < 0) { selected_scene_ = 0; } if (selected_scene_ >= static_cast(scenes_.size())) { selected_scene_ = static_cast(scenes_.size()) - 1; } RefreshObjectsForScene(); } void UnityExplorerModule::RefreshObjectsForScene() { objects_.clear(); selected_object_ = -1; inspector_active_ = false; inspected_obj_ptr_ = nullptr; inspected_obj_name_.clear(); inspector_ = InspectorState{}; if (selected_scene_ < 0 || selected_scene_ >= static_cast(scenes_.size())) { return; } const std::string& selected_scene_name = scenes_[selected_scene_].name; if (selected_scene_name == "DontDestroyOnLoad") { objects_.push_back({"[UnityEngine]: DontDestroyOnLoad Root", nullptr}); } auto camera_main = GetIl2cppFn(IL2CPP::Camera_get_main); if (camera_main) { void* camera = camera_main(nullptr); if (camera) { objects_.push_back({"[UnityEngine]: Main Camera", camera}); } } if (selected_scene_name != "DontDestroyOnLoad") { objects_.push_back({"[Game]: Player", nullptr}); objects_.push_back({"[Game]: WorldRoot", nullptr}); objects_.push_back({"[UI]: UIRoot", nullptr}); } else if (objects_.size() <= 1) { objects_.push_back({"[UnityEngine]: DontDestroyOnLoad (Not Found)", nullptr}); } } void UnityExplorerModule::Render() { if (!ImGui::CollapsingHeader(Tr("Unity Explorer"), ImGuiTreeNodeFlags_DefaultOpen)) { return; } bool enabled = Enabled(); if (ImGui::Checkbox(Tr("ON / OFF"), &enabled)) { SetEnabled(enabled); if (enabled && scenes_.size() <= 1) { RefreshScenes(); } } if (!Enabled()) { return; } active_window_ = true; if (!ImGui::Begin(Tr("Unity Explorer Window"), &active_window_)) { ImGui::End(); return; } ImGui::TextUnformatted("Search Filter:"); ImGui::InputText("##SearchFilter", search_filter_.data(), search_filter_.size()); if (ImGui::Button("Refresh")) { RefreshScenes(); last_refresh_time_ = static_cast(GetTickCount64()) / 1000.0; } ImGui::SameLine(); ImGui::Checkbox("Auto Refresh", &auto_refresh_); ImGui::SetNextItemWidth(250.0f); ImGui::DragFloat("Interval (s)", &refresh_interval_, 0.05f, 0.1f, 60.0f, "%.2f"); if (refresh_interval_ < 0.1f) { refresh_interval_ = 0.1f; } ImGui::Separator(); ImGui::Text("Scenes found: %d", static_cast(scenes_.size())); if (scenes_.empty()) { ImGui::TextUnformatted("No scenes available. Click 'Refresh Scenes' to load."); } else { ImGui::TextUnformatted("Select Scene:"); std::vector items; items.reserve(scenes_.size()); for (const SceneEntry& scene : scenes_) { items.push_back(scene.name.c_str()); } if (ImGui::Combo("##SceneCombo", &selected_scene_, items.data(), static_cast(items.size()))) { RefreshObjectsForScene(); } } ImGui::Separator(); ImGui::TextUnformatted("GameObjectHierarchy"); if (ImGui::BeginChild("##GameObjectHierarchy", ImVec2(0.0f, 250.0f), true)) { int shown = 0; for (int i = 0; i < static_cast(objects_.size()); ++i) { const ObjectEntry& entry = objects_[i]; if (!ContainsNoCase(entry.display_name, search_filter_.data())) { continue; } ImGui::PushID(i); ImGui::TextUnformatted(entry.display_name.c_str()); ImGui::SameLine(); const std::string inspect_button_id = "Ins##" + std::to_string(i); if (ImGui::SmallButton(inspect_button_id.c_str())) { InspectObject(i); } ImGui::PopID(); shown++; } if (shown == 0) { ImGui::TextUnformatted("No GameObjects found in this scene."); } } ImGui::EndChild(); ImGui::Separator(); ImGui::TextUnformatted("Inspector"); if (!inspector_active_ || selected_object_ < 0 || selected_object_ >= static_cast(objects_.size())) { ImGui::TextUnformatted("No GameObject selected."); ImGui::TextUnformatted("Click 'Ins' button..."); ImGui::End(); return; } ImGui::Text("Selected: %s", inspected_obj_name_.c_str()); if (inspected_obj_ptr_) { auto get_active = GetIl2cppFn(IL2CPP::GameObject_get_activeInHierarchy); if (get_active) { inspector_.active = get_active(inspected_obj_ptr_); } } bool active = inspector_.active; if (ImGui::Checkbox("Active", &active)) { inspector_.active = active; auto set_active = GetIl2cppFn(IL2CPP::GameObject_SetActive); if (set_active && inspected_obj_ptr_) { set_active(inspected_obj_ptr_, active); } } ImGui::Separator(); ImGui::TextUnformatted("Transform"); if (inspected_obj_ptr_) { LoadTransformState(inspected_obj_ptr_); } if (!inspector_.has_transform || !inspector_.transform) { ImGui::TextUnformatted("No Transform component found."); } else { auto set_position = GetIl2cppFn(IL2CPP::Transform_set_position); auto set_local_position = GetIl2cppFn(IL2CPP::Transform_set_localPosition); auto set_local_scale = GetIl2cppFn(IL2CPP::Transform_set_localScale); auto set_rotation = GetIl2cppFn(IL2CPP::Transform_set_rotation); if (ImGui::InputFloat3("Position", &inspector_.position.x, "%.3f") && set_position) { set_position(inspector_.transform, &inspector_.position); } if (ImGui::InputFloat3("Local Position", &inspector_.local_position.x, "%.3f") && set_local_position) { set_local_position(inspector_.transform, &inspector_.local_position); } if (ImGui::InputFloat3("Rotation", &inspector_.rotation_xyz.x, "%.3f") && set_rotation) { Quat q{}; q.x = inspector_.rotation_xyz.x; q.y = inspector_.rotation_xyz.y; q.z = inspector_.rotation_xyz.z; q.w = inspector_.rotation_w; set_rotation(inspector_.transform, &q); } if (ImGui::InputFloat3("Scale", &inspector_.scale.x, "%.3f") && set_local_scale) { set_local_scale(inspector_.transform, &inspector_.scale); } } ImGui::Separator(); ImGui::TextUnformatted("Components"); if (inspected_obj_ptr_) { LoadComponentList(inspected_obj_ptr_); } if (!inspector_.class_name.empty()) { ImGui::Text("GameObject Class: %s.%s", inspector_.class_namespace.c_str(), inspector_.class_name.c_str()); } else { ImGui::TextUnformatted("Unable to get GameObject class information."); } ImGui::TextUnformatted("All Components:"); if (inspector_.components.empty()) { ImGui::TextUnformatted("No components found or failed to get components."); } else { ImGui::BeginChild("##AllComponents", ImVec2(0.0f, 180.0f), true); for (int i = 0; i < static_cast(inspector_.components.size()); ++i) { ImGui::PushID(i); ImGui::TextUnformatted(inspector_.components[i].type_name.c_str()); ImGui::PopID(); } ImGui::EndChild(); } ImGui::End(); } void UnityExplorerModule::Update() { if (!Enabled() || !auto_refresh_) { return; } const double now = static_cast(GetTickCount64()) / 1000.0; if ((now - last_refresh_time_) >= refresh_interval_) { last_refresh_time_ = now; RefreshScenes(); } } void UnityExplorerModule::InspectObject(int object_index) { if (object_index < 0 || object_index >= static_cast(objects_.size())) { return; } selected_object_ = object_index; inspected_obj_ptr_ = objects_[object_index].unity_object; inspected_obj_name_ = objects_[object_index].display_name; inspector_active_ = true; RefreshInspectorState(); } void UnityExplorerModule::RefreshInspectorState() { inspector_ = InspectorState{}; if (!inspector_active_) { return; } if (!inspected_obj_ptr_) { return; } std::string class_ns; std::string class_name; if (TryGetClassName(inspected_obj_ptr_, &class_ns, &class_name)) { inspector_.class_namespace = class_ns; inspector_.class_name = class_name; } auto get_active = GetIl2cppFn(IL2CPP::GameObject_get_activeInHierarchy); if (get_active) { inspector_.active = get_active(inspected_obj_ptr_); } LoadTransformState(inspected_obj_ptr_); LoadComponentList(inspected_obj_ptr_); } bool UnityExplorerModule::LoadTransformState(void* unity_object) { inspector_.has_transform = false; inspector_.transform = nullptr; if (!unity_object) { return false; } auto get_transform = GetIl2cppFn(IL2CPP::GameObject_get_transform); auto component_get_transform = GetIl2cppFn(IL2CPP::Component_GetComponent_Transform); void* transform = nullptr; if (get_transform) { transform = get_transform(unity_object); } if (!transform && component_get_transform) { transform = component_get_transform(unity_object); } if (!transform) { return false; } inspector_.transform = transform; inspector_.has_transform = true; auto get_position = GetIl2cppFn(IL2CPP::Transform_get_position); auto get_local_position = GetIl2cppFn(IL2CPP::Transform_get_localPosition); auto get_local_scale = GetIl2cppFn(IL2CPP::Transform_get_localScale); auto get_rotation = GetIl2cppFn(IL2CPP::Transform_get_rotation); if (get_position) { if (Vec3* v = get_position(transform)) { inspector_.position = *v; } } if (get_local_position) { if (Vec3* v = get_local_position(transform)) { inspector_.local_position = *v; } } if (get_local_scale) { if (Vec3* v = get_local_scale(transform)) { inspector_.scale = *v; } } if (get_rotation) { if (Quat* q = get_rotation(transform)) { inspector_.rotation_xyz.x = q->x; inspector_.rotation_xyz.y = q->y; inspector_.rotation_xyz.z = q->z; inspector_.rotation_w = q->w; } } return true; } void UnityExplorerModule::LoadComponentList(void* unity_object) { inspector_.components.clear(); if (!unity_object) { return; } auto get_component_by_index = GetIl2cppFn(IL2CPP::GameObject_GetComponentByIndex); auto object_get_name = GetIl2cppFn(IL2CPP::Object_get_name); if (!get_component_by_index) { return; } std::unordered_set seen_components; for (int i = 0; i < 128; ++i) { void* component = get_component_by_index(unity_object, i); if (!component) { break; } if (!seen_components.insert(component).second) { break; } std::string component_ns; std::string component_name; if (TryGetClassName(component, &component_ns, &component_name)) { inspector_.components.push_back( {FormatClassName(component_ns, component_name), component}); continue; } if (object_get_name) { const std::string name_from_object = Il2CppStringToUtf8(object_get_name(component)); if (!name_from_object.empty()) { inspector_.components.push_back({name_from_object, component}); continue; } } inspector_.components.push_back({"Unknown Component", component}); } }