www
This commit is contained in:
HelenWalker committed 2026-03-08 16:19:24 -06:00
1 parent 3933cd1901
commit 0aa65c433d
98 files changed
+56670

No files matched your search

+514
View File
@@ -0,0 +1,514 @@
#include "UnityExplorer.h"
#include "helpers.h"
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <string>
#include <unordered_set>
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<char>(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<const Il2CppString*>(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<FnIl2CppObjectGetClass>(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<const Il2CppClassLite*>(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<double>(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<int>(scenes_.size())) {
selected_scene_ = static_cast<int>(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<int>(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<FnCameraMain>(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<double>(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<int>(scenes_.size()));
if (scenes_.empty()) {
ImGui::TextUnformatted("No scenes available. Click 'Refresh Scenes' to load.");
} else {
ImGui::TextUnformatted("Select Scene:");
std::vector<const char*> 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<int>(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<int>(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<int>(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<FnGameObjectGetActiveInHierarchy>(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<FnGameObjectSetActive>(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<FnSetVec3>(IL2CPP::Transform_set_position);
auto set_local_position = GetIl2cppFn<FnSetVec3>(IL2CPP::Transform_set_localPosition);
auto set_local_scale = GetIl2cppFn<FnSetVec3>(IL2CPP::Transform_set_localScale);
auto set_rotation = GetIl2cppFn<FnSetQuat>(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<int>(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<double>(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<int>(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<FnGameObjectGetActiveInHierarchy>(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<FnGameObjectGetTransform>(IL2CPP::GameObject_get_transform);
auto component_get_transform = GetIl2cppFn<FnComponentGetTransform>(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<FnGetVec3>(IL2CPP::Transform_get_position);
auto get_local_position = GetIl2cppFn<FnGetVec3>(IL2CPP::Transform_get_localPosition);
auto get_local_scale = GetIl2cppFn<FnGetVec3>(IL2CPP::Transform_get_localScale);
auto get_rotation = GetIl2cppFn<FnGetQuat>(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<FnGameObjectGetComponentByIndex>(IL2CPP::GameObject_GetComponentByIndex);
auto object_get_name = GetIl2cppFn<FnObjectGetName>(IL2CPP::Object_get_name);
if (!get_component_by_index) {
return;
}
std::unordered_set<void*> 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});
}
}