From ad3e4b14fcba59417e3b9b67485d358e1f9fc21f Mon Sep 17 00:00:00 2001 From: William Miles Date: Sat, 10 Sep 2022 22:12:31 +1000 Subject: [PATCH] ESP Tree view. --- Navmesher/Navmesher.vcxproj | 10 +- Navmesher/main.cpp | 266 +++++++++++++++++++++++++----------- espReader/ESPReader.h | 7 +- espReader/Reader.c | 41 +++++- 4 files changed, 242 insertions(+), 82 deletions(-) diff --git a/Navmesher/Navmesher.vcxproj b/Navmesher/Navmesher.vcxproj index 3f97a8e..1cc51af 100644 --- a/Navmesher/Navmesher.vcxproj +++ b/Navmesher/Navmesher.vcxproj @@ -104,7 +104,7 @@ true _DEBUG;_WINDOWS;%(PreprocessorDefinitions) true - ..\imgui\backends;..\glfw\include;..\imgui;%(AdditionalIncludeDirectories) + ..\SizedBuffer;..\ESPReader;..\imgui\backends;..\glfw\include;..\imgui;%(AdditionalIncludeDirectories) Console @@ -159,6 +159,14 @@ + + + {c403cba2-86ae-4442-87cf-26b283a549c2} + + + {cb1c8f66-5b90-4de7-890b-f6430daaf25f} + + diff --git a/Navmesher/main.cpp b/Navmesher/main.cpp index c1d7834..5fc73a0 100644 --- a/Navmesher/main.cpp +++ b/Navmesher/main.cpp @@ -1,42 +1,152 @@ -// Dear ImGui: standalone example application for GLFW + OpenGL 3, using programmable pipeline -// (GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan/Metal graphics context creation, etc.) -// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. -// Read online: https://github.com/ocornut/imgui/tree/master/docs +// Based on Dear ImGui GLFW + OpenGL 3 standalone example +#include "ESPReader.h" #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include +#include +#undef NDEBUG +#include +#define NDEBUG #include // Will drag system OpenGL headers const float BASE_FONT_SIZE = 13; +const ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; + +MetaTree ReadESP(const char *filename); + +GLFWwindow *Setup(void); +void NewFrame(void); +void Fullscreen(void); +void Render(GLFWwindow *window, const ImVec4 clear_color); +void Cleanup(GLFWwindow *window); +void ChangeScale(bool *scale_update, const float scale); + +void ScalingWidget(bool *scale_update, float *scale); +void ESPTreeWalk(MetaNode *m); static void glfw_error_callback(int error, const char *description) { fprintf(stderr, "Glfw Error %d: %s\n", error, description); } -void ChangeScale(bool *scale_update, const float scale); -void ScalingWidget(bool *scale_update, float *scale); - int main(int, char **) { - // Setup window - glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) + GLFWwindow *window = Setup(); + if (!window) return 1; - // GL 3.0 + GLSL 130 + MetaTree m = ReadESP("Skyrim.esm"); + m.root->user = (void *)true; + + // Our state + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + bool scale_update = false; + float scale = 1.0f; + + // Main loop + while (!glfwWindowShouldClose(window)) + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + glfwPollEvents(); + + // Interface scaling + ChangeScale(&scale_update, scale); + + // Start the Dear ImGui frame + NewFrame(); + + // Setup for fullscreen + Fullscreen(); + + // Begin window + ImGui::Begin("ESP Browser", NULL, window_flags); + + ScalingWidget(&scale_update, &scale); + + ESPTreeWalk(m.root); + + // End window + ImGui::End(); + + Render(window, clear_color); + } + + free(m.esp.data); + free(m.tree.data); + + Cleanup(window); + + return 0; +} + +MetaTree ReadESP(const char *filename) +{ + errno_t ret = 0; + + SizedBuf esp_buf = { 0 }; + ESPStats stats = { 0 }; + SizedBuf tree_buf = { 0 }; + MetaTree tree = { 0 }; + + // Read in the esp file + { + FILE *fp; + ret = fopen_s(&fp, "Skyrim.esm", "rb"); + assert(fp && !ret); + + fseek(fp, 0L, SEEK_END); + esp_buf.size = ftell(fp); + rewind(fp); + + esp_buf.data = (char *)malloc(esp_buf.size); + assert(esp_buf.data); + + size_t read = fread(esp_buf.data, sizeof(char), esp_buf.size, fp); + fclose(fp); + assert(read == esp_buf.size); + } + + // Calculate esp stats + stats = espr_stats(esp_buf); + + // Construct the meta tree + { + tree_buf.size = espr_tree_size(stats); + tree_buf.data = (char *)malloc(tree_buf.size); + assert(tree_buf.data); + tree = espr_create_tree(esp_buf, tree_buf); + } + + // lazy file pointer cleanup + _fcloseall(); + + return tree; +} + +GLFWwindow *Setup(void) +{ + // Setup window + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) + return NULL; + + // GL 3.0 + GLSL 130 const char *glsl_version = "#version 130"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - // Create window with graphics context + // Create window with graphics context GLFWwindow *window = glfwCreateWindow(1920, 1080, "Dear ImGui GLFW+OpenGL3 example", NULL, NULL); if (window == NULL) - return 1; + return NULL; glfwMakeContextCurrent(window); glfwSwapInterval(1); // Enable vsync @@ -67,80 +177,60 @@ int main(int, char **) ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); - // Our state - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - bool scale_update = false; - float scale = 1.0f; + return window; +} - // Main loop - while (!glfwWindowShouldClose(window)) +void NewFrame(void) +{ + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + +} + +void Fullscreen(void) +{ + const ImGuiViewport *viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); +} + + +void Render(GLFWwindow * window, const ImVec4 clear_color) +{ + ImGuiIO &io = ImGui::GetIO(); + + ImGui::Render(); + + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + // Update and Render additional Platform Windows + // (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere. + // For this specific demo app we could also call glfwMakeContextCurrent(window) directly) + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { - // Poll and handle events (inputs, window resize, etc.) - // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. - // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. - // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. - // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. - glfwPollEvents(); - - static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; - - // Interface scaling - ChangeScale(&scale_update, scale); - - // Start the Dear ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - // setup for fullscreen - const ImGuiViewport *viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->WorkPos); - ImGui::SetNextWindowSize(viewport->WorkSize); - - ImGui::Begin("ESP Browser", NULL, flags); - - ScalingWidget(&scale_update, &scale); - - if (ImGui::TreeNode("Test")) - { - ImGui::Text("Text"); - ImGui::TreePop(); - } - - ImGui::End(); - - // Rendering - ImGui::Render(); - int display_w, display_h; - glfwGetFramebufferSize(window, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); - glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - // Update and Render additional Platform Windows - // (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere. - // For this specific demo app we could also call glfwMakeContextCurrent(window) directly) - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - GLFWwindow *backup_current_context = glfwGetCurrentContext(); - ImGui::UpdatePlatformWindows(); - ImGui::RenderPlatformWindowsDefault(); - glfwMakeContextCurrent(backup_current_context); - } - - glfwSwapBuffers(window); + GLFWwindow *backup_current_context = glfwGetCurrentContext(); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + glfwMakeContextCurrent(backup_current_context); } - // Cleanup + glfwSwapBuffers(window); +} + +void Cleanup(GLFWwindow *window) +{ ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); - - return 0; } void ChangeScale(bool *scale_update, const float scale) @@ -152,7 +242,8 @@ void ChangeScale(bool *scale_update, const float scale) if (*scale_update) { ImFontConfig font_config; - font_config.SizePixels = (BASE_FONT_SIZE * scale); + // Truncate to 2 decimal places + font_config.SizePixels = ((float)(int)(BASE_FONT_SIZE * scale * 100)) / 100; io.Fonts->Clear(); io.Fonts->AddFontDefault(&font_config); ImGui_ImplOpenGL3_UpdateFontsTexture(); @@ -179,3 +270,22 @@ void ScalingWidget(bool *scale_update, float *scale) ImGui::PopItemWidth(); } +void ESPTreeWalk(MetaNode *m) +{ + char buf[1024] = { 0 }; + SizedBuf sb = { buf, 1024 }; + espr_meta_string(m, sb); + if (m->n.type == NT_RECORD) { + ImGui::BulletText(buf); + } else { + if (ImGui::TreeNode(buf)) { + MetaNode *c = m->first_child; + while (c) { + ESPTreeWalk(c); + c = c->next; + } + ImGui::TreePop(); + } + } +} + diff --git a/espReader/ESPReader.h b/espReader/ESPReader.h index a6d166a..1880e9b 100644 --- a/espReader/ESPReader.h +++ b/espReader/ESPReader.h @@ -247,7 +247,7 @@ extern "C" { */ struct meta_node { Node n; - MetaNode *parent; + void *user; MetaNode *first_child; MetaNode *last_child; MetaNode *prev; @@ -255,6 +255,9 @@ extern "C" { }; struct meta_tree { + // hack, not usable when modifying tree + SizedBuf tree; + SizedBuf esp; MetaNode *root; size_t size; }; @@ -395,6 +398,8 @@ extern "C" { void espr_serialize(MetaTree tree, SizedBuf out); + void espr_meta_string(MetaNode *m, SizedBuf str_buf); + // End C++ guard #ifdef __cplusplus } diff --git a/espReader/Reader.c b/espReader/Reader.c index d401909..a40102f 100644 --- a/espReader/Reader.c +++ b/espReader/Reader.c @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -468,8 +469,10 @@ void litcopy(struct str_buf *sb, struct str_lit lit) void num_str(struct str_buf *sb, unsigned long num, int radix) { + _CrtSetDebugFillThreshold(0); errno_t ret = _ultoa_s(num, sb->buf, sb->size, radix); assert(ret == 0); + _CrtSetDebugFillThreshold(SIZE_MAX); int len = (int)strlen(sb->buf); sb->size -= len; sb->buf += len; @@ -629,6 +632,8 @@ Timestamp convert_ts(uint16_t ts) MetaTree espr_create_tree(SizedBuf in, SizedBuf tree) { + MetaTree mtree = { .esp = in, .tree = tree }; + // create root node MetaNode *root = NULL; { @@ -641,7 +646,10 @@ MetaTree espr_create_tree(SizedBuf in, SizedBuf tree) struct walker_callbacks cb = { .pre = create_tree_cb, .data = &tree }; espr_walk(in, cb, root); - return (MetaTree) { .root = root, .size = in.size }; + mtree.root = root; + mtree.size = in.size; + + return mtree; } void create_tree_cb( @@ -667,7 +675,6 @@ void create_tree_cb( // construct new node *m = (MetaNode){ 0 }; // zero/null unused m->n = n; - m->parent = p; m->prev = p->last_child; // the linked list of children may not already exist if (p->last_child) @@ -730,3 +737,33 @@ void serialize_cb(MetaNode *m, void *data) { } } +void espr_meta_string(MetaNode *m, SizedBuf str_buf) +{ + // root node + if (!m->n.data) { + snprintf(str_buf.data, str_buf.size, "Root"); + return; + } + + char buf[1024] = { 0 }; + struct str_buf sb = { .buf = buf, .size = 1024 }; + + switch (m->n.type) { + case NT_GROUP: + { + group_label_str(&sb, m->n.header.group); + const char *lbl = group_type_strings[m->n.header.group->type].lit; + snprintf(str_buf.data, str_buf.size, "GRUP; %s; %s", lbl, buf); + break; + } + case NT_RECORD: + { + type_str(&sb, m->n.header.record->type); + snprintf(str_buf.data, str_buf.size, "%s; FormID[%08x]", buf, m->n.header.record->formid); + break; + } + default: + assert(false); + } +} +