[
  {
    "path": ".gitignore",
    "content": "# CMake\nCMakeLists.txt.user\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nTesting\nMakefile\ncmake_install.cmake\ninstall_manifest.txt\ncompile_commands.json\nCTestTestfile.cmake\n_deps\n\n# C++ \n## Prerequisites\n*.d\n\n## Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n## Precompiled Headers\n*.gch\n*.pch\n\n## Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n## Fortran module files\n*.mod\n*.smod\n\n## Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n## Executables\n*.exe\n*.out\n*.app\n\n# Xcode\n## App packaging\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n# macOS\n.DS_Store\n\n# build\nbuild\nbin\n.dmg\n.app\n\n# vim\ntags\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"external/imgui\"]\n\tpath = external/imgui\n\turl = git@github.com:ocornut/imgui.git\n[submodule \"external/glfw\"]\n\tpath = external/glfw\n\turl = git@github.com:glfw/glfw.git\n[submodule \"external/llvm-project\"]\n\tpath = external/llvm-project\n\turl = git@github.com:llvm/llvm-project.git\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.18)\n\nset(CMAKE_OSX_ARCHITECTURES \"x86_64;arm64\" CACHE STRING \"\" FORCE)\n\nproject(Hook\n    VERSION 0.1.1\n    LANGUAGES CXX OBJCXX\n)\n\nset(CMAKE_CXX_STANDARD 20)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_BUILD_TYPE RelWithDebInfo)\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wall -Wformat\")\n\nif(APPLE)\n    set(CMAKE_INSTALL_PREFIX \"/Applications\")\nendif()\n\nfind_package(Git REQUIRED)\nif(GIT_FOUND AND EXISTS \"${PROJECT_SOURCE_DIR}/.git\")\n   option(GIT_SUBMODULE \"Check submodules during build\" ON)\n   if(GIT_SUBMODULE)\n       message(STATUS \"Submodule update\")\n       execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive\n                       WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n                       RESULT_VARIABLE GIT_SUBMOD_RESULT)\n       if(NOT GIT_SUBMOD_RESULT EQUAL \"0\")\n           message(FATAL_ERROR \"git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules\")\n       endif()\n   endif()\nendif()\n\nexecute_process(COMMAND ${PROJECT_SOURCE_DIR}/external/llvm-project/lldb/scripts/macos-setup-codesign.sh)\n\ninclude(ExternalProject)\n\nExternalProject_Add(${PROJECT_NAME}-llvm\n    SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/llvm-project/llvm\n    BINARY_DIR ${PROJECT_BINARY_DIR}/lldb-build\n    CONFIGURE_COMMAND ${CMAKE_COMMAND}\n        -G Ninja\n        -DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.app/Contents/Frameworks\n        -C ${CMAKE_CURRENT_SOURCE_DIR}/cmake/standalone.cmake\n        ${CMAKE_CURRENT_SOURCE_DIR}/external/llvm-project/llvm\n    BUILD_COMMAND ninja\n        -C ${PROJECT_BINARY_DIR}/lldb-build\n        lldb\n        debugserver\n        #        darwin-debug\n    INSTALL_COMMAND ninja\n        -C ${PROJECT_BINARY_DIR}/lldb-build\n        #install-lldb\n        install-debugserver\n        install-liblldb\n        #install-darwin-debug\n    TEST_COMMAND \"\"\n)\n\nadd_subdirectory(external/glfw)\n\nset(CPP_SOURCES\n    src/main.cpp\n    external/imgui/imgui.cpp\n    external/imgui/misc/cpp/imgui_stdlib.cpp\n    external/imgui/imgui_draw.cpp\n    external/imgui/imgui_tables.cpp\n    external/imgui/imgui_widgets.cpp\n    external/imgui/backends/imgui_impl_glfw.cpp\n)\n\nset(OBJC_SOURCES\n    external/imgui/backends/imgui_impl_metal.mm\n    src/backend.cpp\n)\n\nset(APP_ICON_MACOSX resources/icons/hook.icns)\n\nset_source_files_properties(${APP_ICON_MACOSX} PROPERTIES\n    MACOSX_PACKAGE_LOCATION \"Resources\"\n)\n\nadd_executable(${PROJECT_NAME} MACOSX_BUNDLE\n    ${CPP_SOURCES}\n    ${OBJC_SOURCES}\n    ${APP_ICON_MACOSX}\n)\n\nset_target_properties(${PROJECT_NAME} PROPERTIES\n    MACOSX_BUNDLE_ICON_FILE hook.icns\n)\n\nconfigure_file(src/config.h.in config.h)\n\ntarget_include_directories(${PROJECT_NAME}\n    PUBLIC ${PROJECT_BINARY_DIR}\n    PUBLIC external/glfw/include\n    PUBLIC external/imgui\n    PUBLIC external/imgui/backends\n    PUBLIC external/imgui/misc/cpp\n    PUBLIC external/llvm-project/lldb/include\n    PUBLIC src\n)\n\ntarget_link_directories(${PROJECT_NAME}\n    PUBLIC ${PROJECT_BINARY_DIR}/lldb-build/lib\n)\n\ntarget_link_libraries(${PROJECT_NAME}\n    \"lldb\"\n    \"glfw\"\n    \"-framework Metal\"\n    \"-framework MetalKit\"\n    \"-framework Cocoa\"\n    \"-framework IOKit\"\n    \"-framework CoreVideo\"\n    \"-framework QuartzCore\"\n    \"-framework Security\"\n)\n\nset_source_files_properties(${OBJC_SOURCES} PROPERTIES\n    LANGUAGE OBJCXX\n    COMPILE_FLAGS \"-x objective-c++\"\n)\n\nadd_custom_command(TARGET ${PROJECT_NAME} POST_BUILD\n    COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change @rpath/liblldb.17.0.4.dylib @executable_path/../Frameworks/lib/liblldb.dylib $<TARGET_FILE:${PROJECT_NAME}>\n)\n\nset(GLFW_INSTALL OFF CACHE BOOL \"\" FORCE)\ninstall(TARGETS ${PROJECT_NAME}\n    BUNDLE DESTINATION .\n)\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img width=\"256\" height=\"256\" src=\"./resources/icons/hook.svg\"/>\n</p>\n\n# Hook\nA **graphical C/C++ runtime editor** for rapid experimentation. It attaches to your running program and allows you to easily change variables live, breaking the time-consuming edit-compile-run-edit cycle.\n\n<img width=\"1141\" alt=\"hook_in_action\" src=\"https://github.com/abolinsky/hook/assets/5623716/0f699866-4934-4e79-991b-07e6579bed36\">\n\n# build and install\n> Warning: This will take a long time. (~20 minutes on an M2 MacBook air)\n```\n./scripts/build.sh\n```\n\n# run\n```\nopen -a Hook\n```\n"
  },
  {
    "path": "cmake/standalone.cmake",
    "content": "set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING \"\")\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL \"\")\n\nset(LLVM_TARGETS_TO_BUILD AArch64;X86 CACHE STRING \"\")\nset(LLVM_ENABLE_PROJECTS clang;lldb CACHE STRING \"\")\nset(LLDB_INCLUDE_TESTS OFF CACHE BOOL \"\")\nset(LLDB_SKIP_STRIP ON CACHE BOOL \"\")\n\nset(LLDB_NO_INSTALL_DEFAULT_RPATH OFF CACHE BOOL \"\")\nset(CMAKE_OSX_DEPLOYMENT_TARGET 13.0 CACHE STRING \"\")\n\nset(LLDB_BUILD_FRAMEWORK OFF CACHE BOOL \"\")\n\nset(LLVM_ENABLE_ZSTD OFF CACHE BOOL \"\")\nset(LLVM_ENABLE_DOXYGEN OFF CACHE BOOL \"\")\nset(LLDB_ENABLE_SWIG OFF CACHE BOOL \"\")\nset(LLDB_ENABLE_PYTHON OFF CACHE BOOL \"\")\nset(LLDB_ENABLE_CURSES OFF CACHE BOOL \"\")\nset(LLDB_ENABLE_TERMIOS OFF CACHE BOOL \"\")\nset(LLDB_ENABLE_LIBEDIT OFF CACHE BOOL \"\")\nset(LLDB_ENABLE_LIBXML2 OFF CACHE BOOL \"\")\nset(LLDB_ENABLE_LZMA OFF CACHE BOOL \"\")\nset(LLDB_ENABLE_LUA OFF CACHE BOOL \"\")\n\nset(LLDB_USE_OS_LOG ON CACHE BOOL \"\")\n\nset(LLVM_DISTRIBUTION_COMPONENTS\n  #lldb\n  liblldb\n  #lldb-argdumper\n  #darwin-debug\n  debugserver\n  CACHE STRING \"\")\n"
  },
  {
    "path": "resources/icons/svg2icns.sh",
    "content": "set -e\n\nSIZES=\"\n16,16x16\n32,16x16@2x\n32,32x32\n64,32x32@2x\n128,128x128\n256,128x128@2x\n256,256x256\n512,256x256@2x\n512,512x512\n1024,512x512@2x\n\"\n\nfor SVG in \"$@\"; do\n    BASE=$(basename \"$SVG\" | sed 's/\\.[^\\.]*$//')\n    echo $BASE\n    ICONSET=\"$BASE.iconset\"\n    mkdir -p \"$ICONSET\"\n    for PARAMS in $SIZES; do\n        SIZE=$(echo $PARAMS | cut -d, -f1)\n        LABEL=$(echo $PARAMS | cut -d, -f2)\n        svg2png -w $SIZE -h $SIZE \"$SVG\" \"$ICONSET\"/icon_$LABEL.png\n    done\n\n    iconutil -c icns \"$ICONSET\"\n    rm -rf \"$ICONSET\"\ndone\n"
  },
  {
    "path": "scripts/build.sh",
    "content": "rm -rf build\ncmake -B build -S .\ncmake --build build\ncmake --install build\n"
  },
  {
    "path": "scripts/create_dmg.sh",
    "content": "create-dmg \\\n  --volname \"Hook Installer\" \\\n  --window-pos 200 120 \\\n  --window-size 800 400 \\\n  --icon-size 100 \\\n  --icon \"hook.app\" 200 190 \\\n  --hide-extension \"Hook.app\" \\\n  --app-drop-link 600 185 \\\n  \"build/Hook.dmg\" \\\n  \"build/Hook.app\"\n"
  },
  {
    "path": "src/backend.cpp",
    "content": "#include \"backend.h\"\n#include \"imgui_impl_glfw.h\"\n#include \"imgui_impl_metal.h\"\n\n#include \"config.h\"\n\n#define GLFW_INCLUDE_NONE\n#define GLFW_EXPOSE_NATIVE_COCOA\n#include <GLFW/glfw3.h>\n#include <GLFW/glfw3native.h>\n\n#import <Metal/Metal.h>\n#import <QuartzCore/QuartzCore.h>\n\n#include <functional>\n#include <iostream>\n\nvoid glfw_error_callback(int error, const char* description) {\n    throw std::runtime_error(\"Glfw Error \" + std::to_string(error) + \": \" + description);\n}\n\nvoid main_loop(void (*user_function)()) {\n    glfwSetErrorCallback(glfw_error_callback);\n    if (!glfwInit())\n        return;\n\n    // Create window with graphics context\n    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);\n    std::string title = project::name + \" \" + project::version.to_string();\n    GLFWwindow* window = glfwCreateWindow(440, 720, title.c_str(), nullptr, nullptr);\n    if (window == nullptr)\n        return;\n\n    id <MTLDevice> device = MTLCreateSystemDefaultDevice();\n    id <MTLCommandQueue> commandQueue = [device newCommandQueue];\n\n    // Setup Platform/Renderer backends\n    ImGui_ImplGlfw_InitForOpenGL(window, true);\n    ImGui_ImplMetal_Init(device);\n\n    NSWindow *nswin = glfwGetCocoaWindow(window);\n    CAMetalLayer *layer = [CAMetalLayer layer];\n    layer.device = device;\n    layer.pixelFormat = MTLPixelFormatBGRA8Unorm;\n    nswin.contentView.layer = layer;\n    nswin.contentView.wantsLayer = YES;\n\n    MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor new];\n\n    float clear_color[4] = {0.45f, 0.55f, 0.60f, 1.00f};\n\n    while (!glfwWindowShouldClose(window))\n    {\n        @autoreleasepool\n        {\n            glfwPollEvents();\n\n            int width, height;\n            glfwGetFramebufferSize(window, &width, &height);\n            layer.drawableSize = CGSizeMake(width, height);\n            id<CAMetalDrawable> drawable = [layer nextDrawable];\n\n            id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];\n            renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]);\n            renderPassDescriptor.colorAttachments[0].texture = drawable.texture;\n            renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;\n            renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;\n            id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];\n\n            // Start the Dear ImGui frame\n            ImGui_ImplMetal_NewFrame(renderPassDescriptor);\n            ImGui_ImplGlfw_NewFrame();\n            \n            user_function();\n\n            ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), commandBuffer, renderEncoder);\n\n            [renderEncoder endEncoding];\n\n            [commandBuffer presentDrawable:drawable];\n            [commandBuffer commit];\n        }\n    }\n\n    // Cleanup\n    ImGui_ImplMetal_Shutdown();\n    ImGui_ImplGlfw_Shutdown();\n    ImGui::DestroyContext();\n\n    glfwDestroyWindow(window);\n    glfwTerminate();\n}"
  },
  {
    "path": "src/backend.h",
    "content": "#pragma once\n\nvoid glfw_error_callback(int error, const char* description);\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid main_loop(void (*user_function)());\n\n#ifdef __cplusplus\n}\n#endif"
  },
  {
    "path": "src/config.h.in",
    "content": "#pragma once\n\n#include <string>\n\nnamespace project {\n\nstruct Version {\n    int major = @PROJECT_VERSION_MAJOR@;\n    int minor = @PROJECT_VERSION_MINOR@;\n    int patch = @PROJECT_VERSION_PATCH@;\n\n    std::string to_string() const {\n        return \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\";\n    }\n} version;\n\nstd::string name { \"@PROJECT_NAME@\" };\n\n}"
  },
  {
    "path": "src/main.cpp",
    "content": "#include \"backend.h\"\n\n#include <mach-o/dyld.h>\n\n#include <imgui.h>\n#include <imgui_stdlib.h>\n\n#include <lldb/API/LLDB.h>\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <sstream>\n#include <limits>\n\n\nnamespace Hook {\n\nstd::string GetExecutablePath() {\n    char path[PATH_MAX];\n    uint32_t size = sizeof(path);\n    if (_NSGetExecutablePath(path, &size) != 0) {\n        throw std::runtime_error(\"Could not get executable path\");\n    }\n\n    return std::string(path);\n}\n\nstd::string GetDebugServerPath() {\n    std::string executablePath = GetExecutablePath();\n    size_t appDirPos = executablePath.find(\".app\");\n    if (appDirPos == std::string::npos) {\n        throw std::runtime_error(\"Could not get debugserver path\");\n    }\n\n    std::string debugServerPath = executablePath.substr(0, appDirPos) + \".app/Contents/Frameworks/bin/debugserver\";\n    return debugServerPath;\n}\n\nstruct VariableInfo {\n    VariableInfo(lldb::SBValue& value) {\n        this->name = value.GetName();\n        this->function_name = (name.substr(0,2) == \"::\") ? \"\" : value.GetFrame().GetFunctionName();\n        this->id = value.GetID();\n\n        this->type = value.GetType().GetCanonicalType();\n        while (type.GetTypeClass() == lldb::eTypeClassTypedef) {\n            this->type.GetTypedefedType();\n        }\n        this->type_name = this->type.GetName();\n        this->type_class = this->type.GetTypeClass();\n        this->basic_type = this->type.GetBasicType();\n        if (this->basic_type == lldb::eBasicTypeUnsignedChar) {\n            value.SetFormat(lldb::Format::eFormatDecimal);\n        }\n\n        this->is_nested = this->type.IsAggregateType();\n        if (!this->is_nested) {\n            this->value = value.GetValue();\n        }\n    }\n\n    VariableInfo() = default;\n\n    bool IsAggregateType() const {\n        return is_nested;\n    }\n\n    std::string GetFullyQualifiedValue() const {\n        if (type_class == lldb::eTypeClassEnumeration) {\n            return type_name + \"::\" + value;\n        } else {\n            return value;\n        }\n    }\n\n    const VariableInfo& GetRoot() const {\n        const VariableInfo* root = this;\n        while (root->parent) {\n            root = root->parent;\n        }\n        return *root;\n    }\n\n    bool IsRoot() const {\n        return !parent;\n    }\n\n    bool ParentIsContainer() const {\n        return name.back() == ']';\n    }\n\n    std::string GetFullyQualifiedName() const {\n        std::string full_name;\n        const VariableInfo* current = this;\n        while (current != nullptr) {\n            full_name = current->name + full_name;\n            if (!current->IsRoot() && !current->ParentIsContainer()) {\n                full_name = \".\" + full_name;\n            }\n            current = current->parent;\n        }\n        return full_name;\n    }\n\n    std::string name;\n    std::string function_name;\n    std::string value;\n    lldb::SBType type;\n    std::string type_name;\n    lldb::TypeClass type_class;\n    lldb::BasicType basic_type;\n    bool is_nested = false;\n    uint64_t id = std::numeric_limits<uint64_t>::max();\n    std::vector<VariableInfo*> children;\n    VariableInfo* parent = nullptr;\n};\n\nstd::vector<VariableInfo> variables;\nconst VariableInfo* current_var_info;\nbool published_changes = true;\nbool open_pid_popup = true;\n\nlldb::SBDebugger debugger;\nlldb::SBTarget target;\nlldb::SBListener listener;\nlldb::SBError error;\nlldb::SBProcess process;\n\nlldb::pid_t pid = 0;\n\nvoid HelpMarker(const char* desc) {\n    ImGui::TextDisabled(\"(?)\");\n    if (ImGui::BeginItemTooltip()) {\n        ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);\n        ImGui::TextUnformatted(desc);\n        ImGui::PopTextWrapPos();\n        ImGui::EndTooltip();\n    }\n}\n\nvoid PublishChange(const VariableInfo& varInfo) {\n    current_var_info = &varInfo;\n    published_changes = false;\n    process.Stop();\n}\n\nlldb::SBValue FindVariableById(lldb::SBFrame& frame, uint64_t id) {\n    lldb::SBValueList vars = frame.GetVariables(true, true, true, true);\n    for (int i = 0; i < vars.GetSize(); ++i) {\n        lldb::SBValue v = vars.GetValueAtIndex(i);\n        if (v.GetID() == id) {\n            return v;\n        }\n    }\n    return lldb::SBValue();\n}\n\nstd::vector<lldb::SBValue> GetVariablesFromFrame(lldb::SBFrame& frame) {\n    std::vector<lldb::SBValue> variables;\n\n    lldb::SBValueList frameVariables = frame.GetVariables(true, true, true, true); // arguments, locals, statics, in_scope_only\n    for (int i = 0; i < frameVariables.GetSize(); i++) {\n        lldb::SBValue var = frameVariables.GetValueAtIndex(i);\n        if (var) {\n            variables.push_back(var);\n        }\n    }\n    return variables;\n}\n\nlldb::SBThread GetThread(lldb::SBProcess& process) {\n    lldb::SBThread thread = process.GetSelectedThread();\n    if (!thread) {\n        std::cerr << \"Failed to get thread\" << std::endl;\n    }\n    return thread;\n}\n\nstd::vector<lldb::SBFrame> GetFrames(lldb::SBThread& thread) {\n    std::vector<lldb::SBFrame> frames;\n    const auto num_frames = thread.GetNumFrames();\n    frames.reserve(num_frames);\n\n    for (auto i = 0; i < num_frames; ++i) {\n        lldb::SBFrame frame = thread.GetFrameAtIndex(i);\n        if (frame) {\n            frames.push_back(frame);\n        }\n    }\n    return frames;\n}\n\nvoid DisplayVariable(VariableInfo& varInfo) {\n    std::string prefix = !varInfo.IsRoot() ? \"\" : varInfo.function_name.empty() ? \"\" : \"(\" + varInfo.function_name + \") \";\n    ImGui::Text(\"%s%s =\", prefix.c_str(), varInfo.name.c_str());\n    ImGui::SameLine();\n\n    if (varInfo.IsAggregateType()) {\n        std::string treeNodeLabel = \"##varname\" + std::to_string(varInfo.id);\n        if (ImGui::TreeNode(treeNodeLabel.c_str())) {\n            for (auto childVar : varInfo.children) {\n                DisplayVariable(*childVar);\n            }\n            ImGui::TreePop();\n        }\n    } else {\n        std::string inputTextLabel = \"##varname\" + varInfo.function_name + varInfo.name;\n        if (varInfo.type_class == lldb::eTypeClassEnumeration) {\n            auto members = varInfo.type.GetEnumMembers();\n            auto num_members = members.GetSize();\n            std::vector<std::string> member_names;\n            for (unsigned i = 0; i < num_members; ++i) {\n                auto current_enum_member = members.GetTypeEnumMemberAtIndex(i);\n                if (current_enum_member.IsValid() && std::string(current_enum_member.GetName()) != \"\") {\n                    member_names.push_back(current_enum_member.GetName());\n                }\n            }\n            int index = 0;\n            for (auto& n : member_names) {\n                if (varInfo.value == n) {\n                    std::string inputTextLabel = \"##sliderEnum\" + varInfo.name;\n                    ImGui::SliderInt(inputTextLabel.c_str(), &index, 0, member_names.size() - 1, member_names[index].c_str());\n                    varInfo.value = member_names[index];\n                    break;\n                }\n                ++index;\n            }\n            if (ImGui::IsItemDeactivatedAfterEdit()) {\n                PublishChange(varInfo);\n            }\n        } else if (varInfo.basic_type == lldb::eBasicTypeBool) {\n            bool value = varInfo.value == \"true\";\n            ImGui::Checkbox(inputTextLabel.c_str(), &value);\n            varInfo.value = value ? \"true\" : \"false\";\n            if (ImGui::IsItemDeactivatedAfterEdit()) {\n                PublishChange(varInfo);\n            }\n        } else if (varInfo.basic_type == lldb::eBasicTypeUnsignedChar) {\n            uint8_t value = std::stoul(varInfo.value);\n            static auto min = std::numeric_limits<uint8_t>::min();\n            static auto max = std::numeric_limits<uint8_t>::max();\n            ImGui::SliderScalar(inputTextLabel.c_str(), ImGuiDataType_U8, &value, &min , &max);\n            if (ImGui::IsItemDeactivatedAfterEdit()) {\n                PublishChange(varInfo);\n            }\n            ImGui::SameLine(); HelpMarker(\"CTRL+click to input value\");\n            varInfo.value = std::to_string(value);\n        } else if (varInfo.basic_type == lldb::eBasicTypeInt) {\n            int value = std::stoi(varInfo.value);\n            static auto min = std::numeric_limits<int>::min() / 2;\n            static auto max = std::numeric_limits<int>::max() / 2;\n            ImGui::SliderScalar(inputTextLabel.c_str(), ImGuiDataType_S32, &value, &min , &max);\n            if (ImGui::IsItemDeactivatedAfterEdit()) {\n                PublishChange(varInfo);\n            }\n            ImGui::SameLine(); HelpMarker(\"CTRL+click to input value\");\n            varInfo.value = std::to_string(value);\n        } else {\n            ImGui::InputText(inputTextLabel.c_str(), &varInfo.value, ImGuiInputTextFlags_CharsDecimal);\n            if (ImGui::IsItemDeactivatedAfterEdit()) {\n                PublishChange(varInfo);\n            }\n        }\n    }\n}\n\nvoid FetchNestedMembers(lldb::SBValue& aggregateValue, VariableInfo& parent) {\n    for (int i = 0; i < aggregateValue.GetNumChildren(); ++i) {\n        lldb::SBValue childValue = aggregateValue.GetChildAtIndex(i);\n        if (!childValue.IsValid()) continue;\n\n        variables.emplace_back(childValue);\n        variables.back().parent = &parent;\n        parent.children.push_back(&variables.back());\n\n        if (parent.children.back()->IsAggregateType()) {\n            FetchNestedMembers(childValue, *parent.children.back());\n        }\n    }\n}\n\nstd::vector<lldb::SBValue> GetVariablesFromThread(lldb::SBThread& thread) {\n    std::vector<lldb::SBValue> variables;\n    for (auto& frame : GetFrames(thread)) {\n        auto frame_vars = GetVariablesFromFrame(frame);\n        variables.insert(variables.end(), frame_vars.begin(), frame_vars.end());\n    }\n    return variables;\n}\n\nvoid FetchAllVariables() {\n    auto thread = GetThread(process);\n    if (!thread) return;\n\n    auto thread_variables = GetVariablesFromThread(thread);\n    \n    variables.clear();\n    variables.reserve(thread_variables.size() * 100);\n\n    for (auto& var : thread_variables) {\n        const VariableInfo varInfo(var);\n        if (std::none_of(variables.begin(), variables.end(), [&varInfo](const VariableInfo& v) {\n            return v.function_name == varInfo.function_name && v.name == varInfo.name;\n        })) {\n            variables.push_back(varInfo);\n            if (varInfo.IsAggregateType()) {\n                FetchNestedMembers(var, variables.back());\n            }\n        }\n    }\n}\n\nvoid AttachToProcess(lldb::SBAttachInfo& attachInfo) {\n    target = debugger.CreateTarget(\"\");\n    process = target.Attach(attachInfo, error);\n    if (!process.IsValid() || error.Fail()) {\n        throw std::runtime_error(std::string(\"Failed to attach to process: \") + error.GetCString());\n    }\n}\n\nvoid AttachToProcessWithID(lldb::pid_t pid) {\n    lldb::SBAttachInfo attachInfo;\n    attachInfo.SetProcessID(pid);\n    AttachToProcess(attachInfo);\n}\n\nvoid SetupEventListener() {\n    listener = debugger.GetListener();\n    auto event_mask = lldb::SBProcess::eBroadcastBitStateChanged;\n    auto event_bits = process.GetBroadcaster().AddListener(listener, event_mask);\n    if (event_bits != event_mask) {\n        throw std::runtime_error(\"Could not set up event listener\");\n    }\n}\n\nvoid HandleAttachProcess() {\n    AttachToProcessWithID(pid);\n    SetupEventListener();\n    FetchAllVariables();\n    process.Continue();\n}\n\nvoid StyleColorsFunky() {\n    auto yellow = ImVec4{0.996, 0.780, 0.008, 1.0};\n    auto blue = ImVec4{0.090, 0.729, 0.808, 1.0};\n    auto green = ImVec4{0.149, 0.918, 0.694, 1.0};\n    auto white = ImVec4{0.996, 0.996, 0.996, 1.0};\n    auto middle_gray = ImVec4{0.5f, 0.5f, 0.5f, 1.0};\n    auto light_gray = ImVec4{0.85f, 0.85f, 0.85f, 1.0};\n    auto off_white = ImVec4{0.96f, 0.96f, 0.96f, 1.0};\n    auto black = ImVec4{0.0, 0.0, 0.0, 1.0};\n\n    auto &colors = ImGui::GetStyle().Colors;\n    colors[ImGuiCol_WindowBg] = green;\n    colors[ImGuiCol_MenuBarBg] = blue;\n\n    // Border\n    colors[ImGuiCol_Border] = white;\n    colors[ImGuiCol_BorderShadow] = ImVec4{0.0f, 0.0f, 0.0f, 0.24f};\n\n    // Text\n    colors[ImGuiCol_Text] = black;\n    colors[ImGuiCol_TextDisabled] = middle_gray;\n\n    // Headers\n    colors[ImGuiCol_Header] = yellow;\n    colors[ImGuiCol_HeaderHovered] = light_gray;\n    colors[ImGuiCol_HeaderActive] = white;\n\n    // Buttons\n    colors[ImGuiCol_Button] = blue;\n    colors[ImGuiCol_ButtonHovered] = white;\n    colors[ImGuiCol_ButtonActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_CheckMark] = blue;\n\n    // Popups\n    colors[ImGuiCol_PopupBg] = white;\n\n    // Slider\n    colors[ImGuiCol_SliderGrab] = yellow;\n    colors[ImGuiCol_SliderGrabActive] = white;\n\n    // Frame BG\n    colors[ImGuiCol_FrameBg] = off_white;\n    colors[ImGuiCol_FrameBgHovered] = light_gray;\n    colors[ImGuiCol_FrameBgActive] = yellow;\n\n    // Tabs\n    colors[ImGuiCol_Tab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_TabHovered] = ImVec4{0.24, 0.24f, 0.32f, 1.0f};\n    colors[ImGuiCol_TabActive] = ImVec4{0.2f, 0.22f, 0.27f, 1.0f};\n    colors[ImGuiCol_TabUnfocused] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_TabUnfocusedActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n\n    // Title\n    colors[ImGuiCol_TitleBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_TitleBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_TitleBgCollapsed] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n\n    // Scrollbar\n    colors[ImGuiCol_ScrollbarBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0f};\n    colors[ImGuiCol_ScrollbarGrab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};\n    colors[ImGuiCol_ScrollbarGrabActive] = ImVec4{0.24f, 0.24f, 0.32f, 1.0f};\n\n    // Seperator\n    colors[ImGuiCol_Separator] = ImVec4{0.44f, 0.37f, 0.61f, 1.0f};\n    colors[ImGuiCol_SeparatorHovered] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f};\n    colors[ImGuiCol_SeparatorActive] = ImVec4{0.84f, 0.58f, 1.0f, 1.0f};\n\n    // Resize Grip\n    colors[ImGuiCol_ResizeGrip] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f};\n    colors[ImGuiCol_ResizeGripHovered] = ImVec4{0.74f, 0.58f, 0.98f, 0.29f};\n    colors[ImGuiCol_ResizeGripActive] = ImVec4{0.84f, 0.58f, 1.0f, 0.29f};\n\n    auto &style = ImGui::GetStyle();\n    style.TabRounding = 4;\n    style.ScrollbarRounding = 9;\n    style.WindowBorderSize = 0;\n    style.GrabRounding = 3;\n    style.FrameRounding = 3;\n    style.PopupRounding = 4;\n    style.ChildRounding = 4;\n}\n\nvoid StyleColorsBlack() {\n    auto &colors = ImGui::GetStyle().Colors;\n    colors[ImGuiCol_WindowBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0};\n    colors[ImGuiCol_MenuBarBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n\n    // Border\n    colors[ImGuiCol_Border] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f};\n    colors[ImGuiCol_BorderShadow] = ImVec4{0.0f, 0.0f, 0.0f, 0.24f};\n\n    // Text\n    colors[ImGuiCol_Text] = ImVec4{1.0f, 1.0f, 1.0f, 1.0f};\n    colors[ImGuiCol_TextDisabled] = ImVec4{0.5f, 0.5f, 0.5f, 1.0f};\n\n    // Headers\n    colors[ImGuiCol_Header] = ImVec4{0.13f, 0.13f, 0.17, 1.0f};\n    colors[ImGuiCol_HeaderHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};\n    colors[ImGuiCol_HeaderActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n\n    // Buttons\n    colors[ImGuiCol_Button] = ImVec4{0.13f, 0.13f, 0.17, 1.0f};\n    colors[ImGuiCol_ButtonHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};\n    colors[ImGuiCol_ButtonActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_CheckMark] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f};\n\n    // Popups\n    colors[ImGuiCol_PopupBg] = ImVec4{0.1f, 0.1f, 0.13f, 0.92f};\n\n    // Slider\n    colors[ImGuiCol_SliderGrab] = ImVec4{0.44f, 0.37f, 0.61f, 0.54f};\n    colors[ImGuiCol_SliderGrabActive] = ImVec4{0.74f, 0.58f, 0.98f, 0.54f};\n\n    // Frame BG\n    colors[ImGuiCol_FrameBg] = ImVec4{0.13f, 0.13, 0.17, 1.0f};\n    colors[ImGuiCol_FrameBgHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};\n    colors[ImGuiCol_FrameBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n\n    // Tabs\n    colors[ImGuiCol_Tab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_TabHovered] = ImVec4{0.24, 0.24f, 0.32f, 1.0f};\n    colors[ImGuiCol_TabActive] = ImVec4{0.2f, 0.22f, 0.27f, 1.0f};\n    colors[ImGuiCol_TabUnfocused] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_TabUnfocusedActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n\n    // Title\n    colors[ImGuiCol_TitleBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_TitleBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_TitleBgCollapsed] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n\n    // Scrollbar\n    colors[ImGuiCol_ScrollbarBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0f};\n    colors[ImGuiCol_ScrollbarGrab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};\n    colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};\n    colors[ImGuiCol_ScrollbarGrabActive] = ImVec4{0.24f, 0.24f, 0.32f, 1.0f};\n\n    // Seperator\n    colors[ImGuiCol_Separator] = ImVec4{0.44f, 0.37f, 0.61f, 1.0f};\n    colors[ImGuiCol_SeparatorHovered] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f};\n    colors[ImGuiCol_SeparatorActive] = ImVec4{0.84f, 0.58f, 1.0f, 1.0f};\n\n    // Resize Grip\n    colors[ImGuiCol_ResizeGrip] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f};\n    colors[ImGuiCol_ResizeGripHovered] = ImVec4{0.74f, 0.58f, 0.98f, 0.29f};\n    colors[ImGuiCol_ResizeGripActive] = ImVec4{0.84f, 0.58f, 1.0f, 0.29f};\n\n    auto &style = ImGui::GetStyle();\n    style.TabRounding = 4;\n    style.ScrollbarRounding = 9;\n    style.WindowBorderSize = 0;\n    style.GrabRounding = 3;\n    style.FrameRounding = 3;\n    style.PopupRounding = 4;\n    style.ChildRounding = 4;\n}\n\nvoid Draw() {\n    ImGui::NewFrame();\n\n    ImGuiIO& io = ImGui::GetIO();\n    ImGui::SetNextWindowSize(io.DisplaySize);\n    ImGui::SetNextWindowPos(ImVec2(0, 0));\n\n    if (!ImGui::Begin(\"Variables\", nullptr, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {\n        ImGui::End();\n        return;\n    }\n    \n    if (ImGui::BeginMenuBar()) {\n        if (ImGui::BeginMenu(\"File\")) {\n            if (ImGui::MenuItem(\"Attach with PID\", \"Ctrl+A\")) {\n                open_pid_popup = true;\n            }\n            if (ImGui::BeginMenu(\"Theme\")) {\n                if (ImGui::MenuItem(\"Dark\", \"Ctrl+D\")) {\n                    ImGui::StyleColorsDark();\n                }\n                if (ImGui::MenuItem(\"Light\", \"Ctrl+L\")) {\n                    ImGui::StyleColorsLight();\n                }\n                if (ImGui::MenuItem(\"Classic\", \"Ctrl+C\")) {\n                    ImGui::StyleColorsClassic();\n                }\n                if (ImGui::MenuItem(\"Black\", \"Ctrl+B\")) {\n                    StyleColorsBlack();\n                }\n                if (ImGui::MenuItem(\"Funky\", \"Ctrl+F\")) {\n                    StyleColorsFunky();\n                }\n                ImGui::EndMenu();\n            }\n            ImGui::EndMenu();\n        }\n        ImGui::EndMenuBar();\n    }\n\n    ImVec2 center = ImGui::GetMainViewport()->GetCenter();\n    ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));\n    \n    if (ImGui::BeginPopup(\"AttachWithPID\", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove)) {\n        static bool attach_failed = false;\n\n        std::string pidInput;\n        pidInput.reserve(64);\n        ImGui::Text(\"PID:\");\n        ImGui::SameLine();\n        ImGui::SetKeyboardFocusHere();\n        if (ImGui::InputText(\"##pid\", &pidInput, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CharsDecimal)) {\n            pid = std::stoull(pidInput);\n            try {\n                HandleAttachProcess();\n                ImGui::CloseCurrentPopup();\n                attach_failed = false;\n            } catch (const std::exception& e) {\n                std::cerr << e.what() << std::endl;\n                attach_failed = true;\n            }\n        }\n        // TODO: progress bar\n\n        if (attach_failed) {\n            ImGui::BeginDisabled();\n            ImGui::TextColored(ImVec4{1.000, 0.353, 0.322, 1.0}, \"Error: Could not attach to pid %llu\", pid);\n            ImGui::EndDisabled();\n        }\n        ImGui::EndPopup();\n    }\n\n    if (open_pid_popup) {\n        open_pid_popup = false;\n        ImGui::OpenPopup(\"AttachWithPID\");\n    }\n\n    for (auto& var : variables) {\n        if (var.IsRoot()) {\n            DisplayVariable(var);\n        }\n    }\n\n    ImGui::End();\n    ImGui::Render();\n}\n\nvoid UpdateVariableValue(const VariableInfo* var_info) {\n    auto thread = GetThread(process);\n    if (!thread) return;\n\n    std::string fully_qualified_name = var_info->GetFullyQualifiedName();\n    std::string expression = fully_qualified_name + \" = \" + var_info->GetFullyQualifiedValue();\n\n    for (auto& frame : GetFrames(thread)) {\n        lldb::SBValue var = FindVariableById(frame, var_info->GetRoot().id);\n        if (!var) continue;\n\n        lldb::SBValue value = frame.EvaluateExpression(expression.c_str());\n        if (value && value.GetValue()) return;\n    }\n\n    std::cerr << \"Failed to evaluate \" << expression << std::endl;\n}\n\nvoid HandleLLDBProcessEvents() {\n    lldb::SBEvent event;\n    while (listener.PeekAtNextEvent(event)) {\n        if (lldb::SBProcess::EventIsProcessEvent(event)) {\n            lldb::StateType state = lldb::SBProcess::GetStateFromEvent(event);\n\n            if (state == lldb::eStateStopped) {\n                if (!published_changes) {\n                    UpdateVariableValue(current_var_info);\n                    published_changes = true;\n                }\n                FetchAllVariables();\n                process.Continue();\n            }\n        }\n        listener.GetNextEvent(event);\n    }\n}\n\nvoid HandleKeys() {\n    if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {\n        if (ImGui::IsKeyDown(ImGuiKey_A)) {\n            open_pid_popup = true;\n        } else if (ImGui::IsKeyDown(ImGuiKey_D)) {\n            ImGui::StyleColorsDark(nullptr);\n        } else if (ImGui::IsKeyDown(ImGuiKey_C)) {\n            ImGui::StyleColorsClassic(nullptr);\n        } else if (ImGui::IsKeyDown(ImGuiKey_L)) {\n            ImGui::StyleColorsLight(nullptr);\n        } else if (ImGui::IsKeyDown(ImGuiKey_B)) {\n            StyleColorsBlack();\n        } else if (ImGui::IsKeyDown(ImGuiKey_F)) {\n            StyleColorsFunky();\n        }\n    } else if (ImGui::IsKeyDown(ImGuiKey_S)) {\n        process.Stop();\n    }\n}\n\nvoid core() {\n    HandleKeys();\n    HandleLLDBProcessEvents();\n    Draw();\n}\n\nvoid SetupLoop() {\n    IMGUI_CHECKVERSION();\n    ImGui::CreateContext();\n    StyleColorsBlack();\n}\n\nvoid TearDownDebugger() {\n    lldb::SBDebugger::Destroy(debugger);\n}\n\nvoid SetupDebugger() {\n    setenv(\"LLDB_DEBUGSERVER_PATH\", GetDebugServerPath().c_str(), 1);\n    lldb::SBDebugger::Initialize();\n    debugger = lldb::SBDebugger::Create();\n}\n\n}\n\nint main() {\n    using namespace Hook;\n    try {\n        SetupDebugger();\n        SetupLoop();\n        main_loop(core);\n        TearDownDebugger();\n    } catch (const std::exception& e) {\n        std::cout << e.what() << std::endl;\n    }\n}\n"
  },
  {
    "path": "test/hook.h",
    "content": "#ifdef HOOK\n#define const volatile\n#endif"
  },
  {
    "path": "test/test.cpp",
    "content": "#include <iostream>\n#include <chrono>\n#include <thread>\n#include \"hook.h\"\n\nusing namespace std::chrono_literals;\n\nfloat global_float = 3.14f;\n\nstruct Foo {\n    int a = 0;\n    int b = 0;\n};\nstd::ostream& operator<<(std::ostream& s, const Foo& foo) {\n    s << \"foo: (a: \" << foo.a << \", b: \" << foo.b << \")\";\n    return s;\n}\n\nclass Bar {\npublic:\n    int c = 0;\n\nprivate:\n    Foo foo;\n    friend std::ostream& operator<<(std::ostream& os, const Bar& obj);\n};\nstd::ostream& operator<<(std::ostream& s, const Bar& bar) {\n    s << \"bar: (\" << bar.c << \", \" << bar.foo << \")\";\n    return s;\n}\n\nvoid infinite_sleep() {\n    bool stop = false;\n    while (!stop) {\n        std::this_thread::sleep_for(20ms);\n    }\n}\n\nint main() {\n    Foo foo;\n    Bar bar;\n    bool stop = false;\n    for (const int i = 0; !stop;) {\n        infinite_sleep();\n        std::cout << \"i: \" << i << std::endl;\n        std::cout << foo << std::endl;\n        std::cout << bar << std::endl;\n        std::cout << \"global_float: \" << global_float << std::endl;\n    }\n\n    std::cout << \"exited!\" << std::endl;\n}"
  },
  {
    "path": "test/test.sh",
    "content": "g++ -std=c++20 -g -O0 -DHOOK -o test test.cpp\n./test &"
  }
]