mirror of
https://github.com/geode-sdk/geode.git
synced 2025-04-15 14:34:50 -04:00
Merge branch 'geode-sdk:main' into main
This commit is contained in:
commit
c21c666e6c
31 changed files with 2105 additions and 238 deletions
|
@ -2,3 +2,5 @@
|
|||
11e81e3d64313d319955d9a214ad0ded78985bed
|
||||
2bb416ba77f2f01897cc10a1afac40c957feadfc
|
||||
|
||||
# whole lot of whitespace changes
|
||||
0cecd677561d1992859f30dc1e233d8d5d83c9ad
|
||||
|
|
73
.github/workflows/build.yml
vendored
73
.github/workflows/build.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
BIN
loader/include/link/ios/libcurl.a
Normal file
BIN
loader/include/link/ios/libcurl.a
Normal file
Binary file not shown.
BIN
loader/include/link/ios/libfmod_iphoneos.a
Normal file
BIN
loader/include/link/ios/libfmod_iphoneos.a
Normal file
Binary file not shown.
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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")
|
371
loader/src/cocos2d-ext/CCClippingNode.cpp
Normal file
371
loader/src/cocos2d-ext/CCClippingNode.cpp
Normal 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 *)¤tStencilWriteMask);
|
||||
glGetIntegerv(GL_STENCIL_FUNC, (GLint *)¤tStencilFunc);
|
||||
glGetIntegerv(GL_STENCIL_REF, ¤tStencilRef);
|
||||
glGetIntegerv(GL_STENCIL_VALUE_MASK, (GLint *)¤tStencilValueMask);
|
||||
glGetIntegerv(GL_STENCIL_FAIL, (GLint *)¤tStencilFail);
|
||||
glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, (GLint *)¤tStencilPassDepthFail);
|
||||
glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, (GLint *)¤tStencilPassDepthPass);
|
||||
|
||||
// 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, ¤tDepthWriteMask);
|
||||
|
||||
// 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 *)¤tAlphaTestFunc);
|
||||
glGetFloatv(GL_ALPHA_TEST_REF, ¤tAlphaTestRef);
|
||||
// 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
|
393
loader/src/cocos2d-ext/CCKeyboardDispatcher.cpp
Normal file
393
loader/src/cocos2d-ext/CCKeyboardDispatcher.cpp
Normal 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
|
39
loader/src/cocos2d-ext/CCKeyboardHandler.cpp
Normal file
39
loader/src/cocos2d-ext/CCKeyboardHandler.cpp
Normal 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
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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!")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
89
loader/src/platform/ios/LoaderImpl.mm
Normal file
89
loader/src/platform/ios/LoaderImpl.mm
Normal 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;
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
#include <crashlog.hpp>
|
||||
|
||||
bool crashlog::setupPlatformHandler() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool crashlog::didLastLaunchCrash() {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::path crashlog::getCrashLogDirectory() {
|
||||
return "";
|
||||
}
|
423
loader/src/platform/ios/crashlog.mm
Normal file
423
loader/src/platform/ios/crashlog.mm
Normal 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";
|
||||
}
|
|
@ -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;
|
48
loader/src/platform/ios/main.mm
Normal file
48
loader/src/platform/ios/main.mm
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue