diff --git a/CMakeLists.txt b/CMakeLists.txt index b9023ae..0b52092 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,10 @@ add_library(${PROJECT_NAME} SHARED ${SOURCES} src/platform/Mac.mm) set_source_files_properties(src/platform/Mac.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON) +if ("${CMAKE_SYSTEM_NAME}" STREQUAL "iOS" OR IOS) + target_link_libraries(${PROJECT_NAME} "-framework CoreGraphics") +endif() + if (NOT DEFINED ENV{GEODE_SDK}) message(FATAL_ERROR "Unable to find Geode SDK! Please define GEODE_SDK environment variable to point to Geode") else() diff --git a/mod.json b/mod.json index 93ff646..743eb35 100644 --- a/mod.json +++ b/mod.json @@ -1,5 +1,5 @@ { - "geode": "4.8.0", + "geode": "4.9.0", "version": "v1.10.0", "gd": { "win": "2.2074", diff --git a/src/DevTools.cpp b/src/DevTools.cpp index 294855f..6ab71b9 100644 --- a/src/DevTools.cpp +++ b/src/DevTools.cpp @@ -275,6 +275,10 @@ void DevTools::toggle() { this->show(!m_visible); } +bool DevTools::isVisible() { + return m_visible; +} + void DevTools::sceneChanged() { m_selectedNode = nullptr; } diff --git a/src/DevTools.hpp b/src/DevTools.hpp index a27dfa5..614c303 100644 --- a/src/DevTools.hpp +++ b/src/DevTools.hpp @@ -116,4 +116,6 @@ class DevTools { void show(bool visible); void toggle(); + + bool isVisible(); }; diff --git a/src/main.cpp b/src/main.cpp index 9e2cd45..11f3df5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "DevTools.hpp" #include #include "ImGui.hpp" @@ -41,6 +42,32 @@ class $modify(MenuLayer) { #endif +class $modify(GameToolbox) { + static void preVisitWithClippingRect(CCNode* node, CCRect clipRect) { + if (!node->isVisible() || !DevTools::get()->isVisible()) + return GameToolbox::preVisitWithClippingRect(node, clipRect); + + glEnable(GL_SCISSOR_TEST); + + clipRect.origin = node->convertToWorldSpace(clipRect.origin); + + kmMat4 mat; + kmGLGetMatrix(KM_GL_PROJECTION, &mat); + if (mat.mat[5] < 0) { + auto ws = CCDirector::get()->getWinSize(); + clipRect.origin.y = ws.height - (clipRect.origin.y + node->getContentSize().height); + } + + CCEGLView::get()->setScissorInPoints( + clipRect.origin.x, + clipRect.origin.y, + clipRect.size.width, + clipRect.size.height + ); + } + +}; + class $modify(CCDirector) { void willSwitchToScene(CCScene* scene) { CCDirector::willSwitchToScene(scene); diff --git a/src/pages/Attributes.cpp b/src/pages/Attributes.cpp index f308f63..bf8ac1b 100644 --- a/src/pages/Attributes.cpp +++ b/src/pages/Attributes.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace geode::prelude; @@ -14,6 +15,7 @@ using namespace geode::prelude; &AxisLayoutOptions::get##Name_, \ &AxisLayoutOptions::set##Name_ + template bool checkbox(const char* text, T* ptr, bool(T::* get)(), R(T::* set)(bool)) { bool value = (ptr->*get)(); @@ -72,13 +74,31 @@ void DevTools::drawBasicAttributes(CCNode* node) { } ImGui::SameLine(); if (ImGui::Button(U8STR(FEATHER_COPY " Copy Class Name"))) { - clipboard::write(getNodeName(node)); + clipboard::write(std::string(geode::cocos::getObjectName(node))); + } + ImGui::SameLine(); + if (ImGui::Button(U8STR(FEATHER_SAVE " Screenshot"))) { + file::pick(file::PickMode::SaveFile, file::FilePickOptions { + .filters = {{ .description = "PNG Image", .files = {"*.png"} }} + }).listen([node](auto choice) { + if (auto file = choice->ok()) { + int width, height; + auto bytes = renderToBytes(node, width, height); + + auto path = string::pathToString(*file); + if (!path.ends_with(".png")) { + path += ".png"; + } + saveRenderToFile(bytes, width, height, path.c_str()); + } + }); } + ImGui::Text("Address: %s", fmt::to_string(fmt::ptr(node)).c_str()); ImGui::SameLine(); if (ImGui::Button(U8STR(FEATHER_COPY " Copy"))) { clipboard::write( - utils::intToHex(reinterpret_cast(node)) + fmt::format("{:#x}", reinterpret_cast(node)) ); } if (node->getUserData()) { diff --git a/src/pages/Tree.cpp b/src/pages/Tree.cpp index 51f1faa..e18669c 100644 --- a/src/pages/Tree.cpp +++ b/src/pages/Tree.cpp @@ -24,7 +24,7 @@ void DevTools::drawTreeBranch(CCNode* node, size_t index) { flags |= ImGuiTreeNodeFlags_OpenOnArrow; } std::stringstream name; - name << "[" << index << "] " << getNodeName(node) << " "; + name << "[" << index << "] " << geode::cocos::getObjectName(node) << " "; if (node->getTag() != -1) { name << "(" << node->getTag() << ") "; } diff --git a/src/platform/Android.cpp b/src/platform/Android.cpp index 288f683..0e27da0 100644 --- a/src/platform/Android.cpp +++ b/src/platform/Android.cpp @@ -7,9 +7,10 @@ using namespace geode::prelude; #include "utils.hpp" std::string formatAddressIntoOffsetImpl(uintptr_t addr, bool module) { - if (addr > base::get() && addr - 0x1000000 < base::get()) + if (addr > base::get() && addr - 0x1000000 < base::get()) { if(module) return fmt::format("libcocos2d.so + {:#x}", addr - base::get()); else return fmt::format("{:#x}", addr - base::get()); + } return fmt::format("{:#x}", addr); } diff --git a/src/platform/Mac.mm b/src/platform/Mac.mm index 6a28dcb..8b96b50 100644 --- a/src/platform/Mac.mm +++ b/src/platform/Mac.mm @@ -5,6 +5,8 @@ #include "utils.hpp" #include +#include +#include #include #include #include @@ -18,6 +20,13 @@ #include #import +#import +#ifdef GEODE_IS_MACOS +#include +#else +#import +#endif + static std::vector getAllImages() { std::vector images; struct task_dyld_info dyldInfo; @@ -86,4 +95,5 @@ else return fmt::format("{:#x}", addr - base); } + #endif \ No newline at end of file diff --git a/src/platform/utils.cpp b/src/platform/utils.cpp index 2aca9ae..ee4c260 100644 --- a/src/platform/utils.cpp +++ b/src/platform/utils.cpp @@ -1,6 +1,14 @@ #include "utils.hpp" +#if defined(GEODE_IS_MACOS) +#include +#elif defined(GEODE_IS_IOS) +#include +#endif #include +#include + +using namespace cocos2d; std::string formatAddressIntoOffset(uintptr_t addr, bool module) { static std::unordered_map> formatted; @@ -16,4 +24,92 @@ std::string formatAddressIntoOffset(uintptr_t addr, bool module) { if(module) return pair.first; else return pair.second; } +} + +std::vector renderToBytes(CCNode* node, int& width, int& height) { + // Get scale from cocos2d units to opengl units + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + auto winSize = CCDirector::get()->getWinSize(); + + width = node->getContentSize().width * (viewport[2] / winSize.width); + height = node->getContentSize().height * (viewport[3] / winSize.height); + + // Create Texture + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Create Framebuffer Object + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + + // Unbind texture + glBindTexture(GL_TEXTURE_2D, 0); + + // Clear any data + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Flip Y when projecting + kmGLMatrixMode(KM_GL_PROJECTION); + kmGLPushMatrix(); + kmGLLoadIdentity(); + + kmMat4 ortho; + kmMat4OrthographicProjection(&ortho, + 0.0f, winSize.width, + winSize.height, 0.0f, + -1.0f, 1.0f + ); + kmGLMultMatrix(&ortho); + + // Transform matrix so the node is drawn at 0,0 + kmGLMatrixMode(KM_GL_MODELVIEW); + kmGLPushMatrix(); + kmGLLoadIdentity(); + + auto anchor = node->isIgnoreAnchorPointForPosition() ? ccp(0, 0) : node->getAnchorPointInPoints(); + kmGLTranslatef( + anchor.x - node->getPositionX(), + anchor.y - node->getPositionY() + (winSize.height - node->getContentSize().height), + 0 + ); + + // Visit + node->visit(); + + // Undo matrix transformations + kmGLPopMatrix(); + kmGLMatrixMode(KM_GL_PROJECTION); + kmGLPopMatrix(); + kmGLMatrixMode(KM_GL_MODELVIEW); + + // Read from Framebuffer + std::vector pixels(width * height * 4); // RGBA8 + glReadPixels( + 0, 0, width, height, + GL_RGBA, GL_UNSIGNED_BYTE, + pixels.data() + ); + + // Unbind Framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Delete + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &texture); + + return pixels; +} + +void saveRenderToFile(std::vector const& data, int width, int height, char const* filename) { + auto img = new CCImage(); + img->initWithImageData((void*)data.data(), data.size(), CCImage::kFmtRawData, width, height, 8); + img->saveToFile(filename, false); } \ No newline at end of file diff --git a/src/platform/utils.hpp b/src/platform/utils.hpp index 13d00a7..11c17e9 100644 --- a/src/platform/utils.hpp +++ b/src/platform/utils.hpp @@ -3,27 +3,10 @@ #include #include #include -#ifndef GEODE_IS_WINDOWS -#include -#endif - -static inline std::string getNodeName(cocos2d::CCObject* node) { -#ifdef GEODE_IS_WINDOWS - return typeid(*node).name() + 6; -#else - std::string ret; - - int status = 0; - auto demangle = abi::__cxa_demangle(typeid(*node).name(), 0, 0, &status); - if (status == 0) { - ret = demangle; - } - free(demangle); - - return ret; -#endif -} std::string formatAddressIntoOffset(uintptr_t addr, bool module); std::string formatAddressIntoOffsetImpl(uintptr_t addr, bool module); + +std::vector renderToBytes(cocos2d::CCNode* node, int& width, int& height); +void saveRenderToFile(std::vector const& data, int width, int height, char const* filename);