www
www
This commit is contained in:
1 parent
3933cd1901
commit
0aa65c433d
98 files changed
+56670
No files matched your search
@@ -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});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user