Merge branch 'geode-sdk:main' into main

This commit is contained in:
Sebastián Meljem 2025-04-01 01:56:30 -03:00 committed by GitHub
commit c21c666e6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 2105 additions and 238 deletions

View file

@ -2,3 +2,5 @@
11e81e3d64313d319955d9a214ad0ded78985bed
2bb416ba77f2f01897cc10a1afac40c957feadfc
# whole lot of whitespace changes
0cecd677561d1992859f30dc1e233d8d5d83c9ad

View file

@ -295,10 +295,74 @@ jobs:
target: ${{ matrix.config.id }}
if: inputs.build-debug-info && (success() || failure())
build-ios:
name: Build iOS
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Prepare for Build Debug Info
id: build-debug-info
uses: ./.github/actions/build-debug-info
with:
has-sccache: ${{ inputs.use-ccache }}
if: inputs.build-debug-info
- name: Setup caches
uses: ./.github/actions/setup-cache
with:
host: mac
target: ios
use-ccache: ${{ github.event_name != 'workflow_dispatch' || inputs.use-ccache }}
- name: Setup Ninja
uses: ./.github/actions/setup-ninja
with:
host: mac
- name: Install LLVM
run: |
brew install llvm
echo "/opt/homebrew/opt/llvm/bin" >> $GITHUB_PATH
- name: Setup CLI
uses: geode-sdk/cli/.github/actions/setup@main
- name: Configure
run: >
${{ env.base-configure-command }}
-DGEODE_TARGET_PLATFORM=iOS
-DCMAKE_SYSTEM_NAME=iOS
-DGEODE_DONT_BUILD_TEST_MODS=ON
-DCMAKE_BUILD_TYPE=RelWithDebInfo
${{ steps.build-debug-info.outputs.extra-configure }}
- name: Build
run: |
${{ env.base-build-command }}
${{ steps.build-debug-info.outputs.extra-build }}
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: geode-ios
path: ./bin/nightly
- name: Complete Build Debug Info
uses: ./.github/actions/build-debug-info-post
with:
target: mac
if: inputs.build-debug-info && (success() || failure())
publish:
name: Publish
runs-on: ubuntu-latest
needs: [ build-windows, build-mac, build-android ]
needs: [ build-windows, build-mac, build-android, build-ios ]
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout
@ -342,6 +406,12 @@ jobs:
files: geode-android64/Geode.android64.so geode-android64/Geode.android64.so.sym
dest: geode-${{ steps.ref.outputs.hash }}-android64.zip
- name: Zip iOS Artifacts
uses: vimtor/action-zip@v1.2
with:
files: geode-ios/Geode.ios.dylib
dest: geode-${{ steps.ref.outputs.hash }}-ios.zip
- name: Zip Resources
uses: vimtor/action-zip@v1.2
with:
@ -364,4 +434,5 @@ jobs:
./geode-${{ steps.ref.outputs.hash }}-mac.zip
./geode-${{ steps.ref.outputs.hash }}-android32.zip
./geode-${{ steps.ref.outputs.hash }}-android64.zip
./geode-${{ steps.ref.outputs.hash }}-ios.zip
./resources.zip

View file

@ -241,8 +241,13 @@ function(setup_geode_mod proname)
if (WIN32 OR LINUX)
file(GLOB libs ${dir}/*.lib)
list(APPEND libs_to_link ${libs})
elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "iOS")
file(GLOB libs ${dir}/*.ios.dylib)
list(APPEND libs_to_link ${libs})
elseif (APPLE)
file(GLOB libs ${dir}/*.dylib)
file(GLOB ios_libs ${dir}/*.ios.dylib)
list(REMOVE_ITEM libs ${ios_libs})
list(APPEND libs_to_link ${libs})
elseif (ANDROID)
if (CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")

View file

@ -5,18 +5,49 @@ if (NOT ${PROJECT_NAME} STREQUAL ${CMAKE_PROJECT_NAME})
endif()
if (GEODE_TARGET_PLATFORM STREQUAL "iOS")
# make sure that we get the ios sdk
execute_process(COMMAND xcrun --show-sdk-path --sdk iphoneos
OUTPUT_VARIABLE GEODE_IOS_SDK
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "iOS c++ compiler: ${CMAKE_CXX_COMPILER}")
set(CMAKE_OSX_ARCHITECTURES arm64)
set(CMAKE_OSX_SYSROOT ${GEODE_IOS_SDK})
set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0")
set(CMAKE_SYSTEM_NAME "iOS")
# this fails on ios builds
set(BUILD_MD2HTML_EXECUTABLE "OFF")
set_target_properties(${PROJECT_NAME} PROPERTIES
SYSTEM_NAME iOS
OSX_SYSROOT ${GEODE_IOS_SDK}
OSX_ARCHITECTURES arm64
)
target_link_libraries(${PROJECT_NAME} INTERFACE
"-framework OpenGLES" # needed for CCClippingNode reimpl and ScrollLayer
"-framework UIKit" # needed for file picking (UIApplication)
"-framework Foundation" # needed for many things
"-framework AVFoundation" # needed for fmod
"-framework AudioToolbox" # needed for fmod
${GEODE_LOADER_PATH}/include/link/ios/libcurl.a
${GEODE_LOADER_PATH}/include/link/ios/libfmod_iphoneos.a
)
target_compile_definitions(${PROJECT_NAME} INTERFACE
-DCommentType=CommentTypeDummy
)
set(GEODE_OUTPUT_NAME "Geode.ios")
set(GEODE_PLATFORM_BINARY "Geode.ios.dylib")
set(GEODE_MOD_BINARY_SUFFIX ".ios.dylib" CACHE STRING "" FORCE)
if (NOT ${PROJECT_NAME} STREQUAL ${CMAKE_PROJECT_NAME})
set(GEODE_TARGET_PLATFORM_SHORT "ios" PARENT_SCOPE)
# this is needed because else loading mods will fail below ios 14.5
set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" PARENT_SCOPE)
else()
set(GEODE_TARGET_PLATFORM_SHORT "ios")
endif()

View file

@ -1,6 +1,6 @@
if (NOT DEFINED GEODE_TARGET_PLATFORM)
if(APPLE)
if(IOS)
if("${CMAKE_SYSTEM_NAME}" STREQUAL "iOS" OR IOS)
set(GEODE_TARGET_PLATFORM "iOS")
else()
set(GEODE_TARGET_PLATFORM "MacOS")

View file

@ -88,7 +88,7 @@ file(GLOB SOURCES CONFIGURE_DEPENDS
)
# Obj-c sources
if (IOS)
if ("${CMAKE_SYSTEM_NAME}" STREQUAL "iOS" OR IOS)
file(GLOB OBJC_SOURCES CONFIGURE_DEPENDS
src/platform/ios/*.mm
src/load.mm
@ -118,17 +118,16 @@ if (WIN32)
)
list(APPEND SOURCES ${WIN_SOURCES})
elseif(IOS)
file(GLOB IOS_SOURCES CONFIGURE_DEPENDS
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "iOS" OR IOS)
file(GLOB IOS_SOURCES CONFIGURE_DEPENDS
src/platform/ios/*.cpp
src/platform/mac/Cocos2d.cpp # identical on ios, so we just use the mac one
)
list(APPEND SOURCES ${IOS_SOURCES})
list(APPEND SOURCES ${OBJC_SOURCES})
elseif(APPLE)
file(GLOB MAC_SOURCES CONFIGURE_DEPENDS
file(GLOB MAC_SOURCES CONFIGURE_DEPENDS
src/platform/mac/*.cpp
)
list(APPEND SOURCES ${MAC_SOURCES})
@ -326,22 +325,22 @@ endif()
# Create launcher
if (APPLE)
set_target_properties(geode-loader PROPERTIES
SYSTEM_NAME MacOS
OSX_DEPLOYMENT_TARGET 10.15
APPLE_SILICON_PROCESSOR x86_64
)
add_subdirectory(launcher/mac)
if("${CMAKE_SYSTEM_NAME}" STREQUAL "iOS" OR IOS)
# Used for File Picker API
find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers)
target_link_libraries(${PROJECT_NAME} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK})
else()
set_target_properties(geode-loader PROPERTIES
SYSTEM_NAME MacOS
OSX_DEPLOYMENT_TARGET 10.15
APPLE_SILICON_PROCESSOR x86_64
)
if(GEODE_TARGET_PLATFORM STREQUAL "iOS")
add_custom_command(TARGET geode-loader
POST_BUILD COMMAND
${CMAKE_INSTALL_NAME_TOOL} -id \"/Library/MobileSubstrate/DynamicLibraries/Geode.dylib\"
$<TARGET_FILE:geode-loader>)
# geodebootstrapper is unused on ios
add_subdirectory(launcher/mac)
set(LAUNCHER_TARGET GeodeBootstrapper)
endif()
set(LAUNCHER_TARGET GeodeBootstrapper)
elseif (WIN32)
add_subdirectory(launcher/windows)

View file

@ -153,7 +153,7 @@
#endif
/* The size of `long', as computed by sizeof. */
#define CURL_SIZEOF_LONG 4
#define CURL_SIZEOF_LONG 8
/* Integral data type used for curl_socklen_t. */
#define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t

View file

@ -16,7 +16,17 @@ namespace geode {
namespace geode::base {
GEODE_NOINLINE inline uintptr_t get() {
static uintptr_t base = _dyld_get_image_vmaddr_slide(0) + 0x100000000;
static uintptr_t base = []() -> uintptr_t {
for(uint32_t gdii = 0; gdii < _dyld_image_count(); gdii++) {
std::string_view imageName(_dyld_get_image_name(gdii));
if (imageName.ends_with("GeometryJump")) {
return _dyld_get_image_vmaddr_slide(gdii) + 0x100000000;
}
}
return 0;
}();
return base;
}
}

View file

@ -23,6 +23,15 @@ namespace geode {
* @returns The implementation of the method, or an error.
*/
Result<void*> getObjcMethodImp(std::string const& className, std::string const& selectorName);
/**
* Replace an Objective-C method with a new implementation.
* @param className The name of the class whose method to replace
* @param selectorName The name of the method to replace
* @param imp The new implementation of the method
* @returns Ok() if the method was replaced successfully, or an error.
*/
Result<void*> replaceObjcMethod(std::string const& className, std::string const& selectorName, void* imp);
}
class ObjcHook {

Binary file not shown.

Binary file not shown.

View file

@ -1,11 +0,0 @@
#!/bin/sh
cd `dirname $0`
cp /Users/jakrillis/tmp/geode/loader/loader_ios/build/Geode.dylib ./bin/ios/Geode.dylib
rm -rf Geode\ Helper.app
cp -r /Users/jakrillis/Library/Developer/Xcode/DerivedData/Geode_Helper-eojylhlhpfgucjaupttaqyhqcbmp/Build/Products/Debug-iphoneos/Geode\ Helper.app .
g++ main.cpp -arch arm64 -std=c++17 -isysroot `xcrun --show-sdk-path --sdk iphoneos` -dynamiclib -o GeodeLauncher.dylib
python3 pkg.py
scp "./geodeloader-test-arm64.deb" root@three.local:/var/mobile/Documents
ssh root@three.local dpkg -i "/var/mobile/Documents/geodeloader-test-arm64.deb" "&&" killall GeometryJump #";" uicache

View file

@ -1,11 +0,0 @@
#include <dlfcn.h>
#include <unistd.h>
#include <thread>
void inject() __attribute__((constructor)) {
std::thread t1([]() {
sleep(1);
dlopen("/Library/MobileSubstrate/DynamicLibraries/Geode.dylib", RTLD_NOW);
});
t1.detach();
}

View file

@ -1,34 +0,0 @@
import sys, os
from shutil import copyfile, rmtree
from distutils.dir_util import copy_tree
print(sys.argv)
out_dir = os.getcwd()
os.makedirs(out_dir, exist_ok=True)
os.chdir(out_dir)
os.makedirs("tmp/Library/MobileSubstrate/DynamicLibraries/", exist_ok=True)
os.makedirs("tmp/Applications/", exist_ok=True)
os.makedirs("tmp/DEBIAN/", exist_ok=True)
open("tmp/DEBIAN/control", "w").write("""Name: Geode Launcher
Architecture: iphoneos-arm
Depends: com.cokepokes.libnotifications (>= 0.2-2)
Description: Modding suite for Geometry Dash (test package!!)
Maintainer: camila314
Package: com.camila314.geode-test
Priority: optional
Section: Tweaks
Version: 0.1.0
""")
copy_tree("Geode Helper.app/", "tmp/Applications/Geode Helper.app/")
copyfile("/Users/jakrillis/tmp/geode/loader/loader_ios/build/Geode.dylib", "bin/ios/Geode.dylib")
os.system("ldid -S GeodeLauncher.dylib")
os.system("ldid -S bin/ios/Geode.dylib")
copyfile("GeodeLauncher.dylib", "tmp/Library/MobileSubstrate/DynamicLibraries/GeodeLauncher.dylib")
copyfile("bin/ios/Geode.dylib", "tmp/Library/MobileSubstrate/DynamicLibraries/Geode.dylib")
open("tmp/Library/MobileSubstrate/DynamicLibraries/GeodeLauncher.plist", "w").write("""{ Filter = { Bundles = ( "com.robtop.geometryjump" ); }; }""")
os.system("dpkg-deb --build tmp geodeloader-test-arm64.deb")
rmtree("tmp")

View file

@ -0,0 +1,371 @@
#include <cocos2d.h>
using namespace cocos2d;
#ifdef GEODE_IS_IOS
#pragma warning(push)
#pragma warning(disable : 4273)
static GLint g_sStencilBits = -1;
static void setProgram(CCNode *n, CCGLProgram *p)
{
n->setShaderProgram(p);
if (!n->getChildren()) return;
CCObject* pObj = NULL;
CCARRAY_FOREACH(n->getChildren(), pObj)
{
setProgram((CCNode*)pObj, p);
}
}
CCClippingNode::CCClippingNode()
: m_pStencil(NULL)
, m_fAlphaThreshold(0.0f)
, m_bInverted(false)
{}
CCClippingNode::~CCClippingNode()
{
CC_SAFE_RELEASE(m_pStencil);
}
CCClippingNode* CCClippingNode::create()
{
CCClippingNode *pRet = new CCClippingNode();
if (pRet && pRet->init())
{
pRet->autorelease();
}
else
{
CC_SAFE_DELETE(pRet);
}
return pRet;
}
CCClippingNode* CCClippingNode::create(CCNode *pStencil)
{
CCClippingNode *pRet = new CCClippingNode();
if (pRet && pRet->init(pStencil))
{
pRet->autorelease();
}
else
{
CC_SAFE_DELETE(pRet);
}
return pRet;
}
bool CCClippingNode::init()
{
return init(NULL);
}
bool CCClippingNode::init(CCNode *pStencil)
{
CC_SAFE_RELEASE(m_pStencil);
m_pStencil = pStencil;
CC_SAFE_RETAIN(m_pStencil);
m_fAlphaThreshold = 1;
m_bInverted = false;
// get (only once) the number of bits of the stencil buffer
static bool once = true;
if (once)
{
glGetIntegerv(GL_STENCIL_BITS, &g_sStencilBits);
if (g_sStencilBits <= 0)
{
CCLOG("Stencil buffer is not enabled.");
}
once = false;
}
return true;
}
void CCClippingNode::onEnter()
{
CCNode::onEnter();
m_pStencil->onEnter();
}
void CCClippingNode::onEnterTransitionDidFinish()
{
CCNode::onEnterTransitionDidFinish();
m_pStencil->onEnterTransitionDidFinish();
}
void CCClippingNode::onExitTransitionDidStart()
{
m_pStencil->onExitTransitionDidStart();
CCNode::onExitTransitionDidStart();
}
void CCClippingNode::onExit()
{
m_pStencil->onExit();
CCNode::onExit();
}
void CCClippingNode::visit()
{
// if stencil buffer disabled
if (g_sStencilBits < 1)
{
// draw everything, as if there where no stencil
CCNode::visit();
return;
}
// return fast (draw nothing, or draw everything if in inverted mode) if:
// - nil stencil node
// - or stencil node invisible:
if (!m_pStencil || !m_pStencil->isVisible())
{
if (m_bInverted)
{
// draw everything
CCNode::visit();
}
return;
}
// store the current stencil layer (position in the stencil buffer),
// this will allow nesting up to n CCClippingNode,
// where n is the number of bits of the stencil buffer.
static GLint layer = -1;
// all the _stencilBits are in use?
if (layer + 1 == g_sStencilBits)
{
// warn once
static bool once = true;
if (once)
{
char warning[200] = {0};
snprintf(warning, sizeof(warning), "Nesting more than %d stencils is not supported. Everything will be drawn without stencil for this node and its childs.", g_sStencilBits);
CCLOG("%s", warning);
once = false;
}
// draw everything, as if there where no stencil
CCNode::visit();
return;
}
///////////////////////////////////
// INIT
// increment the current layer
layer++;
// mask of the current layer (ie: for layer 3: 00000100)
GLint mask_layer = 0x1 << layer;
// mask of all layers less than the current (ie: for layer 3: 00000011)
GLint mask_layer_l = mask_layer - 1;
// mask of all layers less than or equal to the current (ie: for layer 3: 00000111)
GLint mask_layer_le = mask_layer | mask_layer_l;
// manually save the stencil state
GLboolean currentStencilEnabled = GL_FALSE;
GLuint currentStencilWriteMask = ~0;
GLenum currentStencilFunc = GL_ALWAYS;
GLint currentStencilRef = 0;
GLuint currentStencilValueMask = ~0;
GLenum currentStencilFail = GL_KEEP;
GLenum currentStencilPassDepthFail = GL_KEEP;
GLenum currentStencilPassDepthPass = GL_KEEP;
currentStencilEnabled = glIsEnabled(GL_STENCIL_TEST);
glGetIntegerv(GL_STENCIL_WRITEMASK, (GLint *)&currentStencilWriteMask);
glGetIntegerv(GL_STENCIL_FUNC, (GLint *)&currentStencilFunc);
glGetIntegerv(GL_STENCIL_REF, &currentStencilRef);
glGetIntegerv(GL_STENCIL_VALUE_MASK, (GLint *)&currentStencilValueMask);
glGetIntegerv(GL_STENCIL_FAIL, (GLint *)&currentStencilFail);
glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, (GLint *)&currentStencilPassDepthFail);
glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, (GLint *)&currentStencilPassDepthPass);
// enable stencil use
glEnable(GL_STENCIL_TEST);
// check for OpenGL error while enabling stencil test
CHECK_GL_ERROR_DEBUG();
// all bits on the stencil buffer are readonly, except the current layer bit,
// this means that operation like glClear or glStencilOp will be masked with this value
glStencilMask(mask_layer);
glClear(GL_STENCIL_BUFFER_BIT);
// manually save the depth test state
//GLboolean currentDepthTestEnabled = GL_TRUE;
GLboolean currentDepthWriteMask = GL_TRUE;
//currentDepthTestEnabled = glIsEnabled(GL_DEPTH_TEST);
glGetBooleanv(GL_DEPTH_WRITEMASK, &currentDepthWriteMask);
// disable depth test while drawing the stencil
//glDisable(GL_DEPTH_TEST);
// disable update to the depth buffer while drawing the stencil,
// as the stencil is not meant to be rendered in the real scene,
// it should never prevent something else to be drawn,
// only disabling depth buffer update should do
glDepthMask(GL_FALSE);
///////////////////////////////////
// CLEAR STENCIL BUFFER
// manually clear the stencil buffer by drawing a fullscreen rectangle on it
// setup the stencil test func like this:
// for each pixel in the fullscreen rectangle
// never draw it into the frame buffer
// if not in inverted mode: set the current layer value to 0 in the stencil buffer
// if in inverted mode: set the current layer value to 1 in the stencil buffer
glStencilFunc(GL_NEVER, mask_layer, mask_layer);
glStencilOp(!m_bInverted ? GL_ZERO : GL_REPLACE, GL_KEEP, GL_KEEP);
// draw a fullscreen solid rectangle to clear the stencil buffer
//ccDrawSolidRect(CCPointZero, ccpFromSize([[CCDirector sharedDirector] winSize]), ccc4f(1, 1, 1, 1));
ccDrawSolidRect(CCPointZero, ccpFromSize(CCDirector::sharedDirector()->getWinSize()), ccc4f(1, 1, 1, 1));
///////////////////////////////////
// DRAW CLIPPING STENCIL
// setup the stencil test func like this:
// for each pixel in the stencil node
// never draw it into the frame buffer
// if not in inverted mode: set the current layer value to 1 in the stencil buffer
// if in inverted mode: set the current layer value to 0 in the stencil buffer
glStencilFunc(GL_NEVER, mask_layer, mask_layer);
glStencilOp(!m_bInverted ? GL_REPLACE : GL_ZERO, GL_KEEP, GL_KEEP);
// enable alpha test only if the alpha threshold < 1,
// indeed if alpha threshold == 1, every pixel will be drawn anyways
#ifdef GEODE_IS_DESKTOP
GLboolean currentAlphaTestEnabled = GL_FALSE;
GLenum currentAlphaTestFunc = GL_ALWAYS;
GLclampf currentAlphaTestRef = 1;
#endif
if (m_fAlphaThreshold < 1) {
#ifdef GEODE_IS_DESKTOP
// manually save the alpha test state
currentAlphaTestEnabled = glIsEnabled(GL_ALPHA_TEST);
glGetIntegerv(GL_ALPHA_TEST_FUNC, (GLint *)&currentAlphaTestFunc);
glGetFloatv(GL_ALPHA_TEST_REF, &currentAlphaTestRef);
// enable alpha testing
glEnable(GL_ALPHA_TEST);
// check for OpenGL error while enabling alpha test
CHECK_GL_ERROR_DEBUG();
// pixel will be drawn only if greater than an alpha threshold
glAlphaFunc(GL_GREATER, m_fAlphaThreshold);
#else
// since glAlphaTest do not exists in OES, use a shader that writes
// pixel only if greater than an alpha threshold
CCGLProgram *program = CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColorAlphaTest);
GLint alphaValueLocation = glGetUniformLocation(program->getProgram(), kCCUniformAlphaTestValue);
// set our alphaThreshold
program->use();
program->setUniformLocationWith1f(alphaValueLocation, m_fAlphaThreshold);
// we need to recursively apply this shader to all the nodes in the stencil node
// XXX: we should have a way to apply shader to all nodes without having to do this
setProgram(m_pStencil, program);
#endif
}
// draw the stencil node as if it was one of our child
// (according to the stencil test func/op and alpha (or alpha shader) test)
kmGLPushMatrix();
transform();
m_pStencil->visit();
kmGLPopMatrix();
// restore alpha test state
if (m_fAlphaThreshold < 1)
{
#ifdef GEODE_IS_DESKTOP
// manually restore the alpha test state
glAlphaFunc(currentAlphaTestFunc, currentAlphaTestRef);
if (!currentAlphaTestEnabled)
{
glDisable(GL_ALPHA_TEST);
}
#else
// XXX: we need to find a way to restore the shaders of the stencil node and its childs
#endif
}
// restore the depth test state
glDepthMask(currentDepthWriteMask);
//if (currentDepthTestEnabled) {
// glEnable(GL_DEPTH_TEST);
//}
///////////////////////////////////
// DRAW CONTENT
// setup the stencil test func like this:
// for each pixel of this node and its childs
// if all layers less than or equals to the current are set to 1 in the stencil buffer
// draw the pixel and keep the current layer in the stencil buffer
// else
// do not draw the pixel but keep the current layer in the stencil buffer
glStencilFunc(GL_EQUAL, mask_layer_le, mask_layer_le);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
// draw (according to the stencil test func) this node and its childs
CCNode::visit();
///////////////////////////////////
// CLEANUP
// manually restore the stencil state
glStencilFunc(currentStencilFunc, currentStencilRef, currentStencilValueMask);
glStencilOp(currentStencilFail, currentStencilPassDepthFail, currentStencilPassDepthPass);
glStencilMask(currentStencilWriteMask);
if (!currentStencilEnabled)
{
glDisable(GL_STENCIL_TEST);
}
// we are done using this layer, decrement
layer--;
}
CCNode* CCClippingNode::getStencil() const
{
return m_pStencil;
}
void CCClippingNode::setStencil(CCNode *pStencil)
{
CC_SAFE_RELEASE(m_pStencil);
m_pStencil = pStencil;
CC_SAFE_RETAIN(m_pStencil);
}
GLfloat CCClippingNode::getAlphaThreshold() const
{
return m_fAlphaThreshold;
}
void CCClippingNode::setAlphaThreshold(GLfloat fAlphaThreshold)
{
m_fAlphaThreshold = fAlphaThreshold;
}
bool CCClippingNode::isInverted() const
{
return m_bInverted;
}
void CCClippingNode::setInverted(bool bInverted)
{
m_bInverted = bInverted;
}
#pragma warning(pop)
#endif

View file

@ -0,0 +1,393 @@
#include <cocos2d.h>
using namespace cocos2d;
#ifdef GEODE_IS_IOS
CCKeyboardDispatcher::CCKeyboardDispatcher()
: m_bUnknown38(false),
m_bUnknown39(false),
m_bUnknown3a(false),
m_bShiftPressed(false),
m_bControlPressed(false),
m_bAltPressed(false),
m_bCommandPressed(false),
m_bBlockRepeat(false),
m_pDelegates(CCArray::create()),
m_pUnknown3c(ccCArrayNew(8)),
m_pUnknown40(ccCArrayNew(8))
{
m_pDelegates->retain();
}
CCKeyboardDispatcher::~CCKeyboardDispatcher()
{
CC_SAFE_RELEASE(m_pDelegates);
if (m_pUnknown3c)
{
ccCArrayFree(m_pUnknown3c);
}
if (m_pUnknown40)
{
ccCArrayFree(m_pUnknown40);
}
}
void CCKeyboardDispatcher::addDelegate(CCKeyboardDelegate* pDelegate)
{
if (!pDelegate) return;
if (m_bUnknown38)
{
ccCArrayAppendValue(m_pUnknown3c, pDelegate);
m_bUnknown39 = true;
}
else
{
forceAddDelegate(pDelegate);
}
}
void CCKeyboardDispatcher::removeDelegate(CCKeyboardDelegate* pDelegate)
{
if (!pDelegate) return;
if (m_bUnknown38)
{
ccCArrayAppendValue(m_pUnknown40, pDelegate);
m_bUnknown3a = true;
}
else
{
forceRemoveDelegate(pDelegate);
}
}
void CCKeyboardDispatcher::forceAddDelegate(CCKeyboardDelegate* pDelegate)
{
if (auto handler = CCKeyboardHandler::handlerWithDelegate(pDelegate))
{
m_pDelegates->addObject(handler);
}
}
void CCKeyboardDispatcher::forceRemoveDelegate(CCKeyboardDelegate* pDelegate)
{
CCObject* handler = nullptr;
CCARRAY_FOREACH(m_pDelegates, handler)
{
if (pDelegate && pDelegate == static_cast<CCKeyboardHandler*>(handler)->getDelegate())
{
m_pDelegates->removeObject(handler, true);
}
}
}
enumKeyCodes CCKeyboardDispatcher::convertKeyCode(enumKeyCodes key)
{
switch (key)
{
case enumKeyCodes::KEY_ArrowUp:
return enumKeyCodes::KEY_Up;
case enumKeyCodes::KEY_ArrowDown:
return enumKeyCodes::KEY_Down;
case enumKeyCodes::KEY_ArrowLeft:
return enumKeyCodes::KEY_Left;
case enumKeyCodes::KEY_ArrowRight:
return enumKeyCodes::KEY_Right;
default:
return key;
}
}
bool CCKeyboardDispatcher::dispatchKeyboardMSG(enumKeyCodes key, bool isKeyDown, bool isKeyRepeat)
{
if (isKeyRepeat && m_bBlockRepeat)
{
return false;
}
enumKeyCodes convertedKey = convertKeyCode(key);
switch (key)
{
case KEY_Shift:
case KEY_Control:
case KEY_Alt:
case CONTROLLER_Back:
return false;
default:
break;
}
m_bUnknown38 = true;
CCObject* handler;
CCARRAY_FOREACH(m_pDelegates, handler)
{
auto keyboardHandler = static_cast<CCKeyboardHandler*>(handler);
if (!handler)
{
break;
}
auto delegate = keyboardHandler->getDelegate();
if (isKeyDown)
{
delegate->keyDown(key);
}
else
{
delegate->keyUp(key);
}
}
m_bUnknown38 = false;
if (m_bUnknown3a)
{
m_bUnknown3a = false;
void* delegate;
CCARRAYDATA_FOREACH(m_pUnknown40, delegate)
{
forceRemoveDelegate(static_cast<CCKeyboardDelegate*>(delegate));
}
ccCArrayRemoveAllValues(m_pUnknown40);
}
if (!m_bUnknown39)
return true;
m_bUnknown39 = false;
void* delegate;
CCARRAYDATA_FOREACH(m_pUnknown3c, delegate)
{
forceAddDelegate(static_cast<CCKeyboardDelegate*>(delegate));
}
ccCArrayRemoveAllValues(m_pUnknown3c);
return true;
}
const char* CCKeyboardDispatcher::keyToString(enumKeyCodes key)
{
switch (key)
{
case KEY_Backspace: return "Backspace";
case KEY_Tab: return "Tab";
case KEY_Clear: return "Clear";
case KEY_Enter: return "Enter";
case KEY_Shift: return "Shift";
case KEY_Control: return "Control";
case KEY_Alt: return "Alt";
case KEY_Pause: return "Pause";
case KEY_CapsLock: return "CapsLock";
case KEY_Escape: return "Escape";
case KEY_Space: return "Space";
case KEY_PageUp: return "PageUp";
case KEY_PageDown: return "PageDown";
case KEY_End: return "End";
case KEY_Home: return "Home";
case KEY_Left: return "Left";
case KEY_Up: return "Up";
case KEY_Right: return "Right";
case KEY_Down: return "Down";
case KEY_Select: return "Select";
case KEY_Print: return "Print";
case KEY_Execute: return "Execute";
case KEY_PrintScreen: return "PrintScreen";
case KEY_Insert: return "Insert";
case KEY_Delete: return "Delete";
case KEY_Help: return "Help";
case KEY_Zero: return "Zero";
case KEY_One: return "One";
case KEY_Two: return "Two";
case KEY_Three: return "Three";
case KEY_Four: return "Four";
case KEY_Five: return "Five";
case KEY_Six: return "Six";
case KEY_Seven: return "Seven";
case KEY_Eight: return "Eight";
case KEY_Nine: return "Nine";
case KEY_A: return "A";
case KEY_B: return "B";
case KEY_C: return "C";
case KEY_D: return "D";
case KEY_E: return "E";
case KEY_F: return "F";
case KEY_G: return "G";
case KEY_H: return "H";
case KEY_I: return "I";
case KEY_J: return "J";
case KEY_K: return "K";
case KEY_L: return "L";
case KEY_M: return "M";
case KEY_N: return "N";
case KEY_O: return "O";
case KEY_P: return "P";
case KEY_Q: return "Q";
case KEY_R: return "R";
case KEY_S: return "S";
case KEY_T: return "T";
case KEY_U: return "U";
case KEY_V: return "V";
case KEY_W: return "W";
case KEY_X: return "X";
case KEY_Y: return "Y";
case KEY_Z: return "Z";
case KEY_LeftWindowsKey: return "LeftWindowsKey";
case KEY_RightWindowsKey: return "RightWindowsKey";
case KEY_ApplicationsKey: return "ApplicationsKey";
case KEY_Sleep: return "Sleep";
case KEY_NumPad0: return "NumPad0";
case KEY_NumPad1: return "NumPad1";
case KEY_NumPad2: return "NumPad2";
case KEY_NumPad3: return "NumPad3";
case KEY_NumPad4: return "NumPad4";
case KEY_NumPad5: return "NumPad5";
case KEY_NumPad6: return "NumPad6";
case KEY_NumPad7: return "NumPad7";
case KEY_NumPad8: return "NumPad8";
case KEY_NumPad9: return "NumPad9";
case KEY_Multiply: return "Multiply";
case KEY_Add: return "Add";
case KEY_Seperator: return "Seperator";
case KEY_Subtract: return "Subtract";
case KEY_Decimal: return "Decimal";
case KEY_Divide: return "Divide";
case KEY_F1: return "F1";
case KEY_F2: return "F2";
case KEY_F3: return "F3";
case KEY_F4: return "F4";
case KEY_F5: return "F5";
case KEY_F6: return "F6";
case KEY_F7: return "F7";
case KEY_F8: return "F8";
case KEY_F9: return "F9";
case KEY_F10: return "F10";
case KEY_F11: return "F11";
case KEY_F12: return "F12";
case KEY_F13: return "F13";
case KEY_F14: return "F14";
case KEY_F15: return "F15";
case KEY_F16: return "F16";
case KEY_F17: return "F17";
case KEY_F18: return "F18";
case KEY_F19: return "F19";
case KEY_F20: return "F20";
case KEY_F21: return "F21";
case KEY_F22: return "F22";
case KEY_F23: return "F23";
case KEY_F24: return "F24";
case KEY_Numlock: return "Numlock";
case KEY_ScrollLock: return "ScrollLock";
case KEY_LeftShift: return "LeftShift";
case KEY_RightShift: return "RightShift";
case KEY_LeftControl: return "LeftControl";
case KEY_RightContol: return "RightContol";
case KEY_LeftMenu: return "LeftMenu";
case KEY_RightMenu: return "RightMenu";
case KEY_BrowserBack: return "BrowserBack";
case KEY_BrowserForward: return "BrowserForward";
case KEY_BrowserRefresh: return "BrowserRefresh";
case KEY_BrowserStop: return "BrowserStop";
case KEY_BrowserSearch: return "BrowserSearch";
case KEY_BrowserFavorites: return "BrowserFavorites";
case KEY_BrowserHome: return "BrowserHome";
case KEY_VolumeMute: return "VolumeMute";
case KEY_VolumeDown: return "VolumeDown";
case KEY_VolumeUp: return "VolumeUp";
case KEY_NextTrack: return "NextTrack";
case KEY_PreviousTrack: return "PreviousTrack";
case KEY_StopMedia: return "StopMedia";
case KEY_PlayPause: return "PlayPause";
case KEY_LaunchMail: return "LaunchMail";
case KEY_SelectMedia: return "SelectMedia";
case KEY_LaunchApp1: return "LaunchApp1";
case KEY_LaunchApp2: return "LaunchApp2";
case KEY_OEM1: return "OEM1";
case KEY_OEMPlus: return "OEMPlus";
case KEY_OEMComma: return "OEMComma";
case KEY_OEMMinus: return "OEMMinus";
case KEY_OEMPeriod: return "OEMPeriod";
case KEY_OEM2: return "OEM2";
case KEY_OEM3: return "OEM3";
case KEY_OEM4: return "OEM4";
case KEY_OEM5: return "OEM5";
case KEY_OEM6: return "OEM6";
case KEY_OEM7: return "OEM7";
case KEY_OEM8: return "OEM8";
case KEY_OEM102: return "OEM102";
case KEY_Process: return "Process";
case KEY_Packet: return "Packet";
case KEY_Attn: return "Attn";
case KEY_CrSel: return "CrSel";
case KEY_ExSel: return "ExSel";
case KEY_EraseEOF: return "EraseEOF";
case KEY_Play: return "Play";
case KEY_Zoom: return "Zoom";
case KEY_PA1: return "PA1";
case KEY_OEMClear: return "OEMClear";
case KEY_ArrowUp: return "ArrowUp";
case KEY_ArrowDown: return "ArrowDown";
case KEY_ArrowLeft: return "ArrowLeft";
case KEY_ArrowRight: return "ArrowRight";
case CONTROLLER_A: return "Controller_A";
case CONTROLLER_B: return "Controller_B";
case CONTROLLER_Y: return "Controller_Y";
case CONTROLLER_X: return "Controller_X";
case CONTROLLER_Start: return "Controller_Start";
case CONTROLLER_Back: return "Controller_Back";
case CONTROLLER_RB: return "Controller_RB";
case CONTROLLER_LB: return "Controller_LB";
case CONTROLLER_RT: return "Controller_RT";
case CONTROLLER_LT: return "Controller_LT";
case CONTROLLER_Up: return "Controller_Up";
case CONTROLLER_Down: return "Controller_Down";
case CONTROLLER_Left: return "Controller_Left";
case CONTROLLER_Right: return "Controller_Right";
// Geode Additions
case CONTROLLER_LTHUMBSTICK_UP: return "Controller_LTHUMBSTICK_UP";
case CONTROLLER_LTHUMBSTICK_DOWN: return "Controller_LTHUMBSTICK_DOWN";
case CONTROLLER_LTHUMBSTICK_LEFT: return "Controller_LTHUMBSTICK_LEFT";
case CONTROLLER_LTHUMBSTICK_RIGHT: return "Controller_LTHUMBSTICK_RIGHT";
case CONTROLLER_RTHUMBSTICK_UP: return "Controller_RTHUMBSTICK_UP";
case CONTROLLER_RTHUMBSTICK_DOWN: return "Controller_RTHUMBSTICK_DOWN";
case CONTROLLER_RTHUMBSTICK_LEFT: return "Controller_RTHUMBSTICK_LEFT";
case CONTROLLER_RTHUMBSTICK_RIGHT: return "Controller_RTHUMBSTICK_RIGHT";
case KEY_GraveAccent: return "`";
case KEY_OEMEqual: return "=";
case KEY_LeftBracket: return "[";
case KEY_RightBracket: return "]";
case KEY_Backslash: return "\\";
case KEY_Semicolon: return ";";
case KEY_Apostrophe: return "'";
case KEY_Slash: return "/";
case KEY_NumEnter: return "=";
case KEY_World1: return "INTL-1";
case KEY_World2: return "INTL-2";
case MOUSE_4: return "Mouse 4";
case MOUSE_5: return "Mouse 5";
case MOUSE_6: return "Mouse 6";
case MOUSE_7: return "Mouse 7";
case MOUSE_8: return "Mouse 8";
case KEY_None:
case KEY_Unknown:
default:
return nullptr;
}
}
void CCKeyboardDispatcher::updateModifierKeys(bool shft, bool ctrl, bool alt, bool cmd)
{
m_bShiftPressed = shft;
m_bAltPressed = alt;
m_bControlPressed = ctrl || cmd;
m_bCommandPressed = cmd;
}
#endif

View file

@ -0,0 +1,39 @@
#include <cocos2d.h>
using namespace cocos2d;
#ifdef GEODE_IS_IOS
CCKeyboardHandler::~CCKeyboardHandler() {}
CCKeyboardDelegate* CCKeyboardHandler::getDelegate()
{
return m_pDelegate;
}
CCKeyboardHandler* CCKeyboardHandler::handlerWithDelegate(CCKeyboardDelegate* pDelegate)
{
CCKeyboardHandler* handler = new CCKeyboardHandler();
if (handler->initWithDelegate(pDelegate))
{
handler->autorelease();
return handler;
}
handler->release();
return nullptr;
}
bool CCKeyboardHandler::initWithDelegate(CCKeyboardDelegate* pDelegate)
{
m_pDelegate = pDelegate;
return true;
}
void CCKeyboardHandler::setDelegate(CCKeyboardDelegate* pDelegate)
{
m_pDelegate = pDelegate;
}
#endif

View file

@ -1,22 +1,25 @@
#include <Geode/DefaultInclude.hpp>
#import <Cocoa/Cocoa.h>
#include <objc/runtime.h>
#include <Carbon/Carbon.h>
#ifdef GEODE_IS_IOS
#include <AppKit/AppKit.h>
#endif
#include <Geode/cocos/robtop/keyboard_dispatcher/CCKeyboardDispatcher.h>
#include <Geode/cocos/robtop/keyboard_dispatcher/CCKeyboardDelegate.h>
#ifdef GEODE_IS_MACOS
#include <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
#import <Geode/cocos/platform/mac/EAGLView.h>
#include <Geode/cocos/text_input_node/CCIMEDispatcher.h>
#include <Geode/modify/Modify.hpp>
#include <Geode/modify/CCKeyboardDispatcher.hpp>
#else
#include <UIKit/UIKit.h>
#endif
#include <objc/runtime.h>
using namespace geode::prelude;
#ifdef GEODE_IS_MACOS
// https://github.com/SpaghettDev/BetterInputs/blob/44f249cd94f4cc19fca4de570dfab28f4efa3db8/src/platform/macos.mm#L121
// we use this instead of [event keyCode] because the returned value of keyCode for letters is keyboard locale-specific
int normalizedCodeFromEvent(NSEvent* event) {
@ -204,7 +207,7 @@ void mouseUpExecHook(EAGLView* self, SEL sel, NSEvent* event) {
CCKeyboardDispatcher::get()->dispatchKeyboardMSG(keyCode, false, false);
}
#ifdef GEODE_IS_MACOS
class $modify(CCKeyboardDispatcher) {
GEODE_FORWARD_COMPAT_DISABLE_HOOKS("CCKeyboardDispatcher new keys")
@ -251,6 +254,153 @@ class $modify(CCKeyboardDispatcher) {
}
}
};
#else
NSMutableDictionary<NSNumber*, NSNumber*>* keyStates;
enumKeyCodes keyCodeFromKeyCommand(UIKey* key) {
switch (key.keyCode) {
case UIKeyboardHIDUsageKeyboard0: return enumKeyCodes::KEY_Zero;
case UIKeyboardHIDUsageKeyboard1: return enumKeyCodes::KEY_One;
case UIKeyboardHIDUsageKeyboard2: return enumKeyCodes::KEY_Two;
case UIKeyboardHIDUsageKeyboard3: return enumKeyCodes::KEY_Three;
case UIKeyboardHIDUsageKeyboard4: return enumKeyCodes::KEY_Four;
case UIKeyboardHIDUsageKeyboard5: return enumKeyCodes::KEY_Five;
case UIKeyboardHIDUsageKeyboard6: return enumKeyCodes::KEY_Six;
case UIKeyboardHIDUsageKeyboard7: return enumKeyCodes::KEY_Seven;
case UIKeyboardHIDUsageKeyboard8: return enumKeyCodes::KEY_Eight;
case UIKeyboardHIDUsageKeyboard9: return enumKeyCodes::KEY_Nine;
case UIKeyboardHIDUsageKeyboardReturnOrEnter: return enumKeyCodes::KEY_Enter;
case UIKeyboardHIDUsageKeyboardEscape: return enumKeyCodes::KEY_Escape;
case UIKeyboardHIDUsageKeyboardDeleteOrBackspace: return enumKeyCodes::KEY_Backspace; // really..
case UIKeyboardHIDUsageKeyboardTab: return enumKeyCodes::KEY_Tab;
case UIKeyboardHIDUsageKeyboardSpacebar: return enumKeyCodes::KEY_Space;
case UIKeyboardHIDUsageKeyboardHyphen: return enumKeyCodes::KEY_OEMMinus;
case UIKeyboardHIDUsageKeyboardEqualSign: return enumKeyCodes::KEY_Equal;
case UIKeyboardHIDUsageKeyboardOpenBracket: return enumKeyCodes::KEY_LeftBracket;
case UIKeyboardHIDUsageKeyboardCloseBracket: return enumKeyCodes::KEY_RightBracket;
case UIKeyboardHIDUsageKeyboardBackslash: return enumKeyCodes::KEY_Backslash;
// case UIKeyboardHIDUsageKeyboardNonUSPound: return enumKeyCodes::KEY_;
case UIKeyboardHIDUsageKeyboardSemicolon: return enumKeyCodes::KEY_Semicolon;
// case UIKeyboardHIDUsageKeyboardQuote: return enumKeyCodes::KEY_;
case UIKeyboardHIDUsageKeyboardGraveAccentAndTilde: return enumKeyCodes::KEY_GraveAccent;
case UIKeyboardHIDUsageKeyboardComma: return enumKeyCodes::KEY_OEMComma;
case UIKeyboardHIDUsageKeyboardPeriod: return enumKeyCodes::KEY_OEMPeriod;
case UIKeyboardHIDUsageKeyboardSlash: return enumKeyCodes::KEY_Slash;
case UIKeyboardHIDUsageKeyboardCapsLock: return enumKeyCodes::KEY_CapsLock;
case UIKeyboardHIDUsageKeyboardLeftAlt: return enumKeyCodes::KEY_Alt;
case UIKeyboardHIDUsageKeyboardLeftControl: return enumKeyCodes::KEY_LeftControl;
case UIKeyboardHIDUsageKeyboardLeftShift: return enumKeyCodes::KEY_LeftShift;
// case UIKeyboardHIDUsageKeyboardRightAlt: return enumKeyCodes::KEY_;
case UIKeyboardHIDUsageKeyboardRightControl: return enumKeyCodes::KEY_RightContol;
case UIKeyboardHIDUsageKeyboardRightShift: return enumKeyCodes::KEY_RightShift;
case UIKeyboardHIDUsageKeyboardF1: return enumKeyCodes::KEY_F1;
case UIKeyboardHIDUsageKeyboardF2: return enumKeyCodes::KEY_F2;
case UIKeyboardHIDUsageKeyboardF3: return enumKeyCodes::KEY_F3;
case UIKeyboardHIDUsageKeyboardF4: return enumKeyCodes::KEY_F4;
case UIKeyboardHIDUsageKeyboardF5: return enumKeyCodes::KEY_F5;
case UIKeyboardHIDUsageKeyboardF6: return enumKeyCodes::KEY_F6;
case UIKeyboardHIDUsageKeyboardF7: return enumKeyCodes::KEY_F7;
case UIKeyboardHIDUsageKeyboardF8: return enumKeyCodes::KEY_F8;
case UIKeyboardHIDUsageKeyboardF9: return enumKeyCodes::KEY_F9;
case UIKeyboardHIDUsageKeyboardF10: return enumKeyCodes::KEY_F10;
case UIKeyboardHIDUsageKeyboardF11: return enumKeyCodes::KEY_F11;
case UIKeyboardHIDUsageKeyboardF12: return enumKeyCodes::KEY_F12;
case UIKeyboardHIDUsageKeyboardF13: return enumKeyCodes::KEY_F13;
case UIKeyboardHIDUsageKeyboardF14: return enumKeyCodes::KEY_F14;
case UIKeyboardHIDUsageKeyboardF15: return enumKeyCodes::KEY_F15;
case UIKeyboardHIDUsageKeyboardF16: return enumKeyCodes::KEY_F16;
case UIKeyboardHIDUsageKeyboardF17: return enumKeyCodes::KEY_F17;
case UIKeyboardHIDUsageKeyboardF18: return enumKeyCodes::KEY_F18;
case UIKeyboardHIDUsageKeyboardF19: return enumKeyCodes::KEY_F19;
case UIKeyboardHIDUsageKeyboardF20: return enumKeyCodes::KEY_F20;
case UIKeyboardHIDUsageKeyboardF21: return enumKeyCodes::KEY_F21;
case UIKeyboardHIDUsageKeyboardF22: return enumKeyCodes::KEY_F22;
case UIKeyboardHIDUsageKeyboardF23: return enumKeyCodes::KEY_F23;
case UIKeyboardHIDUsageKeyboardF24: return enumKeyCodes::KEY_F24;
case UIKeyboardHIDUsageKeyboardScrollLock: return enumKeyCodes::KEY_ScrollLock;
case UIKeyboardHIDUsageKeyboardInsert: return enumKeyCodes::KEY_Insert;
case UIKeyboardHIDUsageKeyboardHome: return enumKeyCodes::KEY_Home;
case UIKeyboardHIDUsageKeyboardPageUp: return enumKeyCodes::KEY_PageUp;
case UIKeyboardHIDUsageKeyboardDeleteForward: return enumKeyCodes::KEY_Delete;
case UIKeyboardHIDUsageKeyboardEnd: return enumKeyCodes::KEY_End;
case UIKeyboardHIDUsageKeyboardPageDown: return enumKeyCodes::KEY_PageDown;
case UIKeyboardHIDUsageKeyboardRightArrow: return enumKeyCodes::KEY_ArrowRight;
case UIKeyboardHIDUsageKeyboardLeftArrow: return enumKeyCodes::KEY_ArrowLeft;
case UIKeyboardHIDUsageKeyboardDownArrow: return enumKeyCodes::KEY_ArrowDown;
case UIKeyboardHIDUsageKeyboardUpArrow: return enumKeyCodes::KEY_ArrowUp;
case UIKeyboardHIDUsageKeypadNumLock: return enumKeyCodes::KEY_Numlock;
case UIKeyboardHIDUsageKeypadSlash: return enumKeyCodes::KEY_Slash;
case UIKeyboardHIDUsageKeypadAsterisk: return enumKeyCodes::KEY_Multiply;
case UIKeyboardHIDUsageKeypadHyphen: return enumKeyCodes::KEY_OEMMinus;
case UIKeyboardHIDUsageKeypadPlus: return enumKeyCodes::KEY_OEMPlus;
case UIKeyboardHIDUsageKeypadEnter: return enumKeyCodes::KEY_NumEnter;
case UIKeyboardHIDUsageKeypad0: return enumKeyCodes::KEY_NumPad0;
case UIKeyboardHIDUsageKeypad1: return enumKeyCodes::KEY_NumPad1;
case UIKeyboardHIDUsageKeypad2: return enumKeyCodes::KEY_NumPad2;
case UIKeyboardHIDUsageKeypad3: return enumKeyCodes::KEY_NumPad3;
case UIKeyboardHIDUsageKeypad4: return enumKeyCodes::KEY_NumPad4;
case UIKeyboardHIDUsageKeypad5: return enumKeyCodes::KEY_NumPad5;
case UIKeyboardHIDUsageKeypad6: return enumKeyCodes::KEY_NumPad6;
case UIKeyboardHIDUsageKeypad7: return enumKeyCodes::KEY_NumPad7;
case UIKeyboardHIDUsageKeypad8: return enumKeyCodes::KEY_NumPad8;
case UIKeyboardHIDUsageKeypad9: return enumKeyCodes::KEY_NumPad9;
case UIKeyboardHIDUsageKeypadPeriod: return enumKeyCodes::KEY_OEMPeriod;
// case UIKeyboardHIDUsageKeyboardNonUSBackslash: return enumKeyCodes::KEY_;
case UIKeyboardHIDUsageKeyboardApplication: return enumKeyCodes::KEY_ApplicationsKey;
// case UIKeyboardHIDUsageKeyboardPower: return enumKeyCodes::KEY_;
case UIKeyboardHIDUsageKeypadEqualSign: return enumKeyCodes::KEY_OEMEqual;
// case UIKeyboardHIDUsageKeyboardMenu: return enumKeyCodes::KEY_;
case UIKeyboardHIDUsageKeyboardMute: return enumKeyCodes::KEY_VolumeMute;
case UIKeyboardHIDUsageKeyboardVolumeUp: return enumKeyCodes::KEY_VolumeUp;
case UIKeyboardHIDUsageKeyboardVolumeDown: return enumKeyCodes::KEY_VolumeDown;
case UIKeyboardHIDUsageKeypadComma: return enumKeyCodes::KEY_OEMComma;
default:
return enumKeyCodes::KEY_Unknown;
}
}
bool handleKeyEvent(UIKey* key, bool isKeyDown, bool isARepeat) {
enumKeyCodes keyCode = keyCodeFromKeyCommand(key);
if (keyCode == enumKeyCodes::KEY_Unknown) return false;
keyStates[@(keyCode)] = @(isKeyDown);
CCKeyboardDispatcher::get()->updateModifierKeys(
[key modifierFlags] & UIKeyModifierShift,
[key modifierFlags] & UIKeyModifierControl,
[key modifierFlags] & UIKeyModifierAlternate,
[key modifierFlags] & UIKeyModifierCommand
);
CCKeyboardDispatcher::get()->dispatchKeyboardMSG(keyCode, keyStates[@(keyCode)], isARepeat);
return true;
}
void pressesBegan(id self, SEL _cmd, NSSet<UIPress*>* presses, UIPressesEvent* event) {
for (UIPress* press in presses) {
UIKey* key = press.key;
UIKeyboardHIDUsage keyCode = key.keyCode;
if (keyStates[@(keyCode)] == nil)
keyStates[@(keyCode)] = @(NO);
handleKeyEvent(key, true, [keyStates[@(keyCode)] boolValue]);
}
}
void pressesEnded(id self, SEL _cmd, NSSet<UIPress*>* presses, UIPressesEvent* event) {
for (UIPress* press in presses) {
UIKey* key = press.key;
UIKeyboardHIDUsage keyCode = key.keyCode;
handleKeyEvent(key, false, false);
}
}
#endif
@ -267,28 +417,14 @@ __attribute__((constructor)) void initialize_newKeyboardMSGKeysHooks() {
HOOK_OBJC_METHOD(eaglView, mouseDownExec);
HOOK_OBJC_METHOD(eaglView, mouseUpExec);
#elif defined(GEODE_IS_IOS)
@autoreleasepool
{
[NSEvent addGlobalMonitorForEventsMatchingMask:NSEventTypeKeyDown handler: ^(NSEvent* event) {
keyDownExecHook(nullptr, 0, event);
}];
[NSEvent addGlobalMonitorForEventsMatchingMask:NSEventTypeKeyUp handler: ^(NSEvent* event) {
keyUpExecHook(nullptr, 0, event);
}];
[NSEvent
addGlobalMonitorForEventsMatchingMask:NSEventTypeLeftMouseDown | NSEventTypeRightMouseDown | NSEventTypeOtherMouseDown
handler: ^(NSEvent* event) {
mouseDownExecHook(nullptr, 0, event);
}
];
[NSEvent
addGlobalMonitorForEventsMatchingMask:NSEventTypeLeftMouseUp | NSEventTypeRightMouseUp | NSEventTypeOtherMouseUp
handler: ^(NSEvent* event) {
mouseUpExecHook(nullptr, 0, event);
}
];
#else
@autoreleasepool {
keyStates = [NSMutableDictionary dictionary];
}
auto rootViewController = objc_getClass("RootViewController");
class_addMethod(rootViewController, @selector(pressesBegan:withEvent:), (IMP)pressesBegan, "v@:@@");
class_addMethod(rootViewController, @selector(pressesEnded:withEvent:), (IMP)pressesEnded, "v@:@@");
#endif
}

View file

@ -50,6 +50,12 @@ $execute {
(void) Mod::get()->patch(reinterpret_cast<void*>(addr), {
0x48, 0x90, // nop (skip if statement)
});
#elif defined(GEODE_IS_IOS)
auto addr = base::get() + 0x138390;
(void) Mod::get()->patch(reinterpret_cast<void*>(addr), {
0x1f, 0x20, 0x03, 0xd5 // nop (skip if statement)
});
#endif
#else
#pragma message("Unsupported GD version!")

View file

@ -11,6 +11,9 @@ $execute {
#if defined(GEODE_IS_MACOS) && GEODE_COMP_GD_VERSION != 22074
#error "Unsupported version for macOS dynamic cast fix, please update the addresses"
#endif
#if defined(GEODE_IS_IOS) && GEODE_COMP_GD_VERSION != 22074
#error "Unsupported version for iOS dynamic cast fix, please update the addresses"
#endif
#if defined(GEODE_IS_INTEL_MAC)
void* dynamicCastAddr = reinterpret_cast<void*>(base::get() + 0x7ba1d8);
@ -25,5 +28,8 @@ $execute {
(void)Mod::get()->hook(dynamicCastAddr, &cast::typeinfoCastInternal, "__dynamic_cast");
dlclose(handle);
#elif defined(GEODE_IS_IOS)
void* addr = reinterpret_cast<void*>(base::get() + 0x769208);
(void) Mod::get()->patch(addr, geode::toBytes(&cast::typeinfoCastInternal));
#endif
}

View file

@ -1,5 +1,8 @@
#include <Geode/Geode.hpp>
// ios does not link to fmod.
#ifndef GEODE_IS_IOS
using namespace geode::prelude;
auto g_systemInitialized = false;
@ -63,3 +66,5 @@ struct AndroidFMODFix : Modify<AndroidFMODFix, FMODAudioEngine> {
}
};
*/
#endif

View file

@ -1,51 +0,0 @@
#include <loader/LoaderImpl.hpp>
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <loader/ModImpl.hpp>
#include <iostream>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
// @geode-ignore(geode-alternative)
std::cout << title << ": " << info << std::endl;
}
void Loader::Impl::logConsoleMessageWithSeverity(std::string const& msg, Severity severity) {
if (m_platformConsoleOpen) {
// @geode-ignore(geode-alternative)
std::cout << msg << "\n" << std::flush;
}
}
void Loader::Impl::openPlatformConsole() {
std::filesystem::path(getpwuid(getuid())->pw_dir);
freopen(std::filesystem::path(dirs::getGeodeDir() / "geode_log.txt").string().c_str(), "w", stdout);
m_platformConsoleOpen = true;
}
void Loader::Impl::closePlatformConsole() {}
void Loader::Impl::postIPCReply(
void* rawPipeHandle, std::string const& replyID, matjson::Value const& data
) {}
void Loader::Impl::setupIPC() {
#warning "Set up pipes or smth for this platform"
log::warning("IPC is not supported on this platform");
}
bool Loader::Impl::userTriedToLoadDLLs() const {
return false;
}
bool Loader::Impl::supportsLaunchArguments() const {
return false;
}
std::string Loader::Impl::getLaunchCommand() const {
return std::string(); // Empty
}

View file

@ -0,0 +1,89 @@
#include <loader/LoaderImpl.hpp>
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <loader/ModImpl.hpp>
#include <loader/IPC.hpp>
#include <loader/console.hpp>
#include <iostream>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#import <Foundation/Foundation.h>
#include <loader/LogImpl.hpp>
using namespace geode::prelude;
bool s_isOpen = false;
void console::messageBox(char const* title, std::string const& info, Severity severity) {
// TODO: implement
console::log(info, severity);
}
void console::log(std::string const& msg, Severity severity) {
NSLog(@"%s", msg.c_str());
if (s_isOpen) {
int colorcode = 0;
switch (severity) {
case Severity::Debug: colorcode = 36; break;
case Severity::Info: colorcode = 34; break;
case Severity::Warning: colorcode = 33; break;
case Severity::Error: colorcode = 31; break;
default: colorcode = 35; break;
}
auto newMsg = "\033[1;" + std::to_string(colorcode) + "m" + msg.substr(0, 8) + "\033[0m" + msg.substr(8);
std::cout << newMsg << "\n" << std::flush;
}
}
void console::openIfClosed() {
if (s_isOpen) return;
std::filesystem::path(getpwuid(getuid())->pw_dir);
freopen(std::filesystem::path(dirs::getGeodeDir() / "geode_log.txt").string().c_str(), "w", stdout);
s_isOpen = true;
}
void console::setup() {}
// void Loader::Impl::postIPCReply(
// void* rawPipeHandle, std::string const& replyID, matjson::Value const& data
// ) {}
void geode::ipc::setup() {
#warning "Set up pipes or smth for this platform"
log::warn("IPC is not supported on this platform");
}
bool Loader::Impl::userTriedToLoadDLLs() const {
return false;
}
bool Loader::Impl::supportsLaunchArguments() const {
return true;
}
std::string Loader::Impl::getLaunchCommand() const {
return (getenv("LAUNCHARGS")) ? getenv("LAUNCHARGS") : std::string();
}
void Loader::Impl::addNativeBinariesPath(std::filesystem::path const& path) {
log::warn("LoaderImpl::addNativeBinariesPath not implement on this platform, not adding path {}", path.string());
}
std::string Loader::Impl::getGameVersion() {
NSBundle* mainBundle = [NSBundle mainBundle];
NSDictionary* infoDictionary = [mainBundle infoDictionary];
std::string version = [infoDictionary[@"CFBundleShortVersionString"] UTF8String];
// temporary workaround - the bundle version is 2.207 although the actual game is 2.2074
if (version == "2.207") return "2.2074";
return version;
}

View file

@ -17,7 +17,7 @@ T findSymbolOrMangled(void* dylib, char const* name, char const* mangled) {
Result<> Mod::Impl::loadPlatformBinary() {
auto dylib =
dlopen((m_tempDirName / m_info.binaryName()).string().c_str(), RTLD_LAZY);
dlopen((m_tempDirName / m_metadata.getBinaryName()).string().c_str(), RTLD_LAZY);
if (dylib) {
if (m_platformInfo) {
delete m_platformInfo;
@ -39,15 +39,3 @@ Result<> Mod::Impl::loadPlatformBinary() {
std::string err = (char const*)dlerror();
return Err("Unable to load the DYLIB: dlerror returned (" + err + ")");
}
Result<> Mod::Impl::unloadPlatformBinary() {
auto dylib = m_platformInfo->m_dylib;
delete m_platformInfo;
m_platformInfo = nullptr;
if (dlclose(dylib) == 0) {
return Ok();
}
else {
return Err("Unable to free library");
}
}

View file

@ -1,13 +0,0 @@
#include <crashlog.hpp>
bool crashlog::setupPlatformHandler() {
return false;
}
bool crashlog::didLastLaunchCrash() {
return false;
}
std::filesystem::path crashlog::getCrashLogDirectory() {
return "";
}

View file

@ -0,0 +1,423 @@
// this is mostly copied from macos
#include <crashlog.hpp>
#include <Geode/utils/string.hpp>
#include <array>
#include <thread>
#include <execinfo.h>
#include <dlfcn.h>
#include <cxxabi.h>
#include <algorithm>
#include <mach-o/dyld_images.h>
#include <mach-o/dyld.h>
#include <unistd.h>
#include <fcntl.h>
#import <Foundation/Foundation.h>
using namespace geode::prelude;
// https://gist.github.com/jvranish/4441299
static constexpr size_t FRAME_SIZE = 64;
static int s_signal = 0;
static siginfo_t* s_siginfo = nullptr;
static ucontext_t* s_context = nullptr;
static size_t s_backtraceSize = 0;
static std::array<void*, FRAME_SIZE> s_backtrace;
static int s_pipe[2];
static std::string_view getSignalCodeString() {
switch(s_signal) {
case SIGSEGV: return "SIGSEGV: Segmentation Fault";
case SIGINT: return "SIGINT: Interactive attention signal, (usually ctrl+c)";
case SIGFPE:
switch(s_siginfo->si_code) {
case FPE_INTDIV: return "SIGFPE: (integer divide by zero)";
case FPE_INTOVF: return "SIGFPE: (integer overflow)";
case FPE_FLTDIV: return "SIGFPE: (floating-point divide by zero)";
case FPE_FLTOVF: return "SIGFPE: (floating-point overflow)";
case FPE_FLTUND: return "SIGFPE: (floating-point underflow)";
case FPE_FLTRES: return "SIGFPE: (floating-point inexact result)";
case FPE_FLTINV: return "SIGFPE: (floating-point invalid operation)";
case FPE_FLTSUB: return "SIGFPE: (subscript out of range)";
default: return "SIGFPE: Arithmetic Exception";
}
case SIGILL:
switch(s_siginfo->si_code) {
case ILL_ILLOPC: return "SIGILL: (illegal opcode)";
case ILL_ILLOPN: return "SIGILL: (illegal operand)";
case ILL_ILLADR: return "SIGILL: (illegal addressing mode)";
case ILL_ILLTRP: return "SIGILL: (illegal trap)";
case ILL_PRVOPC: return "SIGILL: (privileged opcode)";
case ILL_PRVREG: return "SIGILL: (privileged register)";
case ILL_COPROC: return "SIGILL: (coprocessor error)";
case ILL_BADSTK: return "SIGILL: (internal stack error)";
default: return "SIGILL: Illegal Instruction";
}
case SIGTERM: return "SIGTERM: a termination request was sent to the program";
case SIGABRT: return "SIGABRT: usually caused by an abort() or assert()";
case SIGBUS: return "SIGBUS: Bus error (bad memory access)";
default: return "Unknown signal code";
}
}
static std::string getImageName(struct dyld_image_info const* image) {
if (image == nullptr) {
return "<Unknown>";
}
std::string imageName = image->imageFilePath;
if (imageName.empty()) {
imageName = "<Unknown>";
}
return imageName;
}
// https://stackoverflow.com/questions/28846503/getting-sizeofimage-and-entrypoint-of-dylib-module
size_t getImageSize(struct mach_header_64 const* header) {
if (header == nullptr) {
return 0;
}
size_t sz = sizeof(struct mach_header_64); // Size of the header
sz += header->sizeofcmds; // Size of the load commands
auto lc = (struct load_command const*) (header + 1);
for (uint32_t i = 0; i < header->ncmds; i++) {
if (lc->cmd == LC_SEGMENT) {
sz += ((struct segment_command_64 const*) lc)->vmsize; // Size of segments
}
lc = (struct load_command const*) ((char *) lc + lc->cmdsize);
}
return sz;
}
static std::vector<struct dyld_image_info const*> getAllImages() {
std::vector<struct dyld_image_info const*> images;
struct task_dyld_info dyldInfo;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
if (task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&dyldInfo, &count) == KERN_SUCCESS) {
struct dyld_all_image_infos* imageInfos = (struct dyld_all_image_infos*)dyldInfo.all_image_info_addr;
for (size_t i = 0; i < imageInfos->infoArrayCount; ++i) {
images.push_back(&imageInfos->infoArray[i]);
}
}
return images;
}
static struct dyld_image_info const* imageFromAddress(void const* addr) {
if (addr == nullptr) {
return nullptr;
}
auto loadedImages = getAllImages();
std::sort(loadedImages.begin(), loadedImages.end(), [](auto const a, auto const b) {
return (uintptr_t)a->imageLoadAddress < (uintptr_t)b->imageLoadAddress;
});
auto iter = std::upper_bound(loadedImages.begin(), loadedImages.end(), addr, [](auto const addr, auto const image) {
return (uintptr_t)addr < (uintptr_t)image->imageLoadAddress;
});
if (iter == loadedImages.begin()) {
return nullptr;
}
--iter;
auto image = *iter;
// auto imageSize = getImageSize((struct mach_header_64 const*)image->imageLoadAddress);
auto imageAddress = (uintptr_t)image->imageLoadAddress;
if ((uintptr_t)addr >= imageAddress/* && (uintptr_t)addr < imageAddress + imageSize*/) {
return image;
}
return nullptr;
}
static Mod* modFromAddress(void const* addr) {
if (addr == nullptr) {
return nullptr;
}
auto image = imageFromAddress(addr);
if (image == nullptr) {
return nullptr;
}
std::filesystem::path imagePath = getImageName(image);
if (!std::filesystem::exists(imagePath)) {
return nullptr;
}
auto geodePath = dirs::getGeodeDir() / "Geode.ios.dylib";
if (imagePath.filename() == geodePath.filename()) {
return Mod::get();
}
for (auto& mod : Loader::get()->getAllMods()) {
if (!mod->isEnabled() || !std::filesystem::exists(mod->getBinaryPath())) {
continue;
}
if (std::filesystem::equivalent(imagePath, mod->getBinaryPath())) {
return mod;
}
}
return nullptr;
}
static std::string getInfo(void* address, Mod* faultyMod) {
std::stringstream stream;
stream << "Faulty Lib: " << getImageName(imageFromAddress(address)) << "\n";
stream << "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n";
stream << "Instruction Address: " << address << "\n";
stream << "Signal Code: " << std::hex << s_signal << " (" << getSignalCodeString() << ")" << std::dec << "\n";
return stream.str();
}
extern "C" void signalHandler(int signal, siginfo_t* signalInfo, void* vcontext) {
/*auto context = reinterpret_cast<ucontext_t*>(vcontext);
s_backtraceSize = backtrace(s_backtrace.data(), FRAME_SIZE);
// for some reason this is needed, dont ask me why
s_backtrace[2] = reinterpret_cast<void*>(context->uc_mcontext->__ss.__pc);
if (s_backtraceSize < FRAME_SIZE) {
s_backtrace[s_backtraceSize] = nullptr;
}*/
s_signal = signal;
s_siginfo = signalInfo;
s_context = reinterpret_cast<ucontext_t*>(vcontext);
char buf = '1';
write(s_pipe[1], &buf, 1);
}
// https://stackoverflow.com/questions/8278691/how-to-fix-backtrace-line-number-error-in-c
std::string executeCommand(std::string const& cmd) {
std::stringstream stream;
std::array<char, 1024> buf;
if (FILE* ptr = popen(cmd.c_str(), "r")) {
while (fgets( buf.data(), buf.size(), ptr ) != NULL) {
stream << buf.data();
}
pclose(ptr);
}
return stream.str();
}
std::string addr2Line() {
std::stringstream stream;
stream << "atos -p " << getpid() << " ";
for (int i = 1; i < s_backtraceSize; ++i) {
stream << s_backtrace[i] << " ";
}
// std::cout << "command: " << stream.str() << std::endl;
return executeCommand(stream.str());
}
static std::string getStacktrace() {
std::stringstream stacktrace;
auto messages = backtrace_symbols(s_backtrace.data(), s_backtraceSize);
if (s_backtraceSize < FRAME_SIZE) {
messages[s_backtraceSize] = nullptr;
}
std::stringstream lines(addr2Line());
for (int i = 1; i < s_backtraceSize; ++i) {
auto message = std::string(messages[i]);
auto stream = std::stringstream(message);
int index;
std::string binary;
uintptr_t address;
std::string function;
uintptr_t offset;
std::string line;
stream >> index;
if (!lines.eof()) {
std::getline(lines, line);
}
std::getline(stream, binary);
auto cutoff = binary.find("0x");
stream = std::stringstream(binary.substr(cutoff));
binary = geode::utils::string::trim(binary.substr(0, cutoff));
stream >> std::hex >> address >> std::dec;
if (!line.empty()) {
// log::debug("address: {}", address);
auto image = imageFromAddress(reinterpret_cast<void*>(address));
// log::debug("image: {}", image);
stacktrace << " - " << std::showbase << std::hex;
if (image) {
auto baseAddress = image->imageLoadAddress;
auto imageName = getImageName(image);
stacktrace << imageName << " + " << (address - (uintptr_t)baseAddress);
}
else {
stacktrace << address;
}
stacktrace << std::dec;
stacktrace << ": " << line << "\n";
}
else {
std::getline(stream, function);
cutoff = function.find("+");
stream = std::stringstream(function.substr(cutoff));
stream >> offset;
function = geode::utils::string::trim(function.substr(0, cutoff));
{
int status;
auto demangle = abi::__cxa_demangle(function.c_str(), 0, 0, &status);
if (status == 0) {
function = demangle;
}
free(demangle);
}
stacktrace << "- " << binary;
stacktrace << " @ " << std::showbase << std::hex << address << std::dec;
stacktrace << " (" << function << " + " << offset << ")\n";
stacktrace << "- " << function << "\n";
}
}
free(messages);
return stacktrace.str();
}
static std::string getRegisters() {
std::stringstream registers;
auto context = s_context;
auto& ss = context->uc_mcontext->__ss;
// geez
registers << std::showbase << std::hex /*<< std::setfill('0') << std::setw(16) */;
registers << "x0: " << ss.__x[0] << "\n";
registers << "x1: " << ss.__x[1] << "\n";
registers << "x2: " << ss.__x[2] << "\n";
registers << "x3: " << ss.__x[3] << "\n";
registers << "x4: " << ss.__x[4] << "\n";
registers << "x5: " << ss.__x[5] << "\n";
registers << "x6: " << ss.__x[6] << "\n";
registers << "x7: " << ss.__x[7] << "\n";
registers << "x8: " << ss.__x[8] << "\n";
registers << "x9: " << ss.__x[9] << "\n";
registers << "x10: " << ss.__x[10] << "\n";
registers << "x11: " << ss.__x[11] << "\n";
registers << "x12: " << ss.__x[12] << "\n";
registers << "x13: " << ss.__x[13] << "\n";
registers << "x14: " << ss.__x[14] << "\n";
registers << "x15: " << ss.__x[15] << "\n";
registers << "x16: " << ss.__x[16] << "\n";
registers << "x17: " << ss.__x[17] << "\n";
registers << "x18: " << ss.__x[18] << "\n";
registers << "x19: " << ss.__x[19] << "\n";
registers << "x20: " << ss.__x[20] << "\n";
registers << "x21: " << ss.__x[21] << "\n";
registers << "x22: " << ss.__x[22] << "\n";
registers << "x23: " << ss.__x[23] << "\n";
registers << "x24: " << ss.__x[24] << "\n";
registers << "x25: " << ss.__x[25] << "\n";
registers << "x26: " << ss.__x[26] << "\n";
registers << "x27: " << ss.__x[27] << "\n";
registers << "x28: " << ss.__x[28] << "\n";
registers << "fp: " << ss.__fp << "\n";
registers << "lr: " << ss.__lr << "\n";
registers << "sp: " << ss.__sp << "\n";
registers << "pc: " << ss.__pc << "\n";
registers << "cpsr: " << ss.__cpsr << "\n";
return registers.str();
}
static void handlerThread() {
// no more mutex deadlocker
char buf;
while (read(s_pipe[0], &buf, 1) != 0) {
auto signalAddress = reinterpret_cast<void*>(s_context->uc_mcontext->__ss.__pc);
// as you can tell, i moved code from signalHandler to here
if (s_context) {
//s_backtraceSize = backtrace(s_backtrace.data(), FRAME_SIZE);
// i can't use 2 because then it'll show the actual stacktrace to be lower than what it actually is
s_backtrace[s_backtraceSize++] = signalAddress;
void* current_fp = reinterpret_cast<void*>(s_context->uc_mcontext->__ss.__fp);
/*
if (s_backtraceSize < FRAME_SIZE) {
s_backtrace[s_backtraceSize] = nullptr;
}
*/
while (s_backtraceSize < FRAME_SIZE && current_fp) {
void** frame = reinterpret_cast<void**>(current_fp);
void* next_fp = frame[0];
void* lr = frame[1];
if (next_fp == current_fp || lr == nullptr) break;
s_backtrace[s_backtraceSize++] = lr;
current_fp = next_fp;
}
}
Mod* faultyMod = modFromAddress(signalAddress);
// Mod* faultyMod = nullptr;
// for (int i = 1; i < s_backtraceSize; ++i) {
// auto mod = modFromAddress(s_backtrace[i]);
// if (mod != nullptr) {
// faultyMod = mod;
// break;
// }
// }
auto text = crashlog::writeCrashlog(faultyMod, getInfo(signalAddress, faultyMod), getStacktrace(), getRegisters());
log::error("Geode crashed!\n{}", text);
std::_Exit(EXIT_FAILURE);
//s_signal = 0;
}
}
static bool s_lastLaunchCrashed;
bool crashlog::setupPlatformHandler() {
// for whatever reason, i can't just do int*
if (pipe(s_pipe) != 0) return false;
fcntl(s_pipe[0], F_SETFD, FD_CLOEXEC);
fcntl(s_pipe[1], F_SETFD, FD_CLOEXEC);
struct sigaction action;
action.sa_sigaction = &signalHandler;
action.sa_flags = SA_SIGINFO;
sigemptyset(&action.sa_mask);
sigaction(SIGSEGV, &action, nullptr);
// I'd rather not track interrupt lol
// sigaction(SIGINT, &action, nullptr);
sigaction(SIGFPE, &action, nullptr);
sigaction(SIGILL, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
sigaction(SIGABRT, &action, nullptr);
sigaction(SIGBUS, &action, nullptr);
std::thread(&handlerThread).detach();
auto lastCrashedFile = crashlog::getCrashLogDirectory() / "last-crashed";
if (std::filesystem::exists(lastCrashedFile)) {
s_lastLaunchCrashed = true;
std::filesystem::remove(lastCrashedFile);
}
return true;
}
void crashlog::setupPlatformHandlerPost() {}
bool crashlog::didLastLaunchCrash() {
return s_lastLaunchCrashed;
}
std::filesystem::path crashlog::getCrashLogDirectory() {
return dirs::getGeodeDir() / "crashlogs";
}

View file

@ -1,43 +0,0 @@
#include <Geode/DefaultInclude.hpp>
#include "../load.hpp"
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <unistd.h>
#include <thread>
using namespace geode::prelude;
std::length_error::~length_error() _NOEXCEPT {} // do not ask...
// camila has an old ass macos and this function turned
// from dynamic to static thats why she needs to define it
// this is what old versions does to a silly girl
void dynamicEntry() {
auto dylib = dlopen("GeodeBootstrapper.dylib", RTLD_NOLOAD);
dlclose(dylib);
auto workingDir = dirs::getGameDir();
auto libDir = workingDir / "Frameworks";
auto updatesDir = workingDir / "geode" / "update";
auto error = std::error_code();
if (std::filesystem::exists(updatesDir / "GeodeBootstrapper.dylib", error) && !error) {
std::filesystem::rename(
updatesDir / "GeodeBootstrapper.dylib", libDir / "GeodeBootstrapper.dylib", error
);
if (error) return;
}
geodeEntry(nullptr);
}
extern "C" __attribute__((visibility("default"))) void dynamicTrigger() {
std::thread(&dynamicEntry).detach();
}
// remove when we can figure out how to not remove it
auto dynamicTriggerRef = &dynamicTrigger;

View file

@ -0,0 +1,48 @@
#include <Geode/DefaultInclude.hpp>
#include <Geode/loader/Dirs.hpp>
#include <Geode/Utils.hpp>
#include "../load.hpp"
#include "../../loader/LoaderImpl.hpp"
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <unistd.h>
#include <thread>
using namespace geode::prelude;
static bool(*s_applicationDidFinishLaunchingOrig)(void*, SEL, void*, void*);
bool applicationDidFinishLaunchingHook(void* self, SEL sel, void* p1, void* p2) {
// updateGeode();
int exitCode = geodeEntry(nullptr);
if (exitCode != 0)
return false;
// Don't patch if we are in the wrong version
if (!LoaderImpl::get()->isForwardCompatMode())
{
// Patches the depth format of gd to be GL_DEPTH24_STENCIL8_OES, fixing the CCClippingNode recreation
if (!LoaderImpl::get()->getInternalMod()->patch(reinterpret_cast<void*>(geode::base::get() + 0x268b38), { 0x03, 0x1e, 0x91, 0x52 }).isOk())
return false;
}
return s_applicationDidFinishLaunchingOrig(self, sel, p1, p2);
}
bool loadGeode() {
auto orig = geode::hook::replaceObjcMethod("AppController", "application:didFinishLaunchingWithOptions:", (void*)applicationDidFinishLaunchingHook);
if (!orig)
return false;
s_applicationDidFinishLaunchingOrig = reinterpret_cast<bool(*)(void*, SEL, void*, void*)>(orig.unwrap());
return true;
}
__attribute__((constructor)) void _entry() {
if (!loadGeode())
return;
}

View file

@ -4,9 +4,22 @@ using namespace geode::prelude;
#include <Geode/loader/Dirs.hpp>
#include <UIKit/UIKit.h>
#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include <AVFoundation/AVFoundation.h>
#include <iostream>
#include <sstream>
#include <Geode/utils/web.hpp>
#include <Geode/utils/permission.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/binding/GameManager.hpp>
#include <Geode/binding/AppDelegate.hpp>
#include <Geode/binding/MenuLayer.hpp>
#include <Geode/binding/FLAlertLayer.hpp>
#include <Geode/Utils.hpp>
#include <objc/runtime.h>
#include <stdlib.h>
using geode::utils::permission::Permission;
bool utils::clipboard::write(std::string const& data) {
[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:data.c_str()];
@ -19,26 +32,396 @@ std::string utils::clipboard::read() {
void utils::web::openLinkInBrowser(std::string const& url) {
[[UIApplication sharedApplication]
openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]]];
openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]] options:{} completionHandler:nil];
}
void geode_nslog(uintptr_t x) {
NSLog(@"geode %lx", x);
#pragma region Folder Pick Delegate
@interface PickerDelegate : NSObject <UIDocumentPickerDelegate>
@property (nonatomic, copy) void (^completion)(NSArray<NSURL*>* urls, NSError* error);
- (instancetype)initWithCompletion:(void (^)(NSArray<NSURL*>* urls, NSError* error))completion;
@end
@implementation PickerDelegate
- (instancetype)initWithCompletion:(void (^)(NSArray<NSURL*>* urls, NSError* error))completion {
self = [super init];
if (self) {
_completion = [completion copy];
}
return self;
}
- (void)documentPicker:(UIDocumentPickerViewController*)controller didPickDocumentsAtURLs:(NSArray<NSURL*>*)urls {
if (self.completion) {
self.completion(urls, nil);
}
}
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController*)controller {
if (self.completion) {
self.completion(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]);
}
}
@end
PickerDelegate* PickerDelegate_instance = nil;
#pragma endregion
UIViewController* getCurrentViewController() {
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIViewController *rootViewController = window.rootViewController;
while (rootViewController.presentedViewController) {
rootViewController = rootViewController.presentedViewController;
}
return rootViewController;
}
bool utils::file::openFolder(std::filesystem::path const& path) {
std::string newPath = fmt::format("{}://{}", getenv("GEODEINJECT_LOADED") ? "filza" : "shareddocuments", path);
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:newPath.c_str()]];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
return true;
}
return false;
}
GEODE_DLL Task<Result<std::filesystem::path>> file::pick(file::PickMode mode, file::FilePickOptions const& options) {
using RetTask = Task<Result<std::filesystem::path>>;
return RetTask::runWithCallback([mode, options](auto resultCallback, auto progress, auto cancelled) {
NSMutableArray<UTType*> *documentTypes = [NSMutableArray array];
for (const auto& filter : options.filters) {
for (const auto& file : filter.files) {
UTType* uti = [UTType typeWithFilenameExtension:@(file.c_str())];
if (uti) {
[documentTypes addObject:uti];
}
}
}
NSURL *FileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.file"]];
if (options.defaultPath && !options.defaultPath->parent_path().empty()) {
FileURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:options.defaultPath->c_str()]];
}
else if (options.defaultPath) {
auto FileExtension = [NSString stringWithUTF8String:options.defaultPath->c_str()];
FileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:FileExtension]];
}
// just for the picker not to crash, it gotta have a file to "save" then the writing is handled in the mod once we save the file somewhere
[@"" writeToURL:FileURL atomically:NO encoding:NSUTF8StringEncoding error:nil];
if (documentTypes.count == 0) {
[documentTypes addObject:UTTypeItem]; // Default to any file type if no filters are provided
}
UIDocumentPickerViewController *picker;
switch (mode) {
case file::PickMode::OpenFile:
picker = [[UIDocumentPickerViewController alloc]
initForOpeningContentTypes:documentTypes
asCopy:YES];
break;
case file::PickMode::SaveFile:
picker = [[UIDocumentPickerViewController alloc]
initForExportingURLs:@[FileURL]
asCopy:YES];
break;
case file::PickMode::OpenFolder:
picker = [[UIDocumentPickerViewController alloc]
initForOpeningContentTypes:@[UTTypeFolder]
asCopy:YES];
break;
}
picker.allowsMultipleSelection = NO;
picker.shouldShowFileExtensions = YES;
PickerDelegate_instance = [[PickerDelegate alloc] initWithCompletion:^(NSArray<NSURL*>* urls, NSError* error) {
PickerDelegate_instance = nil;
if (urls && urls.count > 0)
{
std::filesystem::path paths;
for (NSURL* url : urls)
{
if (url && url.path)
{
std::string pathStr = std::string([url.path UTF8String]);
auto path = std::filesystem::path(pathStr);
paths = path;
}
}
resultCallback(Ok(paths));
for (NSURL* url : urls)
{
if (url && url.path)
{
[url stopAccessingSecurityScopedResource];
}
}
}
else if (cancelled()) {
resultCallback(RetTask::Cancel());
} else if (error) {
resultCallback(Err(std::string([[error localizedDescription] UTF8String])));
} else {
resultCallback(RetTask::Cancel());
}
}];
picker.delegate = PickerDelegate_instance;
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *currentViewController = getCurrentViewController();
[currentViewController presentViewController:picker animated:YES completion:nil];
});
});
}
GEODE_DLL Task<Result<std::vector<std::filesystem::path>>> file::pickMany(file::FilePickOptions const& options) {
using RetTask = Task<Result<std::vector<std::filesystem::path>>>;
return RetTask::runWithCallback([options](auto resultCallback, auto progress, auto cancelled) {
NSMutableArray<NSString*> *documentTypes = [NSMutableArray array];
for (const auto& filter : options.filters) {
for (const auto& file : filter.files) {
NSString* uti = [UTType typeWithFilenameExtension:@(file.c_str())].identifier;
if (uti) {
[documentTypes addObject:uti];
}
}
}
if (documentTypes.count == 0) {
[documentTypes addObject:(NSString*)UTTypeItem.identifier]; // Default to any file type if no filters are provided
}
UIDocumentPickerViewController* picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:documentTypes inMode:UIDocumentPickerModeOpen];
picker.allowsMultipleSelection = true;
PickerDelegate_instance = [[PickerDelegate alloc] initWithCompletion:^(NSArray<NSURL*>* urls, NSError* error) {
PickerDelegate_instance = nil;
if (urls && urls.count > 0)
{
std::vector<std::filesystem::path> paths;
for (NSURL* url : urls)
{
if (url && url.path)
{
std::string pathStr = std::string([url.path UTF8String]);
if ([url startAccessingSecurityScopedResource])
{
auto path = std::filesystem::path(pathStr);
paths.push_back(path);
}
else
{
resultCallback(Err("Failed to access security-scoped resource: {}", pathStr));
}
}
}
resultCallback(Ok(paths));
for (NSURL* url : urls)
{
if (url && url.path)
{
[url stopAccessingSecurityScopedResource];
}
}
}
else if (cancelled()) {
resultCallback(RetTask::Cancel());
} else if (error) {
resultCallback(Err(std::string([[error localizedDescription] UTF8String])));
} else {
resultCallback(RetTask::Cancel());
}
}];
picker.delegate = PickerDelegate_instance;
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *currentViewController = getCurrentViewController();
[currentViewController presentViewController:picker animated:YES completion:nil];
});
});
}
// TODO: copied those two from android but idk maybe shouldve copied from mac
void geode::utils::game::exit() {
// TODO: yeah
// if (CCApplication::sharedApplication() &&
// (GameManager::get()->m_playLayer || GameManager::get()->m_levelEditorLayer)) {
// log::error("Cannot exit in PlayLayer or LevelEditorLayer!");
// return;
// }
AppDelegate::get()->trySaveGame(true);
// AppDelegate::get()->showLoadingCircle(false, true);
class Exit : public CCObject {
public:
void shutdown() {
// someone please look into this, I'm unsure if this will cause issues with saving!
std::exit(0);
}
};
CCDirector::get()->getActionManager()->addAction(CCSequence::create(
CCDelayTime::create(0.5f),
CCCallFunc::create(nullptr, callfunc_selector(Exit::shutdown)),
nullptr
), CCDirector::get()->getRunningScene(), false);
}
void geode::utils::game::restart() {
AppDelegate::get()->trySaveGame(true);
class Exit : public CCObject {
public:
void shutdown() {
NSURL* url = [NSURL URLWithString:@"geode://relaunch"];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
} else {
// this would only happen if you don't have the launcher
FLAlertLayer::create(
"Unavailable",
"Restarting is currently <cr>unavailable</c>. Please <cy>restart the game</c> manually.",
"OK"
)->show();
}
}
};
CCDirector::get()->getActionManager()->addAction(CCSequence::create(
CCDelayTime::create(0.5f),
CCCallFunc::create(nullptr, callfunc_selector(Exit::shutdown)),
nullptr
), CCDirector::get()->getRunningScene(), false);
}
void geode::utils::game::launchLoaderUninstaller(bool deleteSaveData) {
log::error("Launching Geode uninstaller is not supported on iOS");
}
CCPoint cocos::getMousePos() {
return CCPoint(0, 0);
}
namespace {
std::string s_savedBaseDir = "";
std::filesystem::path getBaseDir() {
if (!s_savedBaseDir.empty()) {
return std::filesystem::path(s_savedBaseDir);
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths firstObject];
std::filesystem::path documentsPath = [documentsDirectory UTF8String];
s_savedBaseDir = documentsPath;
return std::filesystem::path(documentsPath);
}
}
std::filesystem::path dirs::getGameDir() {
return std::filesystem::current_path();
return getBaseDir() / "game";
}
std::filesystem::path dirs::getModRuntimeDir() {
return dirs::getGeodeDir() / "unzipped";
}
std::filesystem::path dirs::getSaveDir() {
return weaklyCanonical(CCFileUtils::sharedFileUtils()->getWritablePath().c_str());
return getBaseDir() / "save";
}
bool geode::utils::permission::getPermissionStatus(Permission permission) {
return true; // unimplemented
switch (permission) {
case Permission::RecordAudio:
return [[AVAudioSession sharedInstance] recordPermission] == AVAudioSessionRecordPermissionGranted;
default:
return false;
}
}
void geode::utils::permission::requestPermission(Permission permission, std::function<void(bool)> callback) {
callback(true); // unimplemented
switch (permission) {
case Permission::RecordAudio:
return [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
callback(granted == YES);
}];
default: // ios doesnt have a "access all files" permission
return callback(false);
}
}
#include "../../utils/thread.hpp"
std::string geode::utils::thread::getDefaultName() {
uint64_t tid = 0ul;
pthread_threadid_np(nullptr, &tid);
return fmt::format("Thread #{}", tid);
}
void geode::utils::thread::platformSetName(std::string const& name) {
pthread_setname_np(name.c_str());
}
Result<> geode::hook::addObjcMethod(std::string const& className, std::string const& selectorName, void* imp) {
auto cls = objc_getClass(className.c_str());
if (!cls)
return Err("Class not found");
auto sel = sel_registerName(selectorName.c_str());
class_addMethod(cls, sel, (IMP)imp, "v@:");
return Ok();
}
Result<void*> geode::hook::getObjcMethodImp(std::string const& className, std::string const& selectorName) {
auto cls = objc_getClass(className.c_str());
if (!cls)
return Err("Class not found");
auto sel = sel_registerName(selectorName.c_str());
auto method = class_getInstanceMethod(cls, sel);
if (!method)
return Err("Method not found");
return Ok((void*)method_getImplementation(method));
}
Result<void*> geode::hook::replaceObjcMethod(std::string const& className, std::string const& selectorName, void* imp) {
auto cls = objc_getClass(className.c_str());
if (!cls)
return Err("Class not found");
auto sel = sel_registerName(selectorName.c_str());
auto method = class_getInstanceMethod(cls, sel);
if (!method)
return Err("Method not found");
auto oldImp = method_setImplementation(method, (IMP)imp);
return Ok((void*)oldImp);
}

View file

@ -319,6 +319,22 @@ Result<void*> geode::hook::getObjcMethodImp(std::string const& className, std::s
return Ok((void*)method_getImplementation(method));
}
Result<void*> geode::hook::replaceObjcMethod(std::string const& className, std::string const& selectorName, void* imp) {
auto cls = objc_getClass(className.c_str());
if (!cls)
return Err("Class not found");
auto sel = sel_registerName(selectorName.c_str());
auto method = class_getInstanceMethod(cls, sel);
if (!method)
return Err("Method not found");
auto oldImp = method_setImplementation(method, (IMP)imp);
return Ok((void*)oldImp);
}
bool geode::utils::permission::getPermissionStatus(Permission permission) {
return true; // unimplemented
}

View file

@ -18,7 +18,7 @@
#include <filesystem>
#endif
#if defined(GEODE_IS_ANDROID) || defined(GEODE_IS_MACOS)
#if defined(GEODE_IS_ANDROID) || defined(GEODE_IS_MACOS) || defined(GEODE_IS_IOS)
struct path_hash_t {
std::size_t operator()(std::filesystem::path const& path) const noexcept {
return std::filesystem::hash_value(path);