Merge branch '1.4.0-dev' into 2.0.0-dev

This commit is contained in:
HJfod 2023-12-20 12:19:29 +02:00
commit 442789e61a
100 changed files with 3777 additions and 790 deletions

View file

@ -1,7 +1,17 @@
name: Bug Report
description: Report a bug where something is not working as expected in Geode Loader (not specific mods), which does not crash the game.
description: Report a Geode bug (not mods themselves) where something is not working as expected in Geode Loader (not mods created by others), which does not crash the game.
labels: [ "unverified", "bug" ]
body:
- type: checkboxes
attributes:
label: Geode Issue
description: |
The Geode repository is for issues of *Geode Loader*, not individual mods created by other developers.
When submitting a bug report, please make sure that the bug is *actually* related to ***Geode Loader itself*** and not to a mod or mod combination.
Failing to do this will get your issue *closed without explanation*.
options:
- label: I confirm that this bug is NOT related to a mod but directly to Geode Loader itself.
required: true
- type: dropdown
id: platform
attributes:

View file

@ -1,7 +1,17 @@
name: Crash Report
description: Report a bug that crashes the game or prevents startup caused by Geode Loader (not individual mods).
description: Report a Geode bug (not mods themselves) that crashes the game or prevents startup caused by Geode Loader (not mods created by others).
labels: [ "unverified", "crash" ]
body:
- type: checkboxes
attributes:
label: Geode Issue
description: |
The Geode repository is for issues of *Geode Loader*, not individual mods created by other developers.
When submitting a crash report, please make sure that the crash is *actually* related to ***Geode Loader itself*** and not to a mod or mod combination.
Failing to do this will get your issue *closed without explanation*.
options:
- label: I confirm that this crash is NOT related to a mod but directly to Geode Loader itself.
required: true
- type: dropdown
id: platform
attributes:

View file

@ -2,6 +2,7 @@ name: Build Binaries
on:
workflow_dispatch:
pull_request:
push:
branches:
- '**' # every branch
@ -20,6 +21,7 @@ jobs:
- name: Windows
os: windows-latest
id: win
cli_id: win
extra_flags: -T host=x64 -A win32 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DGEODE_DEBUG=On
# uncomment to use vs clang-cl and ninja
#extra_flags: >
@ -34,6 +36,7 @@ jobs:
- name: macOS
os: macos-latest
id: mac
cli_id: mac
extra_flags: >
-DCMAKE_C_COMPILER=clang
-DCMAKE_CXX_COMPILER=clang++
@ -43,6 +46,21 @@ jobs:
cli_cmd: 'chmod +x $GITHUB_WORKSPACE/cli/geode'
package_cmd: './installer/mac/package.sh ./bin/nightly ./installer/mac/geode-installer-mac.pkg'
installer_path: './installer/mac/geode-installer-mac.pkg'
- name: Android
os: ubuntu-latest
id: android
cli_id: linux
extra_flags: >
-DCMAKE_TOOLCHAIN_FILE=$NDK_HOME/build/cmake/android.toolchain.cmake
-DANDROID_ABI=armeabi-v7a
-DANDROID_PLATFORM=android-23
-DGEODE_DONT_BUILD_TEST_MODS=1
-G Ninja
cli_cmd: 'chmod +x $GITHUB_WORKSPACE/cli/geode'
package_cmd: ''
installer_path: ''
name: Build ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
@ -57,7 +75,7 @@ jobs:
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ matrix.config.os }}
if: matrix.config.id == 'mac'
if: matrix.config.id != 'win'
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1.12.1
@ -65,22 +83,36 @@ jobs:
arch: amd64_x86
if: matrix.config.id == 'win'
- name: Setup NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r26b
add-to-path: false
if: matrix.config.id == 'android'
- name: Download CLI
uses: robinraju/release-downloader@v1.8
with:
repository: geode-sdk/cli
latest: true
fileName: '*-${{ matrix.config.id }}.zip'
fileName: '*-${{ matrix.config.cli_id }}.zip'
tarBall: false
zipBall: false
out-file-path: "cli"
- name: Setup CLI
run: |
7z x "${{ github.workspace }}/cli/*-${{ matrix.config.id }}.zip" -o"${{ github.workspace }}/cli"
7z x "${{ github.workspace }}/cli/*-${{ matrix.config.cli_id }}.zip" -o"${{ github.workspace }}/cli"
${{ matrix.config.cli_cmd }}
echo "${{ github.workspace }}/cli" >> $GITHUB_PATH
- name: Setup Android Env
run: |
echo "NDK_HOME=${{ steps.setup-ndk.outputs.ndk-path }}" >> "$GITHUB_ENV"
sudo apt install ninja-build
if: matrix.config.id == 'android'
- name: Configure
run: >
cmake -B ${{ github.workspace }}/build
@ -101,12 +133,14 @@ jobs:
- name: Package Installer
run: ${{ matrix.config.package_cmd }}
if: matrix.config.id != 'android'
- name: Upload Installer
uses: actions/upload-artifact@v3
with:
name: geode-installer-${{ matrix.config.id }}
path: ${{ matrix.config.installer_path }}
if: matrix.config.id != 'android'
publish:
name: Publish
@ -146,21 +180,19 @@ jobs:
files: geode-win/XInput9_1_0.dll geode-win/Geode.dll geode-win/GeodeUpdater.exe geode-win/Geode.lib geode-win/Geode.pdb
dest: geode-${{ steps.ref.outputs.hash }}-win.zip
# TODO change in 2.0.0
- name: Zip Windows Resources
- name: Zip Android Artifacts
uses: vimtor/action-zip@v1.1
with:
files: geode-win/resources
dest: resources-win.zip
files: geode-android/Geode.so
dest: geode-${{ steps.ref.outputs.hash }}-android.zip
# This is basically a hack because of line endings. Blame windows.
- name: Zip MacOS Resources
- name: Zip Resources
uses: vimtor/action-zip@v1.1
with:
files: geode-mac/resources
dest: resources-mac.zip
dest: resources.zip
- name: Update Nightly Release
- name: Update Development Release
uses: andelf/nightly-release@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -174,5 +206,5 @@ jobs:
./geode-installer-${{ steps.ref.outputs.hash }}-win.exe
./geode-${{ steps.ref.outputs.hash }}-mac.zip
./geode-${{ steps.ref.outputs.hash }}-win.zip
./resources-win.zip
./resources-mac.zip
./geode-${{ steps.ref.outputs.hash }}-android.zip
./resources.zip

View file

@ -28,8 +28,8 @@ jobs:
mv dev/geode-installer-*-win.exe geode-installer-v${{ steps.ref.outputs.version }}-win.exe
mv dev/geode-*-mac.zip geode-v${{ steps.ref.outputs.version }}-mac.zip
mv dev/geode-*-win.zip geode-v${{ steps.ref.outputs.version }}-win.zip
mv dev/resources-win.zip resources-win.zip
mv dev/resources-mac.zip resources-mac.zip
mv dev/geode-*-android.zip geode-v${{ steps.ref.outputs.version }}-android.zip
mv dev/resources.zip resources.zip
- name: Create Draft Release
uses: softprops/action-gh-release@v1
@ -49,5 +49,5 @@ jobs:
./geode-installer-v${{ steps.ref.outputs.version }}-win.exe
./geode-v${{ steps.ref.outputs.version }}-mac.zip
./geode-v${{ steps.ref.outputs.version }}-win.zip
./resources-win.zip
./resources-mac.zip
./geode-v${{ steps.ref.outputs.version }}-android.zip
./resources.zip

View file

@ -2,7 +2,11 @@ name: Test Offsets
on:
workflow_dispatch:
pull_request:
push:
paths:
- 'bindings/**' # only when adjusting bindings
- 'loader/test/members/**'
branches:
- '**' # every branch
- '!no-build-**' # unless marked as no-build

18
.gitignore vendored
View file

@ -46,20 +46,32 @@ build2
build-docs/
bin
# Ignore docs folders
docs/**
docs
# Ignore codegenned files
loader/src/internal/about.hpp
loader/src/internal/resources.hpp
loader/resources/mod.json
loader/resources/version
loader/resources/blanks/rename.js
loader/resources/about.md
loader/resources/changelog.md
loader/resources/support.md
# Ignore generated files
installer/mac/*.pkg
installer/windows/*.exe
# Ignore fod's include directories which are stored in this funny file
fods-catgirl-hideout.txt
# Ignore I don't even know what that is probably fod's flash testing script
test-docs.bat
# krita files too because alk is funny
# Ignore krita files too because we don't want our project files shaking my head
**/*.kra
installer/mac/*.pkg
installer/windows/*.exe
# Ignore clangd cache
.cache

View file

@ -1,5 +1,89 @@
# Geode Changelog
## v1.4.0
* Add Android support !!!!
* Implement every Geode functionality except `utils::file::openFolder`
* Requires the Geode launcher in order to be used
* Fixes the text input node allowing typing for 1 less character
* Uses `logcat` in order to get crash reports, so reopening the game is required to generate them
* Broma requires classes to be added `[[link(android)]]` in order to be linked
* All Geode and GD files are stored in `Android/data/com.geode.launcher/files`
* Game files in `game`, save files in `save`
* Allow logging to be disabled per mod (6d599a5)
* Mod cells use layouts (114fa46)
* MacOS console is now separate (182984d)
* Add uninstall button to Geode mod (Only functional in Windows currently) (a738320)
* Make new version label invisible on download (0f179da)
* Fix the toggling of disabled dependencies (cd89ef1)
* Fix spritesheet issues (ef47647)
* Change `LoadingLayer` (ef47647)
* Make mod info popup top a layout (dd806e0)
* Add `GEODE_HIDDEN` to inline unique functions (71a79ab)
* Fix big mod icons (26a6c7e)
* Fix `CCNode::removeChildByID` export (23cd456)
* Make `MDTextArea` fit its size (140f38b)
* Enable ESC/Back to go back in Geode mod list (2847bee)
* Add `SimpleTextArea` (7f277a7)
* Check modified date when unzipping `.geode` files (5c765c6)
* Only hash markdown files on resource checking (f563c46)
## v1.3.10
* Panic if decompressString2 fails, to prevent data loss (0787b8f4)
## v1.3.9
* Fix followThunkFunction (4b766301)
## v1.3.8
* Implement a save file fix for Windows (391f63ed)
* Recursively follow jumps in followThunkFunction (44a018cd)
* Remove need for GEODE_DEBUG for crashlogs (e8a326f7)
* Some bindings (f18335fa)
## v1.3.7
* Fix web download deadlock (16418562)
* Set max time for updating index notification (f7962246)
* Log geode version on startup (c5550a67)
* Fix logic error in addHook (5cf0f3c2)
* Improve logging + minor refactors (5083017b)
* Some bindings changes
## v1.3.6
* Allow error responses in our WebRequest classes (237128bf)
* Display unhandled C++ exceptions in crash log (fdd78aca, 0d091626, 52421d8c, 0472075f)
* Fix GEODE_CLI force caching when not found (0a113744)
* Only write checksum file after unzipping, fixes inconsistent index state (b4fbea51)
* Fix the index notif staying on all the time (c967b520)
* Bump minimum required CMake version in codegen (27ed63e7)
* Only show update indicator if mod is enabled (8762714c)
* Fix FLAlertLayer m_scrollingLayer not being a ScrollingLayer (9694b35d)
* Fix gnustl vector dtor (b55e6465, 0bdb0df7)
* Loads of bindings changes
## v1.3.5
* Follow redirect in web::utils functions (a942a45)
* Lots of bindings
* Make codegen symbols private visibility (696a2ca)
* Add deadstrip to macos (0d62940)
* Readd the nullptr check in InstallListPopup::createCells (499f256)
* Fix garagelayer ids on not logged in users (dd0179c)
## v1.3.4
* Implement string setting character filters (cf8fbba)
* Update bindings
## v1.3.3
* Reunify resources.zip (81de161)
## v1.3.2
* Fix alignment of some textures (8f39c38)
* Bring back unknown problems (0663569)
* Fix some Windows 7 incompatibility (2d2bdd1)
* Remove enabled from the crashlogs (5b7d318)
* Make index unzipping async (7c582f1)
* Fix mods by developer crashing when mod was toggled (a6a47bf)
* Fix nested lists in the markdown (2723588)
* Fix search paths (8f39c38, aa55ebe)
## v1.3.1
* Fix TulipHook not relocating RIP relative operands on MacOS (6cad19d)
@ -196,7 +280,7 @@ Thank you to [Fleeym](https://github.com/Fleeym/Fleeym) for contributing to this
* Fix recursive comparison in VersionTag
* `geode/unzipped` is now deleted on startup if it exists
## v1.0.0-beta.9
## v1.0.0-beta.9
* Fix multiple modifiers not being able to have fields on same class due to having same field index
* Add `Result::ok` and `Result::err` for converting the `Result` into `std::optional`
@ -264,7 +348,7 @@ Thank you to [Fleeym](https://github.com/Fleeym/Fleeym) for contributing to this
- Something related to codegen and addresser? I have no clue what it does, so you probably won't have either
- MacOS minimum version bumped to 10.14
## v1.0.0-beta.2
## v1.0.0-beta.2
* Fixed bug where `Mod::getSavedValue` would cause a crash due to trying operator on a null JSON value
* Fixed bug where loading would crash if one of the mods' binaries failed to load

View file

@ -71,8 +71,8 @@ set(GEODE_BIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
set(GEODE_LOADER_PATH ${CMAKE_CURRENT_SOURCE_DIR}/loader)
set(GEODE_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
include(cmake/GeodeFile.cmake)
include(cmake/Platform.cmake)
include(cmake/GeodeFile.cmake)
include(cmake/CPM.cmake)
if (PROJECT_IS_TOP_LEVEL AND NOT GEODE_BUILDING_DOCS)
@ -96,7 +96,7 @@ if (PROJECT_IS_TOP_LEVEL AND NOT GEODE_BUILDING_DOCS)
set(TULIP_LINK_SOURCE ON)
endif()
set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)
CPMAddPackage("gh:geode-sdk/TulipHook#3423a29")
CPMAddPackage("gh:geode-sdk/TulipHook#d2132de")
set(CMAKE_WARN_DEPRECATED ON CACHE BOOL "" FORCE)
# Silence warnings from dependencies
@ -185,7 +185,17 @@ target_include_directories(GeodeCodegenSources PRIVATE
${GEODE_LOADER_PATH}/include/Geode/cocos/extensions
${GEODE_LOADER_PATH}/include/Geode/fmod
)
set_target_properties(GeodeCodegenSources PROPERTIES CXX_VISIBILITY_PRESET hidden)
target_compile_features(GeodeCodegenSources PUBLIC cxx_std_20)
if (APPLE)
target_compile_options(GeodeCodegenSources PUBLIC -ffunction-sections -fdata-sections)
target_link_options(GeodeCodegenSources PUBLIC -dead_strip)
elseif(ANDROID)
target_compile_options(GeodeCodegenSources PUBLIC -ffunction-sections -fdata-sections)
target_link_options(GeodeCodegenSources PUBLIC -Wl,--gc-sections)
endif()
if (NOT GEODE_DISABLE_PRECOMPILED_HEADERS)
target_precompile_headers(GeodeCodegenSources INTERFACE
"${GEODE_LOADER_PATH}/include/Geode/Bindings.hpp"
@ -218,6 +228,11 @@ else()
set(GEODE_PLATFORM_BIN_PATH ${GEODE_BIN_PATH}/${PROJECT_VERSION}/${GEODE_PLATFORM_BINARY})
endif()
if (WIN32)
# This allows you to compile in debug mode
add_compile_definitions(_HAS_ITERATOR_DEBUGGING=0)
endif()
if (PROJECT_IS_TOP_LEVEL)
add_subdirectory(loader)

View file

@ -1,11 +1,11 @@
// clang-format off
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCActionTween {
static cocos2d::CCActionTween* create(float, char const*, float, float) = mac 0x447590;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCActionManager {
CCActionManager() {
m_pTargets = nullptr;
@ -21,17 +21,17 @@ class cocos2d::CCActionManager {
auto resumeTarget(cocos2d::CCObject*) = mac 0x10bd20;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCAnimate {
static cocos2d::CCAnimate* create(cocos2d::CCAnimation*) = mac 0x1f8fc0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCAnimation {
static auto createWithSpriteFrames(cocos2d::CCArray*, float) = mac 0x140df0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCApplication {
virtual auto run();
virtual auto getCurrentLanguage() = mac 0x1a3f40, ios 0x10e508;
@ -39,13 +39,15 @@ class cocos2d::CCApplication {
virtual auto openURL(char const*) = mac 0x1a4550, ios 0x10e7a4;
virtual auto setAnimationInterval(double) = mac 0x1a3ee0, ios 0x10e494;
static auto sharedApplication() = mac 0x1a3f30;
[[link(win)]]
bool getControllerConnected() const = mac 0x27d1b0;
// ~CCApplication() = mac 0x1a3d10, ios 0x10e384;
CCApplication() {}
~CCApplication() {}
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCArray {
// auto addObject(cocos2d::CCObject*) = mac 0x419f90, ios 0x16504c;
auto addObjectNew(cocos2d::CCObject*) = mac 0x41a450;
@ -67,27 +69,32 @@ class cocos2d::CCArray {
// auto stringAtIndex(unsigned int) = mac 0x41a320;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCBezierTo {
static cocos2d::CCBezierTo* create(float, cocos2d::_ccBezierConfig const&) = mac 0x1f6c10;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCBMFontConfiguration {
static cocos2d::CCBMFontConfiguration* create(char const*) = mac 0x3450f0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCCallFunc {
static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFunc) = mac 0x454d90;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCCallFuncO {
static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFuncO, cocos2d::CCObject*) = mac 0x455940;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCCallFuncND {
static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFuncND, void*) = mac 0x455470;
}
[[link(win, android)]]
class cocos2d::CCClippingNode {
CCClippingNode() {
m_pStencil = nullptr;
@ -121,12 +128,17 @@ class cocos2d::CCClippingNode {
// void updateConnected() = win 0xc7fb0;
//}
[[link(win, android)]]
class cocos2d::CCConfiguration {
void gatherGPUInfo() = mac 0x2a6e10;
}
[[link(win)]]
class cocos2d::CCDelayTime {
static cocos2d::CCDelayTime* create(float) = mac 0x1f4380;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCDictionary {
auto allKeys() = mac 0x190450, ios 0x2de774;
auto count() = mac 0x190430;
@ -142,7 +154,7 @@ class cocos2d::CCDictionary {
auto valueForKey(gd::string const&) = mac 0x1907a0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCDirector {
CCDirector() {}
~CCDirector() {}
@ -185,7 +197,7 @@ class cocos2d::CCDirector {
auto popSceneWithTransition(float, cocos2d::PopTransition) = mac 0x24a8b0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCDrawNode {
CCDrawNode() = mac 0x378b40, win 0x6b9f0;
auto clear() = mac 0x379e80;
@ -201,7 +213,7 @@ class cocos2d::CCDrawNode {
virtual ~CCDrawNode() = mac 0x378cc0, win 0x6ba60;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCAction {
CCAction() = mac 0x35b610, win 0x7a6d0;
virtual ~CCAction() = mac 0x35b6b0, win 0x7a7f0;
@ -213,13 +225,13 @@ class cocos2d::CCAction {
auto update(float time) = mac 0x35b890;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCFiniteTimeAction {
// same as CCActionInterval::reverse i think
auto reverse() = mac 0x1f2720;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCActionInterval {
auto copyWithZone(cocos2d::CCZone* zone) = mac 0x1f2550;
auto isDone() = mac 0x1f2640;
@ -229,32 +241,32 @@ class cocos2d::CCActionInterval {
bool initWithDuration(float d) = mac 0x1f2510;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCEaseBackIn {
static cocos2d::CCEaseBackIn* create(cocos2d::CCActionInterval*) = mac 0x2a41b0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCEaseElasticIn {
static cocos2d::CCEaseElasticIn* create(cocos2d::CCActionInterval*, float) = mac 0x2a2e00;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCEaseElasticOut {
static cocos2d::CCEaseElasticOut* create(cocos2d::CCActionInterval*, float) = mac 0x2a3080;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCEaseIn {
static cocos2d::CCEaseIn* create(cocos2d::CCActionInterval*, float) = mac 0x2a1960;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCEaseInOut {
static cocos2d::CCEaseInOut* create(cocos2d::CCActionInterval*, float) = mac 0x2a1d80;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCEaseOut {
static cocos2d::CCEaseOut* create(cocos2d::CCActionInterval*, float) = mac 0x2a1b70;
}
@ -283,7 +295,7 @@ class cocos2d::CCEGLView {
void onGLFWWindowSizeFunCallback(GLFWwindow* window, int width, int height);
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCEGLViewProtocol {
CCEGLViewProtocol() = win 0xbac00;
virtual ~CCEGLViewProtocol() = win 0xbacc0;
@ -294,17 +306,17 @@ class cocos2d::CCEGLViewProtocol {
virtual void setFrameSize(float, float) = mac 0x29d960;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCFadeOut {
static cocos2d::CCFadeOut* create(float) = mac 0x1f7d80;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCFadeTo {
static cocos2d::CCFadeTo* create(float, unsigned char) = mac 0x1f7ff0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCFileUtils : cocos2d::TypeInfo {
static cocos2d::CCFileUtils* sharedFileUtils() = mac 0x377030, ios 0x159450;
static void purgeFileUtils();
@ -314,19 +326,19 @@ class cocos2d::CCFileUtils : cocos2d::TypeInfo {
void removeAllPaths() = mac 0x241600;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCGLProgram {
auto setUniformsForBuiltins() = mac 0x232c70;
auto use() = mac 0x231d70;
bool compileShader(unsigned int* shader, unsigned int type, const char* source) = mac 0x231a30;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCHide {
static cocos2d::CCHide* create() = mac 0x4543e0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCIMEDelegate {
~CCIMEDelegate() {
CCIMEDispatcher::sharedDispatcher()->removeDelegate(this);
@ -338,7 +350,7 @@ class cocos2d::CCIMEDelegate {
}
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCIMEDispatcher {
static auto sharedDispatcher() = mac 0x2773f0, ios 0x12d170;
auto addDelegate(cocos2d::CCIMEDelegate*) = mac 0x277480, ios 0x12d204;
@ -347,7 +359,7 @@ class cocos2d::CCIMEDispatcher {
void dispatchDeleteBackward() = mac 0x277af0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCImage {
CCImage() = mac 0x24fa00, win 0xc5fd0;
virtual ~CCImage() = mac 0x24fa80, win 0xc6100;
@ -356,20 +368,21 @@ class cocos2d::CCImage {
auto initWithImageData(void*, int, cocos2d::CCImage::EImageFormat, int, int, int) = mac 0x24fcb0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCKeyboardDispatcher {
bool dispatchKeyboardMSG(cocos2d::enumKeyCodes, bool) = mac 0xe8190;
const char* keyToString(cocos2d::enumKeyCodes) = mac 0xe8450;
void updateModifierKeys(bool shft, bool ctrl, bool alt, bool cmd) = mac 0xe8430;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCKeyboardHandler {
static cocos2d::CCKeyboardHandler* handlerWithDelegate(cocos2d::CCKeyboardDelegate*) = mac 0x242030;
virtual auto initWithDelegate(cocos2d::CCKeyboardDelegate*) = mac 0x241ff0, ios 0x13f8b8;
~CCKeyboardHandler() = mac 0x241e90, ios 0x13f87c, win 0x99a10;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCKeypadHandler {
static cocos2d::CCKeypadHandler* handlerWithDelegate(cocos2d::CCKeypadDelegate*) = mac 0x1ff2d0;
virtual auto initWithDelegate(cocos2d::CCKeypadDelegate*) = mac 0x1ff290, ios 0x69; // iOS stub
@ -378,8 +391,9 @@ class cocos2d::CCKeypadHandler {
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCLabelBMFont {
CCLabelBMFont() = mac 0x347b60;
static cocos2d::CCLabelBMFont* create(char const*, char const*) = mac 0x347660;
auto limitLabelWidth(float, float, float) = mac 0x34a6e0, ios 0x21b740;
auto setFntFile(char const*) = mac 0x34a5f0;
@ -387,7 +401,7 @@ class cocos2d::CCLabelBMFont {
static auto create() = mac 0x3473f0;
virtual auto init() = mac 0x347b10, ios 0x2198e0;
bool initWithString(const char* str, const char* fnt, float width, cocos2d::CCTextAlignment align, cocos2d::CCPoint offset);
bool initWithString(const char* str, const char* fnt, float width, cocos2d::CCTextAlignment align, cocos2d::CCPoint offset) = mac 0x347710;
virtual auto setScaleX(float) = mac 0x34a5b0, ios 0x21b6e8;
virtual auto setScaleY(float) = mac 0x34a5d0, ios 0x21b714;
virtual auto setScale(float) = mac 0x34a590, ios 0x21b6bc;
@ -418,7 +432,7 @@ class cocos2d::CCLabelBMFont {
virtual ~CCLabelBMFont() = mac 0x347e80, win 0x9be70;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCLabelTTF {
static cocos2d::CCLabelTTF* create() = mac 0x1fa7e0;
static cocos2d::CCLabelTTF* create(char const*, char const*, float) = mac 0x1fa840;
@ -426,7 +440,7 @@ class cocos2d::CCLabelTTF {
virtual auto setString(char const*) = mac 0x1fad70;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCLayer {
CCLayer() = mac 0x2725b0, ios 0xc7708, win 0xa15e0;
virtual auto ccTouchBegan(cocos2d::CCTouch*, cocos2d::CCEvent*) = mac 0x2734d0, ios 0xc810c;
@ -467,7 +481,7 @@ class cocos2d::CCLayer {
virtual ~CCLayer() = mac 0x2727b0, ios 0xc7848, win 0xa1940;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCLayerColor {
CCLayerColor() = mac 0x274320, ios 0xc8aec, win 0xa1710;
static cocos2d::CCLayerColor* create(cocos2d::_ccColor4B const&, float, float) = mac 0x2745e0;
@ -485,7 +499,7 @@ class cocos2d::CCLayerColor {
virtual ~CCLayerColor() = mac 0x2743d0, ios 0x2743e0, win 0xa1a20;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCLayerRGBA {
CCLayerRGBA() = mac 0x2738d0, ios 0xc85cc, win 0xa1890;
virtual auto init() = mac 0x273b40, ios 0xc8de8;
@ -506,7 +520,7 @@ class cocos2d::CCLayerRGBA {
virtual ~CCLayerRGBA() = mac 0x273aa0, ios 0xc77b0, win 0xa1b20;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMenu {
auto alignItemsHorizontallyWithPadding(float) = mac 0x4393e0, ios 0x132508;
auto alignItemsVerticallyWithPadding(float) = mac 0x439190;
@ -530,7 +544,7 @@ class cocos2d::CCMenu {
cocos2d::CCMenuItem* itemForTouch(cocos2d::CCTouch*) = mac 0x438dd0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMenuItem {
bool initWithTarget(cocos2d::CCObject*, cocos2d::SEL_MenuHandler) = mac 0x1fb7f0;
virtual ~CCMenuItem() = mac 0x1fb8e0, ios 0x2cdf4, win 0xab9c0;
@ -546,13 +560,13 @@ class cocos2d::CCMenuItem {
auto rect() = mac 0x1fbb00, ios 0x2cf3c;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMenuItemImage {
// virtual ~CCMenuItemImage() = mac 0x1febb0;
virtual auto init() = mac 0x1fd750;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMenuItemLabel {
virtual ~CCMenuItemLabel() = mac 0x1fc0d0;
virtual auto activate() = mac 0x1fc240;
@ -565,7 +579,7 @@ class cocos2d::CCMenuItemLabel {
virtual auto setLabel(cocos2d::CCNode*) = mac 0x1fbbc0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMenuItemSprite {
// virtual ~CCMenuItemSprite() = mac 0x1feab0;
virtual auto selected() = mac 0x1fd3f0, ios 0x2d2cc;
@ -585,7 +599,7 @@ class cocos2d::CCMenuItemSprite {
static auto create(cocos2d::CCNode*, cocos2d::CCNode*, cocos2d::CCObject*, cocos2d::SEL_MenuHandler) = mac 0x1fd2d0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMotionStreak {
CCMotionStreak() = win 0xae310;
virtual ~CCMotionStreak() = win 0xae450;
@ -596,30 +610,30 @@ class cocos2d::CCMotionStreak {
virtual auto draw();
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMouseDispatcher {
bool dispatchScrollMSG(float x, float y) = mac 0x2e8f40;
void removeDelegate(cocos2d::CCMouseDelegate* delegate);
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMouseHandler {
static cocos2d::CCMouseHandler* handlerWithDelegate(cocos2d::CCMouseDelegate*) = mac 0x12ef80;
virtual auto initWithDelegate(cocos2d::CCMouseDelegate*) = mac 0x12ef40, ios 0x43798;
~CCMouseHandler() = mac 0x12ede0, ios 0x4375c, win 0xb1fd0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMoveBy {
static cocos2d::CCMoveBy* create(float, cocos2d::CCPoint const&) = mac 0x1f50e0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCMoveTo {
static cocos2d::CCMoveTo* create(float, cocos2d::CCPoint const&) = mac 0x1f54d0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCNode {
CCNode() = mac 0x122550, win 0x5e7d0;
auto boundingBox() = mac 0x123030;
@ -631,6 +645,12 @@ class cocos2d::CCNode {
virtual auto cleanup() = mac 0x123100, ios 0x15e3a4;
auto convertToNodeSpace(cocos2d::CCPoint const&) = mac 0x124750, ios 0x15f55c;
auto convertToWorldSpace(cocos2d::CCPoint const&) = mac 0x124790;
cocos2d::CCPoint convertToNodeSpaceAR(cocos2d::CCPoint const& worldPoint) {
return convertToNodeSpace(worldPoint) - getAnchorPointInPoints();
}
cocos2d::CCPoint convertToWorldSpaceAR(cocos2d::CCPoint const& nodePoint) {
return convertToWorldSpace(nodePoint + getAnchorPointInPoints());
}
static cocos2d::CCNode* create() = mac 0x1230a0;
virtual auto draw() = mac 0x123840, ios 0x15e974;
auto getActionByTag(int) = mac 0x123ee0;
@ -739,7 +759,7 @@ class cocos2d::CCNode {
virtual ~CCNode() = mac 0x122750, ios 0x6c98, win 0x5ea40;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCNodeRGBA {
CCNodeRGBA() = mac 0x124b30, win 0x5e9d0;
virtual ~CCNodeRGBA() = mac 0x124bb0, ios 0x15f748, win 0x5ebb0;
@ -758,7 +778,7 @@ class cocos2d::CCNodeRGBA {
virtual auto setCascadeColorEnabled(bool) = mac 0x125340, ios 0x15fb80;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCObject {
CCObject() = mac 0x250ca0, ios 0x43864, win 0x69230;
auto acceptVisitor(cocos2d::CCDataVisitor&) = mac 0x250f30, ios 0x439f0;
@ -774,7 +794,7 @@ class cocos2d::CCObject {
~CCObject() = mac 0x250d20, ios 0x6ac0, win 0x69270;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCParticleSystem {
CCParticleSystem() = win 0xb6650;
virtual ~CCParticleSystem() = win 0xb68e0;
@ -785,7 +805,7 @@ class cocos2d::CCParticleSystem {
auto stopSystem() = mac 0x46bd10;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCParticleSystemQuad {
CCParticleSystemQuad() = win 0xb9bd0;
virtual ~CCParticleSystemQuad() = win 0xb9c10;
@ -794,18 +814,18 @@ class cocos2d::CCParticleSystemQuad {
auto setupVBO();
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCPoolManager {
auto pop() = mac 0x214620;
static cocos2d::CCPoolManager* sharedPoolManager() = mac 0x2142c0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCRemoveSelf {
static cocos2d::CCRemoveSelf* create(bool) = mac 0x454700;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCRenderTexture {
auto begin() = mac 0x35ce10;
auto end() = mac 0x35d2c0;
@ -815,45 +835,45 @@ class cocos2d::CCRenderTexture {
auto beginWithClear(float r, float g, float b, float a) = mac 0x35d010;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCRepeat {
static cocos2d::CCRepeat* create(cocos2d::CCFiniteTimeAction*, unsigned int) = mac 0x1f3230;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCRepeatForever {
static cocos2d::CCRepeatForever* create(cocos2d::CCActionInterval*) = mac 0x1f3920;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCRotateBy {
static cocos2d::CCRotateBy* create(float, float) = mac 0x1f4c50;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCRotateTo {
static cocos2d::CCRotateTo* create(float, float) = mac 0x1f47b0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCScaleTo {
static cocos2d::CCScaleTo* create(float, float) = mac 0x1f6ff0;
static cocos2d::CCScaleTo* create(float, float, float) = mac 0x1f70f0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCScaleBy {
static cocos2d::CCScaleTo* create(float, float) = mac 0x1f73c0;
static cocos2d::CCScaleTo* create(float, float, float) = mac 0x1f7480;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCScene {
static cocos2d::CCScene* create() = mac 0x13c140, ios 0x163070;
auto getHighestChildZ() = mac 0x13c200, ios 0x1630e4;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCScheduler {
auto scheduleSelector(cocos2d::SEL_SCHEDULE, cocos2d::CCObject*, float, unsigned int, float, bool) = mac 0x242b20;
void scheduleSelector(cocos2d::SEL_SCHEDULE selector, cocos2d::CCObject* target, float interval, bool paused) {
@ -868,12 +888,12 @@ class cocos2d::CCScheduler {
virtual void update(float delta) = mac 0x2446d0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCSequence {
static auto createWithVariableList(cocos2d::CCFiniteTimeAction*, va_list) = mac 0x1f2910;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCSet {
CCSet() = mac 0x45ad80, ios 0x10e870, win 0x699e0;
static auto create() = mac 0x45b0b0;
@ -888,14 +908,14 @@ class cocos2d::CCSet {
}
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCShaderCache {
static auto sharedShaderCache() = mac 0xe6d10;
auto programForKey(const char*) = mac 0xe7d40;
void reloadDefaultShaders();
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCSprite {
virtual ~CCSprite() = mac 0x133430, ios 0x15b92c, win 0xd2f90;
virtual auto init() = mac 0x132ef0, ios 0x15b488;
@ -970,7 +990,7 @@ class cocos2d::CCSprite {
void setFlipY(bool) = mac 0x134c30;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCSpriteBatchNode {
static cocos2d::CCSpriteBatchNode* create(char const*, unsigned int) = mac 0xbb540;
static auto createWithTexture(cocos2d::CCTexture2D*, unsigned int) = mac 0xbb310;
@ -995,28 +1015,28 @@ class cocos2d::CCSpriteBatchNode {
virtual auto getBlendFunc() = mac 0xbcd50, ios 0x131a60;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCSpriteFrame {
static auto createWithTexture(cocos2d::CCTexture2D*, cocos2d::CCRect const&, bool, cocos2d::CCPoint const&, cocos2d::CCSize const&) = mac 0x1ac7f0;
static auto createWithTexture(cocos2d::CCTexture2D*, cocos2d::CCRect const&) = mac 0x1ac5c0;
auto getTexture() = mac 0x1ad250;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCSpriteFrameCache {
auto addSpriteFramesWithFile(char const*) = mac 0x199a10, ios 0x29e818;
static cocos2d::CCSpriteFrameCache* sharedSpriteFrameCache() = mac 0x198970, ios 0x29dc4c;
auto spriteFrameByName(char const*) = mac 0x19a7e0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCStandardTouchHandler {
static cocos2d::CCStandardTouchHandler* handlerWithDelegate(cocos2d::CCTouchDelegate*, int) = mac 0x247f30;
virtual auto initWithDelegate(cocos2d::CCTouchDelegate*, int) = mac 0x247ed0, ios 0x69; // iOS stub
~CCStandardTouchHandler() = mac 0x2482a0, ios 0x6d28, win 0xf5a40;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCString {
// virtual ~CCString() = mac 0x44c590;
virtual auto isEqual(cocos2d::CCObject const*) = mac 0x44c8f0, ios 0x1a1e6c;
@ -1032,7 +1052,7 @@ class cocos2d::CCString {
auto intValue() const = mac 0x44c780, ios 0x1a1ca8;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTargetedTouchHandler {
static cocos2d::CCTargetedTouchHandler* handlerWithDelegate(cocos2d::CCTouchDelegate*, int, bool) = mac 0x248010;
auto initWithDelegate(cocos2d::CCTouchDelegate*, int, bool) = mac 0x2480f0, ios 0x69; // iOS stub
@ -1040,7 +1060,7 @@ class cocos2d::CCTargetedTouchHandler {
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTexture2D {
CCTexture2D() = mac 0x246280, win 0xe9300;
~CCTexture2D() = mac 0x246350, win 0xe93f0;
@ -1065,7 +1085,7 @@ class cocos2d::CCTexture2D {
auto setTexParameters(cocos2d::_ccTexParams*) = mac 0x247980;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTextureAtlas {
CCTextureAtlas() = win 0xea680;
virtual ~CCTextureAtlas() = win 0xea6c0;
@ -1073,12 +1093,12 @@ class cocos2d::CCTextureAtlas {
auto mapBuffers();
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTextFieldTTF {
static auto textFieldWithPlaceHolder(char const*, char const*, float) = mac 0x126220;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTextureCache {
auto addImage(char const*, bool) = mac 0x358120, ios 0xa8388;
auto textureForKey(char const*) = mac 0x359050;
@ -1086,17 +1106,17 @@ class cocos2d::CCTextureCache {
static cocos2d::CCTextureCache* sharedTextureCache() = mac 0x356e00, ios 0xa81ec;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTime {
static auto gettimeofdayCocos2d(cocos2d::cc_timeval*, void*) = mac 0x19eac0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTintTo {
static cocos2d::CCTintTo* create(float, unsigned char, unsigned char, unsigned char) = mac 0x1f82a0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTouch {
auto getDelta() const = mac 0x38340;
auto getLocationInView() const = mac 0x38250;
@ -1106,7 +1126,7 @@ class cocos2d::CCTouch {
auto getStartLocation() const = mac 0x38310;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTouchDispatcher {
auto addTargetedDelegate(cocos2d::CCTouchDelegate*, int, bool) = mac 0x281180;
auto addStandardDelegate(cocos2d::CCTouchDelegate*, int) = mac 0x281060;
@ -1116,7 +1136,7 @@ class cocos2d::CCTouchDispatcher {
void touches(cocos2d::CCSet*, cocos2d::CCEvent*, unsigned int) = mac 0x281a60;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTouchHandler {
virtual auto initWithDelegate(cocos2d::CCTouchDelegate*, int) = mac 0x247d10, ios 0x69f8;
auto getPriority() = mac 0x247c20;
@ -1126,19 +1146,21 @@ class cocos2d::CCTouchHandler {
~CCTouchHandler() = mac 0x247de0, ios 0x6ac0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTransitionFade {
static cocos2d::CCTransitionFade* create(float, cocos2d::CCScene*) = mac 0x8ea30, ios 0x12c244;
virtual bool initWithDuration(float t, cocos2d::CCScene* scene, cocos2d::ccColor3B const& color) = mac 0x8e930;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::ZipUtils {
static auto compressString(gd::string, bool, int) = mac 0xe9a50;
static auto decompressString(gd::string, bool, int) = mac 0xea380;
static auto decompressString2(unsigned char* data, bool decrypt, int size, int decryptionKey);
static int ccDeflateMemory(unsigned char*, unsigned int, unsigned char**) = mac 0xe9cf0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::extension::CCControl {
CCControl() {}
virtual bool init() = mac 0x1a71c0;
@ -1162,27 +1184,34 @@ class cocos2d::extension::CCControl {
auto isSelected() = mac 0x1a7ec0;
}
[[link(win)]]
class cocos2d::extension::CCControlColourPicker {
CCControlColourPicker() {}
[[link(win)]]
CCControlColourPicker() {}
[[link(win)]]
~CCControlColourPicker() = mac 0x1aae30;
auto setColorValue(cocos2d::_ccColor3B const&) = mac 0x1aac10;
[[link(win, android)]]
auto setColorValue(cocos2d::_ccColor3B const&) = mac 0x1aac10;
[[link(win)]]
auto ccTouchBegan(cocos2d::CCTouch*, cocos2d::CCEvent*) = mac 0x1aae10;
[[link(win)]]
auto init() = mac 0x1aa400;
[[link(win)]]
static auto colourPicker() = mac 0x1aaa30;
[[link(win)]]
cocos2d::ccColor3B const& getColorValue() const {
return m_rgb;
return m_rgb;
}
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::extension::CCControlUtils {
static cocos2d::extension::HSV HSVfromRGB(cocos2d::extension::RGBA) = mac 0x1e6750;
static cocos2d::extension::RGBA RGBfromHSV(cocos2d::extension::HSV) = mac 0x1e6850;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::extension::CCScale9Sprite {
CCScale9Sprite() = mac 0x211330;
static cocos2d::extension::CCScale9Sprite* create(char const*) = mac 0x2130d0;
@ -1228,10 +1257,11 @@ class cocos2d::extension::CCScale9Sprite {
virtual auto setSpriteFrame(cocos2d::CCSpriteFrame*) = mac 0x213a90;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::extension::CCScrollView {
CCScrollView() = mac 0x214800;
virtual ~CCScrollView() = mac 0x214c30;
static auto create(cocos2d::CCSize, cocos2d::CCNode*) = mac 0x214cd0;
virtual auto init() = mac 0x214fb0;
virtual auto setContentSize(cocos2d::CCSize const&) = mac 0x215eb0;
virtual auto getContentSize() const = mac 0x215e90;
@ -1247,7 +1277,7 @@ class cocos2d::extension::CCScrollView {
virtual auto setTouchEnabled(bool) = mac 0x215250;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d {
static auto FNTConfigLoadFile(char const*) = mac 0x344f10;
static auto ccGLUseProgram(GLuint) = mac 0x1ae540;
@ -1255,7 +1285,9 @@ class cocos2d {
static auto ccDrawSolidRect(cocos2d::CCPoint, cocos2d::CCPoint, cocos2d::_ccColor4F) = mac 0xecf00;
static auto ccGLEnableVertexAttribs(unsigned int) = mac 0x1ae740;
static auto ccGLBindTexture2D(GLuint) = mac 0x1ae610;
static auto ccGLBindTexture2DN(GLuint, GLuint) = mac 0x1ae650;
static float ccpDistance(cocos2d::CCPoint const&, cocos2d::CCPoint const&) = mac 0x1aaf90;
static auto ccDrawLine(cocos2d::CCPoint const&, cocos2d::CCPoint const&) = mac 0xeccc0;
static void ccDrawPoly(cocos2d::CCPoint const*, unsigned int, bool) = mac 0xed0a0;
static void ccDrawColor4B(GLubyte, GLubyte, GLubyte, GLubyte) = mac 0xeddd0;
static void CCMessageBox(const char* msg, const char* title) = mac 0xbabc0;
@ -1274,7 +1306,7 @@ class DS_Dictionary {
auto getObjectForKey(char const*) = mac 0xC4BB0;
}
[[link(win)]]
[[link(win, android)]]
class pugi::xml_document {
xml_document() = mac 0x393a80;
~xml_document() = mac 0x393b50;

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
set(GEODE_CLI_MINIMUM_VERSION 1.0.5)
# Find Geode CLI
if (NOT DEFINED GEODE_CLI)
if (NOT DEFINED GEODE_CLI OR GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
find_program(GEODE_CLI NAMES geode.exe geode-cli.exe geode geode-cli PATHS ${CLI_PATH})
endif()
@ -51,6 +51,17 @@ function(setup_geode_mod proname)
# Link Geode to the mod
target_link_libraries(${proname} geode-sdk)
if (ANDROID)
if (CMAKE_BUILD_TYPE STREQUAL "Release")
add_custom_command(
TARGET "${PROJECT_NAME}" POST_BUILD
DEPENDS "${PROJECT_NAME}"
COMMAND $<$<CONFIG:release>:${CMAKE_STRIP}>
ARGS -S $<TARGET_FILE:${PROJECT_NAME}>
)
endif()
endif()
if (GEODE_DISABLE_CLI_CALLS)
message("Skipping setting up geode mod ${proname}")
return()
@ -59,7 +70,7 @@ function(setup_geode_mod proname)
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
message(FATAL_ERROR
"setup_geode_mod called, but Geode CLI was not found - "
"Please install CLI: https://docs.geode-sdk.org/info/installcli/"
"Please install CLI: https://docs.geode-sdk.org/"
)
return()
endif()
@ -130,9 +141,8 @@ function(setup_geode_mod proname)
set(HAS_HEADERS Off)
endif()
# todo: figure out how to either not make cmake shit itself and print out --binary path/to/dll "" or
# make cli not shit itself when it sees that
if (HAS_HEADERS)
if (HAS_HEADERS AND WIN32)
# this adds the .lib file on windows, which is needed for linking with the headers
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR}
@ -188,6 +198,9 @@ function(setup_geode_mod proname)
elseif (APPLE)
file(GLOB libs ${dir}/*.dylib)
list(APPEND libs_to_link ${libs})
elseif (ANDROID)
file(GLOB libs ${dir}/*.so)
list(APPEND libs_to_link ${libs})
else()
message(FATAL_ERROR "Library extension not defined on this platform")
endif()
@ -294,9 +307,10 @@ function(package_geode_resources_now proname src dest header_dest)
# "LOADER_RESOURCE_FILES {\n"
)
list(APPEND HASHED_EXTENSIONS ".png")
list(APPEND HASHED_EXTENSIONS ".mp3")
list(APPEND HASHED_EXTENSIONS ".ogg")
# yeah don't think we need to check too many stuff
# list(APPEND HASHED_EXTENSIONS ".png")
# list(APPEND HASHED_EXTENSIONS ".mp3")
# list(APPEND HASHED_EXTENSIONS ".ogg")
list(APPEND HASHED_EXTENSIONS ".md")
foreach(file ${RESOURCE_FILES})
@ -307,6 +321,8 @@ function(package_geode_resources_now proname src dest header_dest)
if (NOT FILE_NAME STREQUAL ".geode_cache" AND NOT FILE_SHOULD_HASH EQUAL -1)
file(SHA256 ${file} COMPUTED_HASH)
file(SIZE ${file} FILE_SIZE)
message(STATUS "Hashed ${file} to ${COMPUTED_HASH} (${FILE_SIZE} bytes)")
list(APPEND HEADER_FILE "\t{ \"${FILE_NAME}\", \"${COMPUTED_HASH}\" },\n")
# list(APPEND HEADER_FILE "\t\"${FILE_NAME}\",\n")

View file

@ -51,6 +51,10 @@ elseif (GEODE_TARGET_PLATFORM STREQUAL "MacOS")
${GEODE_LOADER_PATH}/include/link/libfmod.dylib
)
target_compile_definitions(${PROJECT_NAME} INTERFACE
-DCommentType=CommentTypeDummy
)
set(GEODE_PLATFORM_BINARY "Geode.dylib")
elseif (GEODE_TARGET_PLATFORM STREQUAL "Win32")
@ -78,10 +82,10 @@ elseif (GEODE_TARGET_PLATFORM STREQUAL "Android")
)
target_link_libraries(${PROJECT_NAME} INTERFACE
${GEODE_LOADER_PATH}/include/link/android/libcocos2dcpp.so
${GEODE_LOADER_PATH}/include/link/android/libcurl.a
${GEODE_LOADER_PATH}/include/link/android/libssl.a
${GEODE_LOADER_PATH}/include/link/android/libcrypto.a
${GEODE_LOADER_PATH}/include/link/android/libcocos2dcpp.so
log
)

View file

@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
project(Codegen LANGUAGES C CXX)
include(../cmake/CPM.cmake)
CPMAddPackage("gh:fmtlib/fmt#9.1.0")
CPMAddPackage("gh:geode-sdk/Broma#460f82d")
CPMAddPackage("gh:geode-sdk/Broma#38a3bba")
file(GLOB SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp

View file

@ -1,4 +1,5 @@
#include "Shared.hpp"
#include "AndroidSymbol.hpp"
namespace {
namespace format_strings {
@ -78,6 +79,8 @@ std::string generateAddressHeader(Root const& root) {
output += format_strings::address_begin;
for (auto& f : root.functions) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
std::string address_str;
if (codegen::getStatus(f) == BindStatus::Binded) {
@ -103,6 +106,8 @@ std::string generateAddressHeader(Root const& root) {
for (auto& c : root.classes) {
for (auto& field : c.fields) {
if (codegen::getStatus(field) == BindStatus::Missing) continue;
std::string address_str;
auto fn = field.get_as<FunctionBindField>();
@ -111,8 +116,7 @@ std::string generateAddressHeader(Root const& root) {
continue;
}
if (codegen::getStatus(field) == BindStatus::NeedsBinding || codegen::platformNumber(field)) {
if (codegen::getStatus(field) == BindStatus::NeedsBinding || codegen::platformNumber(field) != -1) {
if (is_cocos_class(field.parent) && codegen::platform == Platform::Windows) {
address_str = fmt::format("base::getCocos() + 0x{:x}", codegen::platformNumber(fn->binds));
}
@ -120,6 +124,13 @@ std::string generateAddressHeader(Root const& root) {
address_str = fmt::format("base::get() + 0x{:x}", codegen::platformNumber(fn->binds));
}
}
else if (codegen::shouldAndroidBind(fn)) {
auto const mangled = generateAndroidSymbol(c, fn);
address_str = fmt::format( // thumb
"reinterpret_cast<uintptr_t>(dlsym(dlopen(\"libcocos2dcpp.so\", RTLD_NOW), \"{}\"))",
mangled
);
}
else if (codegen::getStatus(field) == BindStatus::Binded && fn->prototype.type == FunctionType::Normal) {
address_str = fmt::format(
"addresser::get{}Virtual(Resolve<{}>::func(&{}::{}))",

View file

@ -0,0 +1,155 @@
#pragma once
#include "Shared.hpp"
#include <unordered_set>
#include <vector>
#include <string>
#include <string_view>
std::string mangleIdent(std::string_view str, bool ne = true) {
if (str.find("::") != -1) {
std::string result = ne ? "N" : "";
auto s = str;
do {
const auto i = s.find("::");
const auto t = s.substr(0, i);
result += std::to_string(t.size()) + std::string(t);
if (i == -1) s = "";
else
s = s.substr(i + 2);
} while(s.size());
return result + (ne ? "E" : "");
} else {
return std::to_string(str.size()) + std::string(str);
}
};
std::string intToString(unsigned int value, unsigned int radix) {
static constexpr char base36[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::string result;
do {
unsigned int remainder = value % radix;
value /= radix;
result.insert(result.begin(), base36[remainder]);
} while (value);
return result;
}
std::string lookForSeen(std::vector<std::string>& seen, std::string mangled) {
for (int i = 0; i < seen.size(); ++i) {
if (seen[i] == mangled) {
if (i == 0) return "S_";
// yes, its base 36
return "S" + intToString(i - 1, 36) + "_";
}
}
return "";
}
std::string subsSeen(std::vector<std::string>& seen, std::string mangled, bool subs) {
if (!subs) return mangled;
if (mangled.empty()) return mangled;
if (auto x = lookForSeen(seen, mangled); !x.empty()) return x;
seen.push_back(mangled);
return mangled;
}
std::string mangleType(std::vector<std::string>& seen, std::string name, bool subs = true) {
if (name == "int") return "i";
if (name == "float") return "f";
if (name == "bool") return "b";
if (name == "char") return "c";
if (name == "gd::string") return "Ss";
if (name == "cocos2d::ccColor3B") return mangleType(seen, "cocos2d::_ccColor3B", subs);
// too lazy
if (name == "gd::map<gd::string, gd::string>") return "St3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE";
if (name == "cocos2d::SEL_MenuHandler") {
const auto a = mangleType(seen, "cocos2d::CCObject", subs);
const auto b = mangleType(seen, "cocos2d::CCObject*", subs);
const auto fnptr = subsSeen(seen, "Fv" + b + "E", subs);
return subsSeen(seen, "M" + a + fnptr, subs);
}
if (name.find('*') == name.size() - 1) {
auto inner = mangleType(seen, name.substr(0, name.size() - 1), false);
if (auto x = lookForSeen(seen, "P" + inner); !x.empty()) return x;
inner = mangleType(seen, name.substr(0, name.size() - 1), subs);
return subsSeen(seen, "P" + inner, subs);
}
if (name.find('&') == name.size() - 1) {
auto inner = mangleType(seen, name.substr(0, name.size() - 1), false);
if (auto x = lookForSeen(seen, "R" + inner); !x.empty()) return x;
inner = mangleType(seen, name.substr(0, name.size() - 1), subs);
return subsSeen(seen, "R" + inner, subs);
}
if (auto i = name.find("const"); i != -1) {
std::string inner;
// at the end of the name
if (i == name.size() - 5) {
inner = mangleType(seen, name.substr(0, i - 1));
} else if (i == 0) {
inner = mangleType(seen, name.substr(6));
} else {
inner = "v";
std::cout << "um " << name << std::endl;
}
return subsSeen(seen, "K" + inner, subs);
}
if (name.find("::") != -1) {
std::string result = "";
std::string substituted = "";
auto s = name;
do {
const auto i = s.find("::");
const auto t = s.substr(0, i);
auto part = std::to_string(t.size()) + std::string(t);
if (auto x = lookForSeen(seen, result + part); !x.empty()) {
substituted = x;
} else {
substituted = subsSeen(seen, substituted + part, subs);
}
result += part;
if (i == -1) s = "";
else s = s.substr(i + 2);
} while(s.size());
if (substituted.size() == 3 && substituted[0] == 'S')
return substituted;
return "N" + substituted + "E";
} else {
return subsSeen(seen, mangleIdent(name), subs);
}
};
std::string generateAndroidSymbol(const Class& clazz, const FunctionBindField* fn) {
auto& decl = fn->prototype;
std::string mangledSymbol;
switch (decl.type) {
case FunctionType::Ctor:
mangledSymbol = "_ZN" + mangleIdent(clazz.name, false) + "C2E";
break;
case FunctionType::Dtor:
mangledSymbol = "_ZN" + mangleIdent(clazz.name, false) + "D2E";
break;
default:
mangledSymbol = "_Z" + mangleIdent(clazz.name + "::" + decl.name);
break;
}
if (decl.args.empty()) {
mangledSymbol += "v";
} else {
std::vector<std::string> seen;
static constexpr auto firstPart = [](std::string_view str, std::string_view sep) {
return str.substr(0, str.find(sep));
};
// this is S_
seen.push_back(mangleIdent(firstPart(clazz.name, "::")));
for (auto& [ty, _] : decl.args) {
mangledSymbol += mangleType(seen, ty.name);
}
}
return mangledSymbol;
}

View file

@ -131,6 +131,8 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
single_output += format_strings::class_includes;
for (auto& f : root.functions) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
FunctionProto const* fb = &f.prototype;
char const* used_format = format_strings::function_definition;
@ -185,7 +187,8 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
single_output += fmt::format(::format_strings::class_start,
fmt::arg("class_name", cls.name),
fmt::arg("base_classes", supers)
fmt::arg("base_classes", supers)//,
// fmt::arg("hidden", str_if("GEODE_HIDDEN ", (codegen::platform & (Platform::Mac | Platform::iOS)) != Platform::None))
);
// what.
@ -201,6 +204,8 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
bool unimplementedField = false;
for (auto field : cls.fields) {
if (codegen::getStatus(field) == BindStatus::Missing) continue;
MemberFunctionProto* fb;
char const* used_format = format_strings::function_definition;
@ -220,9 +225,13 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
} else if (auto p = field.get_as<PadField>()) {
auto hardcode = codegen::platformNumber(p->amount);
if (hardcode) {
if (hardcode > 0) {
single_output += fmt::format(format_strings::pad_definition, fmt::arg("hardcode", hardcode));
} else {
}
else if (hardcode == 0) {
single_output += " // no padding\n";
}
else {
unimplementedField = true;
}
continue;
@ -233,7 +242,7 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
} else if (auto fn = field.get_as<FunctionBindField>()) {
fb = &fn->prototype;
if (!codegen::platformNumber(fn->binds)) {
if (codegen::platformNumber(fn->binds) == -1 && codegen::getStatus(field) != BindStatus::Binded) {
used_format = format_strings::error_definition;
if (fb->type != FunctionType::Normal)

View file

@ -57,7 +57,7 @@ namespace geode::modifier {{
std::string generateModifyHeader(Root const& root, ghc::filesystem::path const& singleFolder) {
std::string output;
for (auto& c : root.classes) {
for (auto& c : root.classes) {
if (c.name == "cocos2d") continue;
std::string filename = (codegen::getUnqualifiedClassName(c.name) + ".hpp");
@ -83,6 +83,8 @@ std::string generateModifyHeader(Root const& root, ghc::filesystem::path const&
std::string statics;
std::set<std::string> used;
for (auto& f : c.fields) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
if (auto fn = f.get_as<FunctionBindField>()) {
if (fn->prototype.type == FunctionType::Normal && !used.count(fn->prototype.name)) {
used.insert(fn->prototype.name);
@ -102,13 +104,15 @@ std::string generateModifyHeader(Root const& root, ghc::filesystem::path const&
// modify
for (auto& f : c.fields) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
auto fn = f.get_as<FunctionBindField>();
if (!fn) {
continue;
}
if (codegen::getStatus(f) == BindStatus::NeedsBinding || codegen::platformNumber(f)) {
if (codegen::getStatus(f) == BindStatus::NeedsBinding || codegen::platformNumber(f) != -1) {
}
else if (codegen::getStatus(f) == BindStatus::Binded && fn->prototype.type == FunctionType::Normal) {

View file

@ -51,7 +51,8 @@ inline bool is_cocos_class(std::string const& str) {
enum class BindStatus {
Binded,
NeedsBinding,
Unbindable
Unbindable,
Missing,
};
struct codegen_error : std::runtime_error {
@ -91,7 +92,7 @@ namespace codegen {
inline Platform platform;
inline uintptr_t platformNumberWithPlatform(Platform p, PlatformNumber const& pn) {
inline ptrdiff_t platformNumberWithPlatform(Platform p, PlatformNumber const& pn) {
switch (p) {
case Platform::Mac: return pn.mac;
case Platform::Windows: return pn.win;
@ -102,7 +103,7 @@ namespace codegen {
}
}
inline uintptr_t platformNumber(PlatformNumber const& p) {
inline ptrdiff_t platformNumber(PlatformNumber const& p) {
return platformNumberWithPlatform(codegen::platform, p);
}
@ -114,21 +115,35 @@ namespace codegen {
}
inline BindStatus getStatusWithPlatform(Platform p, Field const& field) {
if ((field.missing & p) != Platform::None) return BindStatus::Missing;
if (auto fn = field.get_as<FunctionBindField>()) {
if ((fn->links & p) != Platform::None) return BindStatus::Binded;
if (platformNumberWithPlatform(p, fn->binds)) return BindStatus::NeedsBinding;
if ((field.links & p) != Platform::None) return BindStatus::Binded;
if (platformNumberWithPlatform(p, fn->binds) != -1) return BindStatus::NeedsBinding;
}
return BindStatus::Unbindable;
}
inline BindStatus getStatusWithPlatform(Platform p, Function const& f) {
if ((f.missing & p) != Platform::None) return BindStatus::Missing;
if ((f.links & p) != Platform::None) return BindStatus::Binded;
if (platformNumberWithPlatform(p, f.binds)) return BindStatus::NeedsBinding;
if (platformNumberWithPlatform(p, f.binds) != -1) return BindStatus::NeedsBinding;
return BindStatus::Unbindable;
}
inline bool shouldAndroidBind(const FunctionBindField* fn) {
if (codegen::platform == Platform::Android) {
if (fn->prototype.type != FunctionType::Normal) return true;
for (auto& [type, name] : fn->prototype.args) {
if (can_find(type.name, "gd::")) return true;
}
}
return false;
}
inline BindStatus getStatus(Field const& field) {
return getStatusWithPlatform(codegen::platform, field);
}

View file

@ -135,6 +135,8 @@ std::string generateBindingSource(Root const& root) {
std::string output(format_strings::source_start);
for (auto& f : root.functions) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
if (codegen::getStatus(f) != BindStatus::NeedsBinding) {
continue;
}
@ -153,11 +155,13 @@ std::string generateBindingSource(Root const& root) {
for (auto& c : root.classes) {
for (auto& f : c.fields) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
if (auto i = f.get_as<InlineField>()) {
// yeah there are no inlines on cocos
}
else if (auto fn = f.get_as<OutOfLineField>()) {
if ((c.links & codegen::platform) != Platform::None) {
if (is_cocos_class(c.name) && (c.links & codegen::platform) != Platform::None) {
continue;
}
if (codegen::getStatus(f) != BindStatus::Unbindable) {
@ -193,15 +197,15 @@ std::string generateBindingSource(Root const& root) {
if (
codegen::getStatus(f) == BindStatus::Unbindable &&
!codegen::platformNumber(fn->binds) &&
codegen::platformNumber(fn->binds) == -1 &&
fn->prototype.is_virtual && fn->prototype.type != FunctionType::Dtor
) {
used_declare_format = format_strings::declare_virtual_error;
}
else if (codegen::getStatus(f) != BindStatus::NeedsBinding) {
else if (codegen::getStatus(f) != BindStatus::NeedsBinding && !codegen::shouldAndroidBind(fn)) {
continue;
}
if (!used_declare_format) {
switch (fn->prototype.type) {

View file

@ -16,5 +16,5 @@ namespace geode {
namespace {
// to make sure the instance is set into the sharedMod<> in load time
static auto mod = geode::getMod();
static auto mod = geode::getMod();
}

View file

@ -27,10 +27,13 @@ execute_process(
)
# Package info file for internal representation
set(GEODE_RESOURCES_PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources)
configure_file(resources/mod.json.in ${CMAKE_CURRENT_SOURCE_DIR}/resources/mod.json)
file(READ resources/mod.json LOADER_MOD_JSON)
configure_file(${GEODE_ROOT_PATH}/VERSION ${CMAKE_CURRENT_SOURCE_DIR}/resources/version COPYONLY)
configure_file(${GEODE_ROOT_PATH}/CHANGELOG.md ${CMAKE_CURRENT_SOURCE_DIR}/resources/changelog.md COPYONLY)
configure_file(${GEODE_ROOT_PATH}/VERSION ${GEODE_RESOURCES_PATH}/version COPYONLY)
configure_file(${GEODE_RESOURCES_PATH}/about.md.in ${GEODE_RESOURCES_PATH}/about.md NEWLINE_STYLE LF)
configure_file(${GEODE_ROOT_PATH}/CHANGELOG.md ${GEODE_RESOURCES_PATH}/changelog.md NEWLINE_STYLE LF)
configure_file(${GEODE_RESOURCES_PATH}/support.md.in ${GEODE_RESOURCES_PATH}/support.md NEWLINE_STYLE LF)
configure_file(src/internal/about.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/src/internal/about.hpp)
# Source files
@ -146,6 +149,11 @@ if (APPLE)
file(COPY ${GEODE_LOADER_PATH}/include/link/libfmod.dylib DESTINATION ${GEODE_BIN_PATH}/nightly)
endif()
if (ANDROID)
# needed to define some opengl functions
target_link_libraries(${PROJECT_NAME} EGL)
endif()
target_include_directories(${PROJECT_NAME} PRIVATE
src/
src/loader/

View file

@ -81,8 +81,7 @@ namespace geode {
GEODE_IOS(GEODE_FILL_CONSTRUCTOR(Class_, 0){}) \
GEODE_WINDOWS(Class_(geode::CutoffConstructorType, size_t fill) \
: Class_() {}) \
GEODE_ANDROID(Class_(geode::CutoffConstructorType, size_t fill) \
: Class_() {})
GEODE_ANDROID(GEODE_FILL_CONSTRUCTOR(Class_, 0){})
#define GEODE_CUTOFF_CONSTRUCTOR_COCOS(Class_, Base_) \
GEODE_MACOS(Class_(geode::CutoffConstructorType, size_t fill) \
@ -91,8 +90,8 @@ namespace geode {
: Base_(geode::CutoffConstructor, fill){}) \
GEODE_WINDOWS(Class_(geode::CutoffConstructorType, size_t fill) \
: Class_() {}) \
GEODE_ANDROID(Class_(geode::CutoffConstructorType, size_t fill) \
: Class_() {})
GEODE_ANDROID(Class_(geode::CutoffConstructorType, size_t fill) \
: Base_(geode::CutoffConstructor, fill){})
#define GEODE_CUTOFF_CONSTRUCTOR_GD(Class_, Base_) \
GEODE_WINDOWS(Class_(geode::CutoffConstructorType, size_t fill) \
@ -106,7 +105,8 @@ namespace geode {
#define GEODE_CUTOFF_CONSTRUCTOR_CUTOFF(Class_, Base_) \
GEODE_WINDOWS(GEODE_FILL_CONSTRUCTOR(Class_, sizeof(Base_)) : Base_(){}) \
GEODE_ANDROID(GEODE_FILL_CONSTRUCTOR(Class_, sizeof(Base_)) : Base_(){}) \
GEODE_ANDROID(Class_(geode::CutoffConstructorType, size_t fill) \
: Base_(geode::CutoffConstructor, fill){}) \
GEODE_MACOS(Class_(geode::CutoffConstructorType, size_t fill) \
: Base_(geode::CutoffConstructor, fill){}) \
GEODE_IOS(Class_(geode::CutoffConstructorType, size_t fill) \

View file

@ -1,5 +1,7 @@
#pragma once
#include "DefaultInclude.hpp"
// thanks pie
enum class SearchType {
Search = 0,
@ -54,6 +56,7 @@ enum class GameObjectType {
MiniSizePortal = 18,
UfoPortal = 19,
Modifier = 20,
Breakable = 21,
SecretCoin = 22,
DualPortal = 23,
SoloPortal = 24,
@ -109,6 +112,16 @@ enum class CommentError {
enum class BackupAccountError {
};
// Thanks cocoa!
#ifdef GEODE_IS_MACOS
#undef CommentType
#endif
enum class CommentType {
Level = 0,
Account = 1,
};
enum class BoomListType {
Default = 0x0,
User = 0x2,
@ -295,7 +308,9 @@ enum class GJRewardType
{
Unknown = 0x0,
Small = 0x1,
Large = 0x2
Large = 0x2,
SmallTreasure = 0x3,
LargeTreasure = 0x4
};
enum class IconType {

View file

@ -405,8 +405,11 @@ namespace gd {
}
~vector() {
for (auto i = m_start; i != m_finish; ++i) {
delete i;
if (m_start) {
for (auto& x : *this) {
x.~T();
}
delete m_start;
}
}

View file

@ -700,12 +700,6 @@ public:
* @param cleanup true if all running actions and callbacks on the child node will be cleanup, false otherwise.
*/
virtual void removeChildByTag(int tag, bool cleanup);
/**
* Removes a child from the container by its ID.
* @param id The ID of the node
* @note Geode addition
*/
void removeChildByID(std::string const& id);
/**
* Removes all children from the container with a cleanup.
*
@ -890,6 +884,13 @@ public:
*/
GEODE_DLL CCNode* getChildByIDRecursive(std::string const& id);
/**
* Removes a child from the container by its ID.
* @param id The ID of the node
* @note Geode addition
*/
GEODE_DLL void removeChildByID(std::string const& id);
/**
* Add a child before a specified existing child
* @param child The node to add. The node may not be a child of another

View file

@ -90,6 +90,7 @@ protected:
bool m_bIsSendCleanupToScene;
public:
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCTransitionScene, CCScene)
/**
* @js ctor
*/
@ -147,6 +148,7 @@ public:
* @js ctor
*/
CCTransitionSceneOriented();
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCTransitionSceneOriented, CCTransitionScene)
/**
* @js NA
* @lua NA
@ -171,6 +173,7 @@ public:
* @js ctor
*/
CCTransitionRotoZoom();
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCTransitionRotoZoom, CCTransitionScene)
/**
* @js NA
* @lua NA
@ -623,6 +626,7 @@ public:
* @js ctor
*/
CCTransitionFade();
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCTransitionFade, CCTransitionScene)
/**
* @js NA
* @lua NA

View file

@ -34,6 +34,8 @@ class CC_DLL CCEGLView : public CCEGLViewProtocol
{
GEODE_FRIEND_MODIFY
public:
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCEGLView, CCEGLViewProtocol)
CCEGLView();
virtual ~CCEGLView();

View file

@ -3,12 +3,8 @@
#include <android/log.h>
#ifdef GEODE_EXPORTING
#define CC_DLL __attribute__((visibility("default")))
#else
#define CC_DLL
#endif
#define ACTUAL_CC_DLL CC_DLL
#define CC_DLL
#define ACTUAL_CC_DLL
#define CC_NO_MESSAGE_PSEUDOASSERT(cond) \
if (!(cond)) { \

View file

@ -3,12 +3,8 @@
#include <assert.h>
#ifdef GEODE_EXPORTING
#define CC_DLL __attribute__((visibility("default")))
#else
#define CC_DLL
#endif
#define ACTUAL_CC_DLL CC_DLL
#define CC_DLL //__attribute__((visibility("hidden")))
#define ACTUAL_CC_DLL
#define CC_ASSERT(cond) assert(cond)

View file

@ -3,12 +3,8 @@
#include <assert.h>
#ifdef GEODE_EXPORTING
#define CC_DLL __attribute__((visibility("default")))
#else
#define CC_DLL
#endif
#define ACTUAL_CC_DLL CC_DLL
#define CC_DLL //__attribute__((visibility("hidden")))
#define ACTUAL_CC_DLL
#if CC_DISABLE_ASSERT > 0

View file

@ -31,7 +31,7 @@ FMOD_RESULT F_API FMOD_File_GetDiskBusy (int *busy);
/*
FMOD System factory functions. Use this to create an FMOD System Instance. below you will see FMOD_System_Init/Close to get started.
*/
FMOD_RESULT F_API FMOD_System_Create (FMOD_SYSTEM **system);
FMOD_RESULT /*F_API*/ FMOD_System_Create (FMOD_SYSTEM **system);
FMOD_RESULT F_API FMOD_System_Release (FMOD_SYSTEM *system);
/*

View file

@ -31,7 +31,8 @@ namespace geode {
LoadFailed,
EnableFailed,
MissingDependency,
PresentIncompatibility
PresentIncompatibility,
UnzipFailed
};
Type type;
std::variant<ghc::filesystem::path, ModMetadata, Mod*> cause;
@ -57,6 +58,7 @@ namespace geode {
public:
// TODO: do we want to expose all of these functions?
static Loader* get();
enum class LoadingState : uint8_t {
@ -70,6 +72,7 @@ namespace geode {
Done
};
// TODO: return void
Result<> saveData();
Result<> loadData();

View file

@ -96,6 +96,11 @@ namespace geode {
*/
std::optional<std::string> match;
/**
* The CCTextInputNode's allowed character filter
*/
std::optional<std::string> filter;
static Result<StringSetting> parse(JsonMaybeObject& obj);
};

View file

@ -5,7 +5,7 @@
namespace geode::modifier {
template <uint32_t Id>
uintptr_t address();
GEODE_HIDDEN uintptr_t address();
Result<tulip::hook::HandlerMetadata> handlerMetadataForAddress(uintptr_t address);
}

View file

@ -1,9 +1,9 @@
#pragma once
#include "../utils/MiniFunction.hpp"
#include "Traits.hpp"
#include <Geode/loader/Loader.hpp>
#include "../utils/MiniFunction.hpp"
#include <cocos2d.h>
#include <vector>
@ -56,7 +56,7 @@ namespace geode::modifier {
using Intermediate = Modify<Parent, Base>;
// Padding used for guaranteeing any member of parents
// will be in between sizeof(Intermediate) and sizeof(Parent)
uintptr_t m_padding;
alignas(std::max(alignof(Base), alignof(uintptr_t))) uintptr_t m_padding;
public:
// the constructor that constructs the fields.
@ -118,7 +118,7 @@ namespace geode::modifier {
reinterpret_cast<std::byte*>(offsetField) - sizeof(Intermediate)
);
}
Parent* self() {
return this->operator Parent*();
}

View file

@ -1,8 +1,6 @@
#pragma once
namespace geode::cast {
using uinthalf_t = uint32_t;
using inthalf_t = int32_t;
struct DummyClass {
virtual ~DummyClass() {}
@ -14,8 +12,10 @@ namespace geode::cast {
struct DummyMultipleClass : DummySingleClass, DummyClass2 {};
struct VtableType;
struct ClassTypeinfoType {
void** m_typeinfoVtable;
VtableType* m_typeinfoVtable;
char const* m_typeinfoName;
};
@ -23,16 +23,17 @@ namespace geode::cast {
ClassTypeinfoType* m_baseClassTypeinfo;
};
#pragma pack(push, 1)
struct MultipleClassSingleEntryType {
ClassTypeinfoType* m_baseClassTypeinfo;
uint8_t m_visibilityFlag;
inthalf_t m_offset;
uint8_t m_padding[sizeof(inthalf_t) - 1];
};
intptr_t m_metadata;
#pragma pack(pop)
uint8_t visibilityFlag() const {
return m_metadata & 0xFF;
}
intptr_t offset() const {
return m_metadata >> 8;
}
};
struct MultipleClassTypeinfoType : ClassTypeinfoType {
uint32_t m_flags;
@ -41,7 +42,7 @@ namespace geode::cast {
};
struct VtableTypeinfoType {
inthalf_t m_offset;
intptr_t m_offset;
ClassTypeinfoType* m_typeinfo;
};
@ -51,36 +52,25 @@ namespace geode::cast {
struct CompleteVtableType : VtableTypeinfoType, VtableType {};
inline void** typeinfoVtableOf(void* ptr) {
auto vftable = *reinterpret_cast<VtableType**>(ptr);
auto typeinfoPtr =
static_cast<VtableTypeinfoType*>(static_cast<CompleteVtableType*>(vftable));
return typeinfoPtr->m_typeinfo->m_typeinfoVtable;
}
inline void* traverseTypeinfoFor(
void* ptr, ClassTypeinfoType const* typeinfo, char const* afterIdent
) {
DummySingleClass dummySingleClass;
DummyMultipleClass dummyMultipleClass;
{
auto optionIdent = typeinfo->m_typeinfoName;
if (std::strcmp(optionIdent, afterIdent) == 0) {
return ptr;
}
}
if (typeinfo->m_typeinfoVtable == typeinfoVtableOf(&dummySingleClass)) {
auto typeinfoVtableName = static_cast<CompleteVtableType*>(typeinfo->m_typeinfoVtable)->m_typeinfo->m_typeinfoName;
if (std::strcmp(typeinfoVtableName, "N10__cxxabiv120__si_class_type_infoE") == 0) {
auto siTypeinfo = static_cast<SingleClassTypeinfoType const*>(typeinfo);
return traverseTypeinfoFor(ptr, siTypeinfo->m_baseClassTypeinfo, afterIdent);
}
else if (typeinfo->m_typeinfoVtable == typeinfoVtableOf(&dummyMultipleClass)) {
else if (std::strcmp(typeinfoVtableName, "N10__cxxabiv121__vmi_class_type_infoE") == 0) {
auto vmiTypeinfo = static_cast<MultipleClassTypeinfoType const*>(typeinfo);
for (int i = 0; i < vmiTypeinfo->m_numBaseClass; ++i) {
auto& entry = vmiTypeinfo->m_baseClasses[i];
auto optionPtr = reinterpret_cast<std::byte*>(ptr) + entry.m_offset;
auto optionPtr = reinterpret_cast<std::byte*>(ptr) + entry.offset();
auto ret = traverseTypeinfoFor(optionPtr, entry.m_baseClassTypeinfo, afterIdent);
if (ret != nullptr) return ret;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <dlfcn.h>
#include "ItaniumCast.hpp"
namespace geode {
struct PlatformInfo {
@ -9,16 +10,6 @@ namespace geode {
}
namespace geode::base {
GEODE_NOINLINE inline uintptr_t get() {
static uintptr_t base = reinterpret_cast<uintptr_t>(dlopen("libcocos2dcpp.so", RTLD_LAZY));
return base;
}
/*GEODE_NOINLINE inline*/ uintptr_t get();
}
namespace geode::cast {
template <class After, class Before>
After typeinfo_cast(Before ptr) {
// yall have symbols smh
return dynamic_cast<After>(ptr);
}
}

View file

@ -13,6 +13,7 @@ namespace geode {
class GEODE_DLL SceneManager {
protected:
cocos2d::CCArray* m_persistedNodes;
cocos2d::CCScene* m_lastScene = nullptr;
bool setup();

View file

@ -0,0 +1,77 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <cocos2d.h>
namespace geode {
enum WrappingMode {
NO_WRAP,
WORD_WRAP,
CUTOFF_WRAP
};
/**
* A class which provides a textarea with proper alignment and some extra features like:
*
* - Max lines
* - Changing all aspects after creation
* - Custom text alignment
* - Configurable and automatic word wrapping
* - Line padding
*
* Contact me on Discord (\@smjs) if you have any questions, suggestions or bugs.
*/
class GEODE_DLL SimpleTextArea : public cocos2d::CCNode {
static SimpleTextArea* create(const std::string& text, const std::string& font = "chatFont.fnt", const float scale = 1);
static SimpleTextArea* create(const std::string& text, const std::string& font, const float scale, const float width);
cocos2d::CCMenu* m_container;
std::string m_font;
std::string m_text;
std::vector<cocos2d::CCLabelBMFont*> m_lines;
cocos2d::ccColor4B m_color;
cocos2d::CCTextAlignment m_alignment;
WrappingMode m_wrappingMode;
size_t m_maxLines;
float m_scale;
float m_lineHeight;
float m_linePadding;
void setFont(const std::string& font);
std::string getFont();
void setColor(const cocos2d::ccColor4B& color);
cocos2d::ccColor4B getColor();
void setAlignment(const cocos2d::CCTextAlignment alignment);
cocos2d::CCTextAlignment getAlignment();
void setWrappingMode(const WrappingMode mode);
WrappingMode getWrappingMode();
void setText(const std::string& text);
std::string getText();
void setMaxLines(const size_t maxLines);
size_t getMaxLines();
void setWidth(const float width);
float getWidth();
void setScale(const float scale) override;
float getScale() override;
void setLinePadding(const float padding);
float getLinePadding();
std::vector<cocos2d::CCLabelBMFont*> getLines();
float getHeight();
float getLineHeight();
private:
static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth);
bool m_shouldUpdate;
bool m_artificialWidth;
SimpleTextArea(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth);
cocos2d::CCLabelBMFont* createLabel(const std::string& text, const float top);
float calculateOffset(cocos2d::CCLabelBMFont* label);
void charIteration(const std::function<cocos2d::CCLabelBMFont*(cocos2d::CCLabelBMFont* line, const char c, const float top)>& overflowHandling);
void updateLinesNoWrap();
void updateLinesWordWrap();
void updateLinesCutoffWrap();
void updateContainer();
virtual void draw() override;
};
}

View file

@ -953,7 +953,7 @@ namespace geode::cocos {
return m_arr ? m_arr->count() : 0;
}
T operator[](size_t index) {
T* operator[](size_t index) {
return static_cast<T*>(m_arr->objectAtIndex(index));
}

View file

@ -240,16 +240,31 @@ namespace geode::utils::file {
/**
* Prompt the user to pick a file using the system's file system picker
* @note Will not work on Android, use the callback version instead
* @param mode Type of file selection prompt to show
* @param options Picker options
*/
GEODE_DLL Result<ghc::filesystem::path> pickFile(PickMode mode, FilePickOptions const& options);
GEODE_DLL void pickFile(
PickMode mode, FilePickOptions const& options,
utils::MiniFunction<void(ghc::filesystem::path)> callback,
utils::MiniFunction<void()> failed = {}
);
/**
* Prompt the user to pick a bunch of files for opening using the system's file system picker
* @note Will not work on Android, use the callback version instead
* @param options Picker options
*/
GEODE_DLL Result<std::vector<ghc::filesystem::path>> pickFiles(FilePickOptions const& options);
GEODE_DLL void pickFiles(
FilePickOptions const& options,
utils::MiniFunction<void(std::vector<ghc::filesystem::path>)> callback,
utils::MiniFunction<void()> failed = {}
);
class GEODE_DLL FileWatchEvent : public Event {
protected:
ghc::filesystem::path m_path;

View file

@ -194,7 +194,7 @@ namespace geode::utils::web {
* URL to fetch from the internet asynchronously
* @param url URL of the data to download. Redirects will be
* automatically followed
* @returns Same AsyncWebRequest
* @returns An AsyncWebResponse object
*/
AsyncWebResponse fetch(std::string const& url);
/**

View file

@ -0,0 +1,29 @@
// FIXME: This fix ends up breaking some of the vanilla text inputs.
#if 0
#include <Geode/modify/CCTextInputNode.hpp>
using namespace geode::prelude;
// rob only uses `CCTextInputNode`s in mostly-flat hierarchies, which still
// happen to work with the weird vanilla code. this fix makes it work even in
// deep hierarchies, because the vanilla code uses `getParent` and manually
// calculates the child location in the world space based on that rather than
// using `convertToNodeSpace`.
struct CCTextInputNodeFix : Modify<CCTextInputNodeFix, CCTextInputNode> {
bool ccTouchBegan(CCTouch* touch, CCEvent* event) {
auto const touchPos = touch->getLocation();
auto const size = this->getContentSize();
auto const pos = this->convertToNodeSpace(touchPos) + m_textField->getAnchorPoint() * size;
if (pos.x < 0 || pos.x > size.width || pos.y < 0 || pos.y > size.height)
return false;
if (m_delegate && !m_delegate->allowTextInput(this))
return false;
this->onClickTrackNode(true);
return true;
}
};
#endif

View file

@ -1,19 +1,20 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_MACOS
#include <Geode/loader/Mod.hpp>
using namespace geode::prelude;
#include <Geode/loader/Mod.hpp>
#include <Geode/modify/Modify.hpp>
$execute {
// this replaces the call to __dynamic_cast with a call to our own
// this is needed because the transitions in cocos uses dynamic cast to check
// layers, which fail on user layers due to typeinfo not matching
(void)Mod::get()->patch(
reinterpret_cast<void*>(base::get() + 0x603948), toByteArray(&cast::typeinfoCastInternal)
);
}
#endif
#if defined(GEODE_IS_MACOS)
(void)Mod::get()->patch(
reinterpret_cast<void*>(base::get() + 0x603948), toByteArray(&cast::typeinfoCastInternal)
);
#elif defined(GEODE_IS_ANDROID)
(void)Mod::get()->addHook(reinterpret_cast<void*>(base::get() + 0x519a8c), &cast::typeinfoCastInternal, "__dynamic_cast");
#endif
}

View file

@ -9,30 +9,38 @@ using namespace geode::prelude;
struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
CCLabelBMFont* m_smallLabel = nullptr;
CCLabelBMFont* m_smallLabel2 = nullptr;
int m_geodeLoadStep = 0;
int m_totalMods = 0;
void updateLoadedModsLabel() {
auto allMods = Loader::get()->getAllMods();
auto count = std::count_if(allMods.begin(), allMods.end(), [&](auto& item) {
return item->isEnabled();
});
auto totalCount = std::count_if(allMods.begin(), allMods.end(), [&](auto& item) {
return item->shouldLoad();
});
auto str = fmt::format("Geode: Loaded {}/{} mods", count, totalCount);
auto str = fmt::format("Geode: Loaded {}/{} mods", count, m_totalMods);
this->setSmallText(str);
auto currentMod = LoaderImpl::get()->m_currentlyLoadingMod;
auto modName = currentMod ? currentMod->getName() : "Unknown";
this->setSmallText2(modName);
}
void setSmallText(std::string const& text) {
m_fields->m_smallLabel->setString(text.c_str());
}
void setSmallText2(std::string const& text) {
m_fields->m_smallLabel2->setString(text.c_str());
}
// hook
bool init(bool fromReload) {
CCFileUtils::get()->updatePaths();
if (!LoadingLayer::init(fromReload)) return false;
m_totalMods = Loader::get()->getAllMods().size();
auto winSize = CCDirector::sharedDirector()->getWinSize();
m_fields->m_smallLabel = CCLabelBMFont::create("", "goldFont.fnt");
@ -41,6 +49,12 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
m_fields->m_smallLabel->setID("geode-small-label");
this->addChild(m_fields->m_smallLabel);
m_fields->m_smallLabel2 = CCLabelBMFont::create("", "goldFont.fnt");
m_fields->m_smallLabel2->setPosition(winSize.width / 2, 15.f);
m_fields->m_smallLabel2->setScale(.45f);
m_fields->m_smallLabel2->setID("geode-small-label");
this->addChild(m_fields->m_smallLabel2);
return true;
}
@ -51,22 +65,29 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
}
else {
this->continueLoadAssets();
this->setSmallText2("");
}
}
void setupLoaderResources() {
log::debug("Verifying Loader Resources");
this->setSmallText("Verifying Loader Resources");
// verify loader resources
if (!LoaderImpl::get()->verifyLoaderResources()) {
this->setSmallText("Downloading Loader Resources");
this->addChild(EventListenerNode<ResourceDownloadFilter>::create(
this, &CustomLoadingLayer::updateResourcesProgress
));
}
else {
this->setSmallText("Loading Loader Resources");
LoaderImpl::get()->updateSpecialFiles();
this->continueLoadAssets();
}
Loader::get()->queueInMainThread([&]() {
if (!LoaderImpl::get()->verifyLoaderResources()) {
log::debug("Downloading Loader Resources");
this->setSmallText("Downloading Loader Resources");
this->addChild(EventListenerNode<ResourceDownloadFilter>::create(
this, &CustomLoadingLayer::updateResourcesProgress
));
}
else {
log::debug("Loading Loader Resources");
this->setSmallText("Loading Loader Resources");
LoaderImpl::get()->updateSpecialFiles();
this->continueLoadAssets();
}
});
}
void updateResourcesProgress(ResourceDownloadEvent* event) {
@ -77,10 +98,12 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
));
},
[&](UpdateFinished) {
log::debug("Downloaded Loader Resources");
this->setSmallText("Downloaded Loader Resources");
this->continueLoadAssets();
},
[&](UpdateFailed const& error) {
log::debug("Failed Loader Resources");
LoaderImpl::get()->platformMessageBox(
"Error updating resources",
error + ".\n"
@ -104,11 +127,11 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
}
int getCurrentStep() {
return m_fields->m_geodeLoadStep + m_loadStep + 1;
return m_fields->m_geodeLoadStep + m_loadStep + 1 + LoaderImpl::get()->m_refreshedModCount;
}
int getTotalStep() {
return 18;
return 18 + m_totalMods;
}
void updateLoadingBar() {

View file

@ -174,6 +174,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
INDEX_UPDATE_NOTIF = Notification::create(
"Updating Index", NotificationIcon::Loading, 0
);
INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
INDEX_UPDATE_NOTIF->show();
Index::get()->update();
}

View file

@ -0,0 +1,78 @@
#include <Geode/loader/Loader.hpp>
#if defined(GEODE_IS_WINDOWS) || defined(GEODE_IS_ANDROID)
using namespace geode::prelude;
#include <Geode/cocos/support/base64.h>
#include "../loader/LoaderImpl.hpp"
void panic(std::string reason) {
LoaderImpl::get()->platformMessageBox("Critical", fmt::format(
"Your save file failed to load (reason: {})\n"
"As to not lose all of your data, the game will now abort.\n"
"Please backup your save files and try opening the game again, it might work.\n"
"Please contact the Geode Team about this", reason
));
std::abort();
}
// This function is well known for crashing on certain save files,
// causing the game to crash at startup, known as the infamous save file bug.
//
// Rob ends up relying on strlen for knowing the size of `data`, instead of just using the passed in `size`.
// Its a miracle this works most of the time, considering `data` is just binary data
//
// To fix this, we just rewrite the function.
gd::string decompressString2(unsigned char* data, bool decrypt, int size, int decryptionKey) {
log::debug("decompressString2 data={} size={}", reinterpret_cast<const void*>(data), size);
if (data == nullptr || size == 0) {
return {};
}
std::vector<unsigned char> copiedData(data, data + size);
if (decrypt) {
for (int i = 0; i < size; i++) {
copiedData[i] ^= decryptionKey;
}
}
// TODO: maybe not use cocos's base64 and inflateMemory..
unsigned char* out = nullptr;
auto const decodedSize = cocos2d::base64Decode(copiedData.data(), size, &out);
std::unique_ptr<unsigned char> b64decoded { out };
if (decodedSize <= 0) {
panic(fmt::format("base64 (size={}) (data={} size={})", decodedSize, reinterpret_cast<const void*>(data), size));
return {};
}
out = nullptr;
auto const inflatedSize = cocos2d::ZipUtils::ccInflateMemory(b64decoded.get(), decodedSize, &out);
std::unique_ptr<unsigned char> inflated { out };
if (inflatedSize <= 0) {
panic(fmt::format("inflate (size={}) (data={} size={})", inflatedSize, reinterpret_cast<const void*>(data), size));
return {};
}
return std::string(reinterpret_cast<char*>(inflated.get()), inflatedSize);
}
// Modify doesnt want to work for some reason!
$execute {
(void) Mod::get()->addHook(
reinterpret_cast<void*>(
geode::addresser::getNonVirtual(
&cocos2d::ZipUtils::decompressString2
)
),
&decompressString2,
"cocos2d::ZipUtils::decompressString2",
tulip::hook::TulipConvention::Cdecl
);
}
#endif

View file

@ -0,0 +1,18 @@
#include <Geode/modify/CCTextInputNode.hpp>
#ifdef GEODE_IS_ANDROID
using namespace geode::prelude;
struct TextNodeFix : Modify<TextNodeFix, CCTextInputNode> {
bool onTextFieldInsertText(cocos2d::CCTextFieldTTF* field, char const* text, int count) {
auto change = count >= m_maxLabelLength ? 1 : 0;
m_maxLabelLength += change;
auto ret = CCTextInputNode::onTextFieldInsertText(field, text, count);
m_maxLabelLength -= change;
return ret;
}
};
#endif

View file

@ -6,14 +6,18 @@ using namespace geode::prelude;
struct SaveLoader : Modify<SaveLoader, AppDelegate> {
void trySaveGame() {
log::info("Saving...");
log::info("Saving mod data...");
log::pushNest();
auto r = Loader::get()->saveData();
if (!r) {
log::info("{}", r.unwrapErr());
}
auto begin = std::chrono::high_resolution_clock::now();
log::info("Saved");
(void)Loader::get()->saveData();
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
log::info("Took {}s", static_cast<float>(time) / 1000.f);
log::popNest();
return AppDelegate::trySaveGame();
}

View file

@ -7,8 +7,15 @@
using namespace geode::prelude;
$register_ids(GJGarageLayer) {
setIDSafe(this, 2, "username-label");
setIDSafe(this, 6, "player-icon");
// the lock does not exist for not logged in users
auto loggedInOffset = GJAccountManager::get()->m_accountID == GJAccountManager::get()->m_playerID ? -1 : 0;
if (loggedInOffset == -1 && !GameManager::get()->m_clickedName) {
// adjusts for the sprite asking for your name
loggedInOffset++;
}
setIDSafe<CCTextInputNode>(this, 0, "username-label");
setIDSafe<SimplePlayer>(this, 0, "player-icon");
auto winSize = CCDirector::get()->getWinSize();
@ -39,7 +46,7 @@ $register_ids(GJGarageLayer) {
setIDs(
this,
10,
10 + loggedInOffset,
"cube-selection-menu",
"ship-selection-menu",
"ball-selection-menu",

View file

@ -170,4 +170,4 @@ struct LevelBrowserLayerIDs : Modify<LevelBrowserLayerIDs, LevelBrowserLayer> {
return true;
}
};
};

View file

@ -134,4 +134,4 @@ struct LevelInfoLayerIDs : Modify<LevelInfoLayerIDs, LevelInfoLayer> {
return true;
}
};
};

View file

@ -3,7 +3,7 @@
using namespace geode::prelude;
static std::string getDateString(bool filesafe) {
std::string crashlog::getDateString(bool filesafe) {
auto const now = std::time(nullptr);
auto const tm = *std::localtime(&now);
std::ostringstream oss;
@ -16,13 +16,13 @@ static std::string getDateString(bool filesafe) {
return oss.str();
}
static void printGeodeInfo(std::stringstream& stream) {
void crashlog::printGeodeInfo(std::stringstream& stream) {
stream << "Loader Version: " << Loader::get()->getVersion().toString() << "\n"
<< "Installed mods: " << Loader::get()->getAllMods().size() << "\n"
<< "Problems: " << Loader::get()->getProblems().size() << "\n";
}
static void printMods(std::stringstream& stream) {
void crashlog::printMods(std::stringstream& stream) {
auto mods = Loader::get()->getAllMods();
if (mods.empty()) {
stream << "<None>\n";
@ -30,7 +30,7 @@ static void printMods(std::stringstream& stream) {
using namespace std::string_view_literals;
for (auto& mod : mods) {
stream << fmt::format("{} | [{}] {}\n",
mod->isEnabled() ? "x"sv : " "sv,
mod->isEnabled() ? "x"sv : mod->shouldLoad() ? "~"sv : " "sv,
mod->getVersion().toString(), mod->getID()
);
}

View file

@ -13,6 +13,12 @@ namespace crashlog {
* @returns True if the handler was successfully installed, false otherwise
*/
bool GEODE_DLL setupPlatformHandler();
/**
* Setup platform-specific crashlog handler for post-launch
*/
void GEODE_DLL setupPlatformHandlerPost();
/**
* Check if previous launch of GD crashed unexpectedly
* @returns True if the launch crashed, false otherwise or if indeterminate
@ -26,4 +32,11 @@ namespace crashlog {
ghc::filesystem::path GEODE_DLL getCrashLogDirectory();
std::string GEODE_DLL writeCrashlog(geode::Mod* faultyMod, std::string const& info, std::string const& stacktrace, std::string const& registers);
std::string getDateString(bool filesafe);
void printGeodeInfo(std::stringstream& stream);
void printMods(std::stringstream& stream);
}

View file

@ -58,8 +58,17 @@ $execute {
}
int geodeEntry(void* platformData) {
log::Logger::setup();
log::info("Running {} {}", Mod::get()->getName(), Mod::get()->getVersion());
auto begin = std::chrono::high_resolution_clock::now();
// set up internal mod, settings and data
log::info("Setting up internal mod");
log::pushNest();
auto internalSetupRes = LoaderImpl::get()->setupInternalMod();
log::popNest();
if (!internalSetupRes) {
LoaderImpl::get()->platformMessageBox(
"Unable to Load Geode!",
@ -72,30 +81,42 @@ int geodeEntry(void* platformData) {
// open console
if (Mod::get()->getSettingValue<bool>("show-platform-console")) {
log::debug("Opening console");
Loader::get()->openPlatformConsole();
}
// set up loader, load mods, etc.
log::info("Setting up loader");
log::pushNest();
auto setupRes = LoaderImpl::get()->setup();
log::popNest();
if (!setupRes) {
LoaderImpl::get()->platformMessageBox(
"Unable to Load Geode!",
"There was an unknown fatal error setting up "
"the loader and Geode can not be loaded. "
"the loader and Geode can not be loaded. "
"(" + setupRes.unwrapErr() + ")"
);
LoaderImpl::get()->forceReset();
return 1;
}
crashlog::setupPlatformHandlerPost();
log::info("Set up loader");
// download and install new loader update in the background
if (Mod::get()->getSettingValue<bool>("auto-check-updates")) {
log::info("Starting loader update check");
LoaderImpl::get()->checkForLoaderUpdates();
}
else {
log::info("Skipped loader update check");
}
log::debug("Entry done.");
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
log::info("Entry took {}s", static_cast<float>(time) / 1000.f);
return 0;
}

View file

@ -35,10 +35,6 @@ ghc::filesystem::path dirs::getModsSaveDir() {
return getGeodeSaveDir() / "mods";
}
ghc::filesystem::path dirs::getModRuntimeDir() {
return dirs::getGeodeDir() / "unzipped";
}
ghc::filesystem::path dirs::getModConfigDir() {
return dirs::getGeodeDir() / "config";
}

View file

@ -44,28 +44,35 @@ tulip::hook::HookMetadata Hook::Impl::getHookMetadata() const {
}
Result<> Hook::Impl::enable() {
if (!m_enabled) {
if (!LoaderImpl::get()->hasHandler(m_address)) {
GEODE_UNWRAP(LoaderImpl::get()->createHandler(m_address, m_handlerMetadata));
}
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
if (m_enabled)
return Ok();
m_handle = tulip::hook::createHook(handler, m_detour, m_hookMetadata);
log::debug("Enabling hook at function {} with address {}", m_displayName, m_address);
m_enabled = true;
if (!LoaderImpl::get()->hasHandler(m_address)) {
GEODE_UNWRAP(LoaderImpl::get()->createHandler(m_address, m_handlerMetadata));
}
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
m_handle = tulip::hook::createHook(handler, m_detour, m_hookMetadata);
if (m_owner)
log::debug("Enabled {} hook at {} for {}", m_displayName, m_address, m_owner->getID());
else
log::debug("Enabled {} hook at {}", m_displayName, m_address);
m_enabled = true;
return Ok();
}
Result<> Hook::Impl::disable() {
if (m_enabled) {
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
if (!m_enabled)
return Ok();
tulip::hook::removeHook(handler, m_handle);
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
log::debug("Disabling hook at function {}", m_displayName);
m_enabled = false;
}
tulip::hook::removeHook(handler, m_handle);
log::debug("Disabled {} hook", m_displayName);
m_enabled = false;
return Ok();
}
@ -101,4 +108,4 @@ bool Hook::Impl::getAutoEnable() const {
void Hook::Impl::setAutoEnable(bool autoEnable) {
m_autoEnable = autoEnable;
}
}

View file

@ -248,7 +248,7 @@ private:
friend class Index;
void cleanupItems();
void downloadIndex();
void downloadIndex(std::string commitHash = "");
void checkForUpdates();
void updateFromLocalTree();
void installNext(size_t index, IndexInstallList const& list);
@ -292,7 +292,7 @@ bool Index::hasTriedToUpdate() const {
return m_impl->m_triedToUpdate;
}
void Index::Impl::downloadIndex() {
void Index::Impl::downloadIndex(std::string commitHash) {
log::debug("Downloading index");
IndexUpdateEvent(UpdateProgress(0, "Beginning download")).post();
@ -303,7 +303,7 @@ void Index::Impl::downloadIndex() {
.join("index-download")
.fetch("https://github.com/geode-sdk/mods/zipball/main")
.into(targetFile)
.then([this, targetFile](auto) {
.then([this, targetFile, commitHash](auto) {
auto targetDir = dirs::getIndexDir() / "v0";
// delete old unzipped index
try {
@ -329,6 +329,10 @@ void Index::Impl::downloadIndex() {
// remove the directory github adds to the root of the zip
(void)flattenGithubRepo(targetDir);
if (!commitHash.empty()) {
auto const checksumPath = dirs::getIndexDir() / ".checksum";
(void)file::writeString(checksumPath, commitHash);
}
Loader::get()->queueInMainThread([this] {
// update index
@ -383,8 +387,7 @@ void Index::Impl::checkForUpdates() {
}
// otherwise save hash and download source
else {
(void)file::writeString(checksum, newSHA);
this->downloadIndex();
this->downloadIndex(newSHA);
}
})
.expect([](std::string const& err) {
@ -396,6 +399,8 @@ void Index::Impl::checkForUpdates() {
void Index::Impl::updateFromLocalTree() {
log::debug("Updating local index cache");
log::pushNest();
IndexUpdateEvent(UpdateProgress(100, "Updating local cache")).post();
// delete old items
m_items.clear();
@ -435,6 +440,8 @@ void Index::Impl::updateFromLocalTree() {
// mark source as finished
m_isUpToDate = true;
IndexUpdateEvent(UpdateFinished()).post();
log::popNest();
}
void Index::update(bool force) {
@ -583,7 +590,7 @@ bool Index::isUpdateAvailable(IndexItemHandle item) const {
bool Index::areUpdatesAvailable() const {
for (auto& mod : Loader::get()->getAllMods()) {
auto item = this->getMajorItem(mod->getID());
if (item && item->getMetadata().getVersion() > mod->getVersion()) {
if (item && item->getMetadata().getVersion() > mod->getVersion() && mod->isEnabled()) {
return true;
}
}
@ -658,7 +665,10 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
}
// recursively add dependencies
GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem));
ranges::push(list.list, deps.list);
for (auto& dep : deps.list) {
if (ranges::contains(list.list, dep)) continue;
list.list.push_back(dep);
}
}
// otherwise user must get this dependency manually from somewhere
else {
@ -730,6 +740,7 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
auto item = list.list.at(index);
auto tempFile = dirs::getTempDir() / (item->getMetadata().getID() + ".index");
log::debug("Installing {}", item->getMetadata().getID());
m_runningInstallations[list.target] = web::AsyncWebRequest()
.join("install_item_" + item->getMetadata().getID())
.fetch(item->getDownloadURL())
@ -765,6 +776,8 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
item->setIsInstalled(true);
log::debug("Installed {}", item->getMetadata().getID());
// Install next item in queue
this->installNext(index + 1, list);
})

View file

@ -41,15 +41,12 @@ void Loader::Impl::createDirectories() {
ghc::filesystem::create_directory(dirs::getSaveDir());
#endif
// try deleting geode/unzipped if it already exists
try { ghc::filesystem::remove_all(dirs::getModRuntimeDir()); } catch(...) {}
(void) utils::file::createDirectoryAll(dirs::getGeodeResourcesDir());
(void) utils::file::createDirectory(dirs::getModConfigDir());
(void) utils::file::createDirectory(dirs::getModsDir());
(void) utils::file::createDirectory(dirs::getGeodeLogDir());
(void) utils::file::createDirectory(dirs::getTempDir());
(void) utils::file::createDirectory(dirs::getModRuntimeDir());
(void) utils::file::createDirectoryAll(dirs::getModConfigDir());
(void) utils::file::createDirectoryAll(dirs::getModsDir());
(void) utils::file::createDirectoryAll(dirs::getGeodeLogDir());
(void) utils::file::createDirectoryAll(dirs::getTempDir());
(void) utils::file::createDirectoryAll(dirs::getModRuntimeDir());
if (!ranges::contains(m_modSearchDirectories, dirs::getModsDir())) {
m_modSearchDirectories.push_back(dirs::getModsDir());
@ -61,33 +58,30 @@ Result<> Loader::Impl::setup() {
return Ok();
}
log::Logger::setup();
if (crashlog::setupPlatformHandler()) {
log::debug("Set up platform crash logger");
}
else {
log::debug("Unable to set up platform crash logger");
log::debug("Setting up crash handler");
log::pushNest();
if (!crashlog::setupPlatformHandler()) {
log::debug("Failed to set up crash handler");
}
log::popNest();
log::debug("Setting up Loader...");
log::debug("Set up internal mod representation");
log::debug("Loading hooks... ");
log::debug("Loading hooks");
log::pushNest();
if (!this->loadHooks()) {
return Err("There were errors loading some hooks, see console for details");
}
log::popNest();
log::debug("Loaded hooks");
log::debug("Setting up IPC...");
log::debug("Setting up IPC");
log::pushNest();
this->setupIPC();
log::popNest();
log::debug("Setting up directories");
log::pushNest();
this->createDirectories();
this->addSearchPaths();
log::popNest();
this->refreshModGraph();
@ -107,14 +101,14 @@ void Loader::Impl::updateResources() {
void Loader::Impl::updateResources(bool forceReload) {
log::debug("Adding resources");
// add mods' spritesheets
log::pushNest();
for (auto const& [_, mod] : m_mods) {
if (forceReload || !ModImpl::getImpl(mod)->m_resourcesLoaded) {
this->updateModResources(mod);
ModImpl::getImpl(mod)->m_resourcesLoaded = true;
}
if (!forceReload && ModImpl::getImpl(mod)->m_resourcesLoaded)
continue;
this->updateModResources(mod);
ModImpl::getImpl(mod)->m_resourcesLoaded = true;
}
log::popNest();
}
std::vector<Mod*> Loader::Impl::getAllMods() {
@ -149,25 +143,27 @@ bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
// Data saving
Result<> Loader::Impl::saveData() {
// save mods' data
for (auto& [id, mod] : m_mods) {
log::debug("{}", mod->getID());
log::pushNest();
auto r = mod->saveData();
if (!r) {
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
}
log::popNest();
}
// save loader data
GEODE_UNWRAP(Mod::get()->saveData());
return Ok();
}
Result<> Loader::Impl::loadData() {
for (auto& [_, mod] : m_mods) {
log::debug("{}", mod->getID());
log::pushNest();
auto r = mod->loadData();
if (!r) {
log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
}
log::popNest();
}
return Ok();
}
@ -210,9 +206,9 @@ void Loader::Impl::updateModResources(Mod* mod) {
if (mod->getMetadata().getSpritesheets().empty())
return;
log::debug("Adding resources for {}", mod->getID());
log::debug("{}", mod->getID());
log::pushNest();
// add spritesheets
for (auto const& sheet : mod->getMetadata().getSpritesheets()) {
log::debug("Adding sheet {}", sheet);
auto png = sheet + ".png";
@ -231,6 +227,8 @@ void Loader::Impl::updateModResources(Mod* mod) {
CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
}
}
log::popNest();
}
// Dependencies and refreshing
@ -353,7 +351,7 @@ void Loader::Impl::buildModGraph() {
void Loader::Impl::loadModGraph(Mod* node, bool early) {
if (early && !node->needsEarlyLoad()) {
m_modsToLoad.push(node);
m_modsToLoad.push_back(node);
return;
}
@ -367,32 +365,86 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
if (node->isEnabled()) {
for (auto const& dep : node->m_impl->m_dependants) {
this->loadModGraph(dep, early);
m_modsToLoad.push_front(dep);
}
log::popNest();
return;
}
if (node->shouldLoad()) {
log::debug("Load");
auto res = node->m_impl->loadBinary();
m_currentlyLoadingMod = node;
m_refreshingModCount += 1;
m_refreshedModCount += 1;
m_lateRefreshedModCount += early ? 0 : 1;
auto unzipFunction = [this, node]() {
log::debug("Unzip");
auto res = node->m_impl->unzipGeodeFile(node->getMetadata());
return res;
};
auto loadFunction = [this, node, early]() {
if (node->shouldLoad()) {
log::debug("Load");
auto res = node->m_impl->loadBinary();
if (!res) {
m_problems.push_back({
LoadProblem::Type::LoadFailed,
node,
res.unwrapErr()
});
log::error("Failed to load binary: {}", res.unwrapErr());
log::popNest();
m_refreshingModCount -= 1;
return;
}
if (node->getID() == "absolllute.megahack")
log::debug("megahack creepypasta");
for (auto const& dep : node->m_impl->m_dependants) {
m_modsToLoad.push_front(dep);
}
}
m_refreshingModCount -= 1;
log::popNest();
};
if (early) {
auto res = unzipFunction();
if (!res) {
m_problems.push_back({
LoadProblem::Type::LoadFailed,
LoadProblem::Type::UnzipFailed,
node,
res.unwrapErr()
});
log::error("Failed to load binary: {}", res.unwrapErr());
log::error("Failed to unzip: {}", res.unwrapErr());
log::popNest();
m_refreshingModCount -= 1;
return;
}
for (auto const& dep : node->m_impl->m_dependants) {
this->loadModGraph(dep, early);
}
loadFunction();
}
else {
std::thread([=]() {
auto res = unzipFunction();
queueInMainThread([=]() {
if (!res) {
m_problems.push_back({
LoadProblem::Type::UnzipFailed,
node,
res.unwrapErr()
});
log::error("Failed to unzip: {}", res.unwrapErr());
log::popNest();
m_refreshingModCount -= 1;
return;
}
loadFunction();
});
}).detach();
}
log::popNest();
}
void Loader::Impl::findProblems() {
@ -481,17 +533,17 @@ void Loader::Impl::findProblems() {
}
void Loader::Impl::refreshModGraph() {
log::info("Refreshing mod graph...");
log::info("Refreshing mod graph");
log::pushNest();
auto begin = std::chrono::high_resolution_clock::now();
if (m_isSetup) {
log::error("Cannot refresh mod graph after startup");
log::popNest();
return;
}
auto begin = std::chrono::high_resolution_clock::now();
m_problems.clear();
m_loadingState = LoadingState::Queue;
@ -533,16 +585,29 @@ void Loader::Impl::refreshModGraph() {
else
m_loadingState = LoadingState::Mods;
queueInMainThread([]() {
Loader::get()->m_impl->continueRefreshModGraph();
queueInMainThread([&]() {
this->continueRefreshModGraph();
});
}
void Loader::Impl::continueRefreshModGraph() {
if (m_refreshingModCount != 0) {
queueInMainThread([&]() {
this->continueRefreshModGraph();
});
return;
}
if (m_lateRefreshedModCount > 0) {
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_timerBegin).count();
log::info("Took {}s", static_cast<float>(time) / 1000.f);
}
log::info("Continuing mod graph refresh...");
log::pushNest();
auto begin = std::chrono::high_resolution_clock::now();
m_timerBegin = std::chrono::high_resolution_clock::now();
switch (m_loadingState) {
case LoadingState::Mods:
@ -550,7 +615,7 @@ void Loader::Impl::continueRefreshModGraph() {
log::pushNest();
this->loadModGraph(m_modsToLoad.front(), false);
log::popNest();
m_modsToLoad.pop();
m_modsToLoad.pop_front();
if (m_modsToLoad.empty())
m_loadingState = LoadingState::Problems;
break;
@ -560,6 +625,11 @@ void Loader::Impl::continueRefreshModGraph() {
this->findProblems();
log::popNest();
m_loadingState = LoadingState::Done;
{
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_timerBegin).count();
log::info("Took {}s", static_cast<float>(time) / 1000.f);
}
break;
default:
m_loadingState = LoadingState::Done;
@ -568,13 +638,9 @@ void Loader::Impl::continueRefreshModGraph() {
break;
}
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
log::info("Took {}s", static_cast<float>(time) / 1000.f);
if (m_loadingState != LoadingState::Done) {
queueInMainThread([]() {
Loader::get()->m_impl->continueRefreshModGraph();
queueInMainThread([&]() {
this->continueRefreshModGraph();
});
}
@ -610,23 +676,22 @@ bool Loader::Impl::isReadyToHook() const {
return m_readyToHook;
}
void Loader::Impl::addInternalHook(Hook* hook, Mod* mod) {
m_internalHooks.emplace_back(hook, mod);
void Loader::Impl::addUninitializedHook(Hook* hook, Mod* mod) {
m_uninitializedHooks.emplace_back(hook, mod);
}
bool Loader::Impl::loadHooks() {
m_readyToHook = true;
auto thereWereErrors = false;
for (auto const& hook : m_internalHooks) {
bool hadErrors = false;
for (auto const& hook : m_uninitializedHooks) {
auto res = hook.second->addHook(hook.first);
if (!res) {
log::internalLog(Severity::Error, hook.second, "{}", res.unwrapErr());
thereWereErrors = true;
hadErrors = true;
}
}
// free up memory
m_internalHooks.clear();
return !thereWereErrors;
m_uninitializedHooks.clear();
return !hadErrors;
}
void Loader::Impl::queueInMainThread(ScheduledFunction func) {
@ -707,14 +772,12 @@ void Loader::Impl::tryDownloadLoaderResources(
.expect([this, tryLatestOnError](std::string const& info, int code) {
// if the url was not found, try downloading latest release instead
// (for development versions)
if (code == 404 && tryLatestOnError) {
this->downloadLoaderResources(true);
}
else {
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
if (code == 404) {
log::warn("Unable to download resources: {}", info);
}
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
})
.progress([](auto&, double now, double total) {
ResourceDownloadEvent(
@ -735,65 +798,67 @@ void Loader::Impl::updateSpecialFiles() {
}
void Loader::Impl::downloadLoaderResources(bool useLatestRelease) {
if (!useLatestRelease) {
web::AsyncWebRequest()
.join("loader-tag-exists-check")
.userAgent("github_api/1.0")
.fetch(fmt::format(
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
web::AsyncWebRequest()
.join("loader-tag-exists-check")
.userAgent("github_api/1.0")
.fetch(fmt::format(
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
this->getVersion().toString()
))
.json()
.then([this](json::Value const& json) {
this->tryDownloadLoaderResources(fmt::format(
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip",
this->getVersion().toString()
))
.json()
.then([this](json::Value const& json) {
this->tryDownloadLoaderResources(fmt::format(
"https://github.com/geode-sdk/geode/releases/download/{}/resources-" GEODE_PLATFORM_SHORT_IDENTIFIER ".zip",
this->getVersion().toString()
), true);
})
.expect([this](std::string const& info, int code) {
if (code == 404) {
log::debug("Loader version {} does not exist on Github, not downloading the resources", this->getVersion().toString());
ResourceDownloadEvent(
UpdateFinished()
).post();
), true);
})
.expect([=](std::string const& info, int code) {
if (code == 404) {
if (useLatestRelease) {
log::debug("Loader version {} does not exist on Github, downloading latest resources", this->getVersion().toString());
fetchLatestGithubRelease(
[this](json::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").template get<std::string>() == "resources.zip") {
this->tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
false
);
return;
}
}
ResourceDownloadEvent(
UpdateFailed("Unable to find resources in latest GitHub release")
).post();
},
[this](std::string const& info) {
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
}
);
return;
}
else {
ResourceDownloadEvent(
UpdateFailed("Unable to check if tag exists: " + info)
).post();
log::debug("Loader version {} does not exist on Github, not downloading the resources", this->getVersion().toString());
}
});
}
else {
fetchLatestGithubRelease(
[this](json::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").template get<std::string>() == "resources-" GEODE_PLATFORM_SHORT_IDENTIFIER ".zip") {
this->tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
false
);
return;
}
}
ResourceDownloadEvent(
UpdateFailed("Unable to find resources in latest GitHub release")
).post();
},
[this](std::string const& info) {
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
UpdateFinished()
).post();
}
);
}
else {
ResourceDownloadEvent(
UpdateFailed("Unable to check if tag exists: " + info)
).post();
}
});
}
bool Loader::Impl::verifyLoaderResources() {
@ -811,7 +876,7 @@ bool Loader::Impl::verifyLoaderResources() {
ghc::filesystem::is_directory(resourcesDir)
)) {
log::debug("Resources directory does not exist");
this->downloadLoaderResources();
this->downloadLoaderResources(true);
return false;
}
@ -1006,21 +1071,16 @@ void Loader::Impl::provideNextMod(Mod* mod) {
}
Mod* Loader::Impl::takeNextMod() {
if (!m_nextMod) {
m_nextMod = this->createInternalMod();
log::debug("Created internal mod {}", m_nextMod->getName());
}
auto ret = m_nextMod;
return ret;
if (!m_nextMod)
m_nextMod = this->getInternalMod();
return m_nextMod;
}
void Loader::Impl::releaseNextMod() {
m_nextMod = nullptr;
m_nextModLock.unlock();
}
Result<> Loader::Impl::createHandler(void* address, tulip::hook::HandlerMetadata const& metadata) {
if (m_handlerHandles.count(address)) {
return Err("Handler already exists at address");

View file

@ -13,7 +13,6 @@
#include <Geode/utils/ranges.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include "ModImpl.hpp"
#include <about.hpp>
#include <crashlog.hpp>
#include <mutex>
#include <optional>
@ -59,7 +58,7 @@ namespace geode {
std::vector<ghc::filesystem::path> m_modSearchDirectories;
std::vector<LoadProblem> m_problems;
std::unordered_map<std::string, Mod*> m_mods;
std::queue<Mod*> m_modsToLoad;
std::deque<Mod*> m_modsToLoad;
std::vector<ghc::filesystem::path> m_texturePaths;
bool m_isSetup = false;
@ -72,7 +71,7 @@ namespace geode {
std::vector<utils::MiniFunction<void(void)>> m_gdThreadQueue;
mutable std::mutex m_gdThreadMutex;
std::vector<std::pair<Hook*, Mod*>> m_internalHooks;
std::vector<std::pair<Hook*, Mod*>> m_uninitializedHooks;
bool m_readyToHook = false;
bool m_platformConsoleOpen = false;
@ -84,6 +83,14 @@ namespace geode {
std::mutex m_nextModAccessMutex;
Mod* m_nextMod = nullptr;
Mod* m_currentlyLoadingMod = nullptr;
int m_refreshingModCount = 0;
int m_refreshedModCount = 0;
int m_lateRefreshedModCount = 0;
std::chrono::time_point<std::chrono::high_resolution_clock> m_timerBegin;
void provideNextMod(Mod* mod);
Mod* takeNextMod();
void releaseNextMod();
@ -166,9 +173,9 @@ namespace geode {
bool isNewUpdateDownloaded() const;
bool isReadyToHook() const;
void addInternalHook(Hook* hook, Mod* mod);
void addUninitializedHook(Hook* hook, Mod* mod);
Mod* createInternalMod();
Mod* getInternalMod();
Result<> setupInternalMod();
bool userTriedToLoadDLLs() const;

View file

@ -113,6 +113,36 @@ std::string Log::toString(bool logTime, uint32_t nestLevel) const {
res += fmt::format("{:%H:%M:%S}", m_time);
}
switch (m_severity.m_value) {
case Severity::Debug:
res += " DBG";
break;
case Severity::Info:
res += " INF";
break;
case Severity::Notice:
res += " NTC";
break;
case Severity::Warning:
res += " WRN";
break;
case Severity::Error:
res += " ERR";
break;
case Severity::Critical:
res += " CRT";
break;
case Severity::Alert:
res += " ALR";
break;
case Severity::Emergency:
res += " FAT";
break;
default:
res += " UNK";
break;
}
res += fmt::format(" [{}]: ", m_sender ? m_sender->getName() : "Geode?");
for (uint32_t i = 0; i < nestLevel; i++) {

View file

@ -3,6 +3,7 @@
#include "ModMetadataImpl.hpp"
#include "about.hpp"
#include <hash/hash.hpp>
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/Hook.hpp>
#include <Geode/loader/Loader.hpp>
@ -44,7 +45,7 @@ Result<> Mod::Impl::setup() {
}
if (!m_resourcesLoaded) {
auto searchPathRoot = dirs::getModRuntimeDir() / m_metadata.getID() / "resources";
CCFileUtils::get()->addSearchPath(searchPathRoot.string().c_str());
// CCFileUtils::get()->addSearchPath(searchPathRoot.string().c_str());
m_resourcesLoaded = true;
}
@ -225,7 +226,7 @@ Result<> Mod::Impl::saveData() {
log::debug("Check covered");
for (auto& [key, value] : m_savedSettingsData.as_object()) {
log::debug("Check if {} is saved", key);
if (!coveredSettings.count(key)) {
if (!coveredSettings.contains(key)) {
json[key] = value;
}
}
@ -470,18 +471,21 @@ Result<> Mod::Impl::disableHook(Hook* hook) {
}
Result<Hook*> Mod::Impl::addHook(Hook* hook) {
m_hooks.push_back(hook);
if (LoaderImpl::get()->isReadyToHook()) {
if (this->isEnabled() && hook->getAutoEnable()) {
auto res = this->enableHook(hook);
if (!res) {
delete hook;
return Err("Can't create hook: " + res.unwrapErr());
}
}
if (!ranges::contains(m_hooks, [&](auto const& h) { return h == hook; }))
m_hooks.push_back(hook);
if (!LoaderImpl::get()->isReadyToHook()) {
LoaderImpl::get()->addUninitializedHook(hook, m_self);
return Ok(hook);
}
else {
LoaderImpl::get()->addInternalHook(hook, m_self);
if (!this->isEnabled() || !hook->getAutoEnable())
return Ok(hook);
auto res = this->enableHook(hook);
if (!res) {
delete hook;
return Err("Can't create hook: " + res.unwrapErr());
}
return Ok(hook);
@ -554,21 +558,52 @@ Result<> Mod::Impl::createTempDir() {
return Err("Unable to create mod runtime directory");
}
// Unzip .geode file into temp dir
GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_metadata.getPath()));
if (!unzip.hasEntry(m_metadata.getBinaryName())) {
return Err(
fmt::format("Unable to find platform binary under the name \"{}\"", m_metadata.getBinaryName())
);
}
GEODE_UNWRAP(unzip.extractAllTo(tempPath));
// Mark temp dir creation as succesful
m_tempDirName = tempPath;
return Ok();
}
Result<> Mod::Impl::unzipGeodeFile(ModMetadata metadata) {
// Unzip .geode file into temp dir
auto tempDir = dirs::getModRuntimeDir() / metadata.getID();
auto datePath = tempDir / "modified-at";
std::string currentHash = file::readString(datePath).unwrapOr("");
auto modifiedDate = ghc::filesystem::last_write_time(metadata.getPath());
auto modifiedCount = std::chrono::duration_cast<std::chrono::milliseconds>(modifiedDate.time_since_epoch());
auto modifiedHash = std::to_string(modifiedCount.count());
if (currentHash == modifiedHash) {
log::debug("Same hash detected, skipping unzip");
return Ok();
}
log::debug("Hash mismatch detected, unzipping");
std::error_code ec;
ghc::filesystem::remove_all(tempDir, ec);
if (ec) {
return Err("Unable to delete temp dir: " + ec.message());
}
(void)utils::file::createDirectoryAll(tempDir);
auto res = file::writeString(datePath, modifiedHash);
if (!res) {
log::warn("Failed to write modified date of geode zip: {}", res.unwrapErr());
}
GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(metadata.getPath()));
if (!unzip.hasEntry(metadata.getBinaryName())) {
return Err(
fmt::format("Unable to find platform binary under the name \"{}\"", metadata.getBinaryName())
);
}
GEODE_UNWRAP(unzip.extractAllTo(tempDir));
return Ok();
}
ghc::filesystem::path Mod::Impl::getConfigDir(bool create) const {
auto dir = dirs::getModConfigDir() / m_metadata.getID();
if (create) {
@ -578,7 +613,6 @@ ghc::filesystem::path Mod::Impl::getConfigDir(bool create) const {
}
char const* Mod::Impl::expandSpriteName(char const* name) {
log::debug("Expanding sprite name {} for {}", name, m_metadata.getID());
if (m_expandedSprites.count(name)) return m_expandedSprites[name];
auto exp = new char[strlen(name) + 2 + m_metadata.getID().size()];
@ -638,9 +672,15 @@ static Result<ModMetadata> getModImplInfo() {
return Ok(info);
}
Mod* Loader::Impl::createInternalMod() {
Mod* Loader::Impl::getInternalMod() {
auto& mod = Mod::sharedMod<>;
if (mod) return mod;
if (mod)
return mod;
if (m_mods.contains("geode.loader")) {
log::warn("Something went wrong and Mod::sharedMod<> got unset after the internal mod was created! Setting sharedMod back...");
mod = m_mods["geode.loader"];
return mod;
}
auto infoRes = getModImplInfo();
if (!infoRes) {
LoaderImpl::get()->platformMessageBox(

View file

@ -75,6 +75,9 @@ namespace geode {
Result<> unloadPlatformBinary();
Result<> createTempDir();
// called on a separate thread
Result<> unzipGeodeFile(ModMetadata metadata);
void setupSettings();
std::string getID() const;

View file

@ -62,6 +62,7 @@ Result<StringSetting> StringSetting::parse(JsonMaybeObject& obj) {
StringSetting sett;
parseCommon(sett, obj);
obj.has("match").into(sett.match);
obj.has("filter").into(sett.filter);
return Ok(sett);
}

View file

@ -8,7 +8,8 @@
using namespace geode::prelude;
Result<> Mod::Impl::loadPlatformBinary() {
auto so = dlopen((m_tempDirName / m_info.binaryName()).string().c_str(), RTLD_LAZY);
auto so =
dlopen((m_tempDirName / m_metadata.getBinaryName()).string().c_str(), RTLD_LAZY);
if (so) {
if (m_platformInfo) {
delete m_platformInfo;

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#pragma once
#include <unistd.h>
class ScopedFd final {
public:
explicit ScopedFd(int fd) : fd_(fd) {
}
ScopedFd() : fd_(-1) {
}
~ScopedFd() {
reset(-1);
}
void reset(int fd = -1) {
fd_ = fd;
}
int get() const {
return fd_;
}
private:
int fd_;
};

View file

@ -0,0 +1,191 @@
/*
* Copyright (C) 2012 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <dlfcn.h>
#include <execinfo.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <unwind.h>
#include <sys/syscall.h>
#include <linux/memfd.h>
#include "ScopedFd.hpp"
struct StackState {
void** frames;
int frame_count;
int cur_frame = 0;
StackState(void** frames, int frame_count) : frames(frames), frame_count(frame_count) {}
};
static _Unwind_Reason_Code TraceFunction(_Unwind_Context* context, void* arg) {
// The instruction pointer is pointing at the instruction after the return
// call on all architectures.
// Modify the pc to point at the real function.
uintptr_t ip = _Unwind_GetIP(context);
if (ip != 0) {
#if defined(__arm__)
// If the ip is suspiciously low, do nothing to avoid a segfault trying
// to access this memory.
if (ip >= 4096) {
// Check bits [15:11] of the first halfword assuming the instruction
// is 32 bits long. If the bits are any of these values, then our
// assumption was correct:
// b11101
// b11110
// b11111
// Otherwise, this is a 16 bit instruction.
uint16_t value = (*reinterpret_cast<uint16_t*>(ip - 2)) >> 11;
if (value == 0x1f || value == 0x1e || value == 0x1d) {
ip -= 4;
} else {
ip -= 2;
}
}
#elif defined(__aarch64__)
// All instructions are 4 bytes long, skip back one instruction.
ip -= 4;
#elif defined(__i386__) || defined(__x86_64__)
// It's difficult to decode exactly where the previous instruction is,
// so subtract 1 to estimate where the instruction lives.
ip--;
#endif
}
StackState* state = static_cast<StackState*>(arg);
state->frames[state->cur_frame++] = reinterpret_cast<void*>(ip);
return (state->cur_frame >= state->frame_count) ? _URC_END_OF_STACK : _URC_NO_REASON;
}
int backtrace(void** buffer, int size) {
if (size <= 0) {
return 0;
}
StackState state(buffer, size);
_Unwind_Backtrace(TraceFunction, &state);
return state.cur_frame;
}
void backtrace_symbols_fd(void* const* buffer, int size, int fd);
char** backtrace_symbols(void* const* buffer, int size) {
if (size <= 0) {
return nullptr;
}
// Do this calculation first in case the user passes in a bad value.
size_t ptr_size;
if (__builtin_mul_overflow(sizeof(char*), size, &ptr_size)) {
return nullptr;
}
ScopedFd fd(syscall(SYS_memfd_create, "backtrace_symbols_fd", MFD_CLOEXEC));
// ScopedFd fd(memfd_create("backtrace_symbols_fd", MFD_CLOEXEC));
if (fd.get() == -1) {
return nullptr;
}
backtrace_symbols_fd(buffer, size, fd.get());
// Get the size of the file.
off_t file_size = lseek(fd.get(), 0, SEEK_END);
if (file_size <= 0) {
return nullptr;
}
// The interface for backtrace_symbols indicates that only the single
// returned pointer must be freed by the caller. Therefore, allocate a
// buffer that includes the memory for the strings and all of the pointers.
// Add one byte at the end just in case the file didn't end with a '\n'.
size_t symbol_data_size;
if (__builtin_add_overflow(ptr_size, file_size, &symbol_data_size) ||
__builtin_add_overflow(symbol_data_size, 1, &symbol_data_size)) {
return nullptr;
}
uint8_t* symbol_data = reinterpret_cast<uint8_t*>(malloc(symbol_data_size));
if (symbol_data == nullptr) {
return nullptr;
}
// Copy the string data into the buffer.
char* cur_string = reinterpret_cast<char*>(&symbol_data[ptr_size]);
// If this fails, the read won't read back the correct number of bytes.
lseek(fd.get(), 0, SEEK_SET);
ssize_t num_read = read(fd.get(), cur_string, file_size);
fd.reset(-1);
if (num_read != file_size) {
free(symbol_data);
return nullptr;
}
// Make sure the last character in the file is '\n'.
if (cur_string[file_size] != '\n') {
cur_string[file_size++] = '\n';
}
for (int i = 0; i < size; i++) {
(reinterpret_cast<char**>(symbol_data))[i] = cur_string;
cur_string = strchr(cur_string, '\n');
if (cur_string == nullptr) {
free(symbol_data);
return nullptr;
}
cur_string[0] = '\0';
cur_string++;
}
return reinterpret_cast<char**>(symbol_data);
}
// This function should do no allocations if possible.
void backtrace_symbols_fd(void* const* buffer, int size, int fd) {
if (size <= 0 || fd < 0) {
return;
}
for (int frame_num = 0; frame_num < size; frame_num++) {
void* address = buffer[frame_num];
Dl_info info;
if (dladdr(address, &info) != 0) {
if (info.dli_fname != nullptr) {
write(fd, info.dli_fname, strlen(info.dli_fname));
}
if (info.dli_sname != nullptr) {
dprintf(fd, "(%s+0x%" PRIxPTR ") ", info.dli_sname,
reinterpret_cast<uintptr_t>(address) - reinterpret_cast<uintptr_t>(info.dli_saddr));
} else {
dprintf(fd, "(+%p) ", info.dli_saddr);
}
}
dprintf(fd, "[%p]\n", address);
}
}

View file

@ -2,16 +2,523 @@
#ifdef GEODE_IS_ANDROID
ghc::filesystem::path crashlog::getCrashLogDirectory() {
return geode::dirs::getSaveDir();
using namespace geode::prelude;
#include <Geode/utils/string.hpp>
#include <array>
#include <thread>
#include <ghc/fs_fwd.hpp>
#include <execinfo.h>
#include <dlfcn.h>
#include <cxxabi.h>
#include <algorithm>
#include <link.h>
#include <unwind.h>
#include <jni.h>
#include <Geode/cocos/platform/android/jni/JniHelper.h>
#include "backtrace/execinfo.hpp"
static constexpr size_t FRAME_SIZE = 64;
static std::mutex s_mutex;
static std::condition_variable s_cv;
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 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(Elf32_Phdr const* image) {
if (image == nullptr) {
return "<Unknown>";
}
std::string imageName;// = image->imageFilePath;
if (imageName.empty()) {
imageName = "<Unknown>";
}
return imageName;
}
// 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 Elf32_Phdr 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;
// }
// ghc::filesystem::path imagePath = getImageName(image);
// if (!ghc::filesystem::exists(imagePath)) {
// return nullptr;
// }
// auto geodePath = dirs::getGameDir() / "Frameworks" / "Geode.dylib";
// if (ghc::filesystem::equivalent(imagePath, geodePath)) {
// return Mod::get();
// }
// for (auto& mod : Loader::get()->getAllMods()) {
// if (!mod->isEnabled() || !ghc::filesystem::exists(mod->getBinaryPath())) {
// continue;
// }
// if (ghc::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();
}
static struct sigaction oldActionSEGV;
static struct sigaction oldActionBUS;
static struct sigaction oldActionILL;
extern "C" void signalHandler(int signal, siginfo_t* signalInfo, void* vcontext) {
auto context = reinterpret_cast<ucontext_t*>(vcontext);
s_backtraceSize = 0;
// s_backtraceSize = backtrace(s_backtrace.data(), FRAME_SIZE);
// for some reason this is needed, dont ask me why
// s_backtrace[1] = reinterpret_cast<void*>(context->uc_mcontext.fault_address);
// if (s_backtraceSize < FRAME_SIZE) {
// s_backtrace[s_backtraceSize] = nullptr;
// }
{
std::unique_lock<std::mutex> lock(s_mutex);
s_signal = signal;
s_siginfo = signalInfo;
s_context = context;
}
s_cv.notify_all();
std::unique_lock<std::mutex> lock(s_mutex);
s_cv.wait(lock, [] { return s_signal == 0; });
// std::_Exit(EXIT_FAILURE);
switch (signal) {
case SIGSEGV:
sigaction(signal, &oldActionSEGV, nullptr);
oldActionSEGV.sa_sigaction(signal, signalInfo, vcontext);
break;
case SIGBUS:
sigaction(signal, &oldActionBUS, nullptr);
oldActionBUS.sa_sigaction(signal, signalInfo, vcontext);
break;
case SIGILL:
sigaction(signal, &oldActionILL, nullptr);
oldActionILL.sa_sigaction(signal, signalInfo, vcontext);
break;
}
}
static std::string getStacktrace() {
std::stringstream stacktrace;
if (s_backtraceSize == 0) {
return stacktrace.str();
}
auto messages = backtrace_symbols(s_backtrace.data(), s_backtraceSize);
if (s_backtraceSize < FRAME_SIZE) {
messages[s_backtraceSize] = nullptr;
}
for (int i = 1; i < s_backtraceSize; ++i) {
auto message = std::string(messages[i]);
// TODO: parse the message
stacktrace << message << "\n";
// 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";
// }
}
free(messages);
return stacktrace.str();
}
static std::string getRegisters() {
std::stringstream registers;
auto context = s_context;
auto& ctx = context->uc_mcontext;
// geez
registers << std::showbase << std::hex /*<< std::setfill('0') << std::setw(16) */;
registers << "r0: " << ctx.arm_r0 << "\n";
registers << "r1: " << ctx.arm_r1 << "\n";
registers << "r2: " << ctx.arm_r2 << "\n";
registers << "r3: " << ctx.arm_r3 << "\n";
registers << "r4: " << ctx.arm_r4 << "\n";
registers << "r5: " << ctx.arm_r5 << "\n";
registers << "r6: " << ctx.arm_r6 << "\n";
registers << "r7: " << ctx.arm_r7 << "\n";
registers << "r8: " << ctx.arm_r8 << "\n";
registers << "r9: " << ctx.arm_r9 << "\n";
registers << "r10: " << ctx.arm_r10 << "\n";
registers << "r11: " << ctx.arm_fp << "\n";
registers << "r12: " << ctx.arm_ip << "\n";
registers << "sp: " << ctx.arm_sp << "\n";
registers << "lr: " << ctx.arm_lr << "\n";
registers << "pc: " << ctx.arm_pc << "\n";
registers << "cpsr: " << ctx.arm_cpsr << "\n";
return registers.str();
}
static void handlerThread() {
std::unique_lock<std::mutex> lock(s_mutex);
s_cv.wait(lock, [] { return s_signal != 0; });
auto signalAddress = reinterpret_cast<void*>(s_context->uc_mcontext.fault_address);
// Mod* faultyMod = nullptr;
// for (int i = 1; i < s_backtraceSize; ++i) {
// auto mod = modFromAddress(s_backtrace[i]);
// if (mod != nullptr) {
// faultyMod = mod;
// break;
// }
// }
Mod* faultyMod = modFromAddress(signalAddress);
auto text = crashlog::writeCrashlog(faultyMod, getInfo(signalAddress, faultyMod), getStacktrace(), getRegisters());
log::error("Geode crashed!\n{}", text);
s_signal = 0;
s_cv.notify_all();
log::debug("Notified");
}
static bool s_lastLaunchCrashed = false;
// bool crashlog::setupPlatformHandler() {
// auto pidFile = crashlog::getCrashLogDirectory() / "last-pid";
// int lastPid = 0;
// if (ghc::filesystem::exists(pidFile)) {
// auto res = file::readString(pidFile);
// if (!res) {
// log::warn("Failed to read last-pid file: {}", res.error());
// }
// else {
// lastPid = std::stoi(res.unwrap());
// }
// std::error_code ec;
// ghc::filesystem::remove(pidFile, ec);
// if (ec) {
// log::warn("Failed to remove last-pid file: {}", ec.message());
// }
// }
// auto res = file::writeString(pidFile, std::to_string(getpid()));
// if (!res) {
// log::warn("Failed to write last-pid file: {}", res.error());
// }
// lastPid = 1513;
// if (lastPid == 0) {
// return true;
// }
// // TODO: get logcat crash
// std::string logcatCrash = R"RAW()RAW";
// std::string crashTrace;
// auto findLast = logcatCrash.find_last_of(fmt::format("pid {} (.geode.launcher)", lastPid));
// if (findLast != std::string::npos) {
// auto begin = logcatCrash.substr(0, findLast).find_last_of("F/libc");
// if (begin != std::string::npos) {
// crashTrace = logcatCrash.substr(begin);
// }
// }
// else {
// return true;
// }
// auto text = crashlog::writeCrashlog(nullptr, "", crashTrace, "");
// s_lastLaunchCrashed = true;
// auto lastCrashedFile = crashlog::getCrashLogDirectory() / "last-crashed";
// if (ghc::filesystem::exists(lastCrashedFile)) {
// std::error_code ec;
// ghc::filesystem::remove(lastCrashedFile, ec);
// if (ec) {
// log::warn("Failed to remove last-crashed file: {}", ec.message());
// }
// }
// return true;
// }
// bool crashlog::didLastLaunchCrash() {
// return s_lastLaunchCrashed;
// }
// ghc::filesystem::path crashlog::getCrashLogDirectory() {
// return dirs::getGeodeDir() / "crashlogs";
// }
ghc::filesystem::path crashlog::getCrashLogDirectory() {
return dirs::getGeodeDir() / "crashlogs";
}
int writeAndGetPid() {
auto pidFile = crashlog::getCrashLogDirectory() / "last-pid";
int lastPid = 0;
if (ghc::filesystem::exists(pidFile)) {
auto res = file::readString(pidFile);
if (!res) {
log::warn("Failed to read last-pid file: {}", res.error());
}
else {
lastPid = std::stoi(res.unwrap());
}
std::error_code ec;
ghc::filesystem::remove(pidFile, ec);
if (ec) {
log::warn("Failed to remove last-pid file: {}", ec.message());
}
}
auto res = file::writeString(pidFile, std::to_string(getpid()));
if (!res) {
log::warn("Failed to write last-pid file: {}", res.error());
}
return lastPid;
}
void printModsAndroid(std::stringstream& stream) {
auto mods = Loader::get()->getAllMods();
if (mods.empty()) {
stream << "<None>\n";
}
using namespace std::string_view_literals;
for (auto& mod : mods) {
stream << fmt::format("{} | [{}] {}\n",
mod->shouldLoad() ? "x"sv : " "sv,
mod->getVersion().toString(), mod->getID()
);
}
}
static std::string s_result;
bool crashlog::setupPlatformHandler() {
return false;
JniMethodInfo t;
if (JniHelper::getStaticMethodInfo(t, "com/geode/launcher/utils/GeodeUtils", "getLogcatCrashBuffer", "()Ljava/lang/String;")) {
jstring stringResult = (jstring)t.env->CallStaticObjectMethod(t.classID, t.methodID);
s_result = JniHelper::jstring2string(stringResult);
t.env->DeleteLocalRef(stringResult);
t.env->DeleteLocalRef(t.classID);
if (s_result.empty()) {
return false;
}
}
else return false;
auto lastPid = writeAndGetPid();
auto index = s_result.rfind(fmt::format("pid {}", lastPid));
if (index != std::string::npos) {
auto begin = s_result.substr(0, index).rfind("F/libc");
if (begin != std::string::npos) {
s_result = s_result.substr(begin);
}
s_lastLaunchCrashed = true;
}
return true;
}
void crashlog::setupPlatformHandlerPost() {
if (s_result.empty()) return;
std::stringstream ss;
ss << "Geode crashed!\n";
ss << "Please submit this crash report to the developer of the mod that caused it.\n";
ss << "\n== Geode Information ==\n";
crashlog::printGeodeInfo(ss);
ss << "\n== Installed Mods ==\n";
printModsAndroid(ss);
ss << "\n== Crash Report (Logcat) ==\n";
ss << s_result;
std::ofstream actualFile;
actualFile.open(
crashlog::getCrashLogDirectory() / (crashlog::getDateString(true) + ".log"), std::ios::app
);
actualFile << ss.rdbuf() << std::flush;
actualFile.close();
}
bool crashlog::didLastLaunchCrash() {
return false;
return s_lastLaunchCrashed;
}
#endif

View file

@ -0,0 +1,68 @@
#include <Geode/c++stl/gdstdlib.hpp>
#ifdef GEODE_IS_ANDROID
namespace geode::base {
uintptr_t get() {
static uintptr_t base = (reinterpret_cast<uintptr_t>(&UILayer::create) - 0x20f168) & (~0x1);
// static uintptr_t base = reinterpret_cast<uintptr_t>(dlopen("libcocos2dcpp.so", RTLD_NOW));
return base;
}
}
namespace gd {
namespace {
static inline auto emptyInternalString() {
return reinterpret_cast<_internal_string*>(
geode::base::get() + 0x75fb24 + sizeof(_internal_string)
);
}
}
string::string() : m_data(nullptr) {
m_data = emptyInternalString();
}
string::string(char const* ok) : m_data(nullptr) {
reinterpret_cast<void (*)(string*, char const*)>(geode::base::get() + 0x506c08)(this, ok);
}
string::string(string const& ok) : m_data(nullptr) {
if (*(string**)(&ok) == nullptr) return;
reinterpret_cast<void (*)(string*, string const&)>(geode::base::get() + 0x506634)(this, ok);
}
string& string::operator=(char const* ok) {
this->~string();
new (this) string(ok);
return *this;
}
string& string::operator=(string const& ok) {
this->~string();
new (this) string(ok);
return *this;
}
string::~string() {
if (m_data == nullptr) return;
reinterpret_cast<void (*)(string*)>(geode::base::get() + 0x5054bc)(this);
}
bool string::operator<(string const& other) const {
return std::string(*this) < std::string(other);
}
bool string::operator==(string const& other) const {
return std::string(*this) == std::string(other);
}
// TODO: these need to stay for old mods linking against geode <1.2.0, remove in 2.0.0
template class map<int, int>;
template class map<gd::string, gd::string>;
template class map<gd::string, bool>;
template class map<short, bool>;
}
#endif

View file

@ -5,9 +5,30 @@
#include "../load.hpp"
#include <jni.h>
// idk where to put this
#include <EGL/egl.h>
PFNGLGENVERTEXARRAYSOESPROC glGenVertexArraysOESEXT = 0;
PFNGLBINDVERTEXARRAYOESPROC glBindVertexArrayOESEXT = 0;
PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArraysOESEXT = 0;
extern "C" [[gnu::visibility("default")]] jint JNI_OnLoad(JavaVM* vm, void* reserved) {
glGenVertexArraysOESEXT = (PFNGLGENVERTEXARRAYSOESPROC)eglGetProcAddress("glGenVertexArraysOES");
glBindVertexArrayOESEXT = (PFNGLBINDVERTEXARRAYOESPROC)eglGetProcAddress("glBindVertexArrayOES");
glDeleteVertexArraysOESEXT = (PFNGLDELETEVERTEXARRAYSOESPROC)eglGetProcAddress("glDeleteVertexArraysOES");
auto updatePath = geode::dirs::getGameDir() / "update";
std::error_code ec;
ghc::filesystem::remove_all(updatePath, ec);
if (ec) {
geode::log::warn("Failed to remove update directory: {}", ec.message());
}
geodeEntry(nullptr);
return JNI_VERSION_1_1;
return JNI_VERSION_1_6;
}
extern "C" [[gnu::visibility("default")]] void emptyFunction(void*) {
// empty
}
#endif

View file

@ -9,25 +9,245 @@ using namespace geode::prelude;
#include <Geode/utils/web.hpp>
#include <ghc/filesystem.hpp>
#include <jni.h>
#include <Geode/cocos/platform/android/jni/JniHelper.h>
bool utils::clipboard::write(std::string const& data) {
JniMethodInfo t;
if (JniHelper::getStaticMethodInfo(t, "com/geode/launcher/utils/GeodeUtils", "writeClipboard", "(Ljava/lang/String;)V")) {
jstring stringArg1 = t.env->NewStringUTF(data.c_str());
t.env->CallStaticVoidMethod(t.classID, t.methodID, stringArg1);
t.env->DeleteLocalRef(stringArg1);
t.env->DeleteLocalRef(t.classID);
return true;
}
return false;
}
std::string utils::clipboard::read() {
JniMethodInfo t;
if (JniHelper::getStaticMethodInfo(t, "com/geode/launcher/utils/GeodeUtils", "readClipboard", "()Ljava/lang/String;")) {
jstring stringResult = (jstring)t.env->CallStaticObjectMethod(t.classID, t.methodID);
std::string result = JniHelper::jstring2string(stringResult);
t.env->DeleteLocalRef(stringResult);
t.env->DeleteLocalRef(t.classID);
return result;
}
return "";
}
CCPoint cocos::getMousePos() {
return CCPoint(0, 0);
}
ghc::filesystem::path dirs::getGameDir() {
return ghc::filesystem::path(CCFileUtils::sharedFileUtils()->getWritablePath().c_str());
return ghc::filesystem::path(
"/storage/emulated/0/Android/data/com.geode.launcher/files/game"
// "/data/user/0/com.geode.launcher/files/"
/*CCFileUtils::sharedFileUtils()->getWritablePath().c_str()*/
);
}
ghc::filesystem::path dirs::getSaveDir() {
return ghc::filesystem::path(CCFileUtils::sharedFileUtils()->getWritablePath().c_str());
return ghc::filesystem::path(
"/storage/emulated/0/Android/data/com.geode.launcher/files/save"
// "/data/user/0/com.geode.launcher/files/"
/*CCFileUtils::sharedFileUtils()->getWritablePath().c_str()*/
);
}
ghc::filesystem::path dirs::getModRuntimeDir() {
return ghc::filesystem::path(
"/data/user/0/com.geode.launcher/files/geode/unzipped"
);
// return dirs::getGeodeDir() / "unzipped";
}
void utils::web::openLinkInBrowser(std::string const& url) {
CCApplication::sharedApplication()->openURL(url.c_str());
}
bool utils::file::openFolder(ghc::filesystem::path const&) {
bool utils::file::openFolder(ghc::filesystem::path const& path) {
JniMethodInfo t;
if (JniHelper::getStaticMethodInfo(t, "com/geode/launcher/utils/GeodeUtils", "openFolder", "(Ljava/lang/String;)Z")) {
jstring stringArg1 = t.env->NewStringUTF(path.string().c_str());
jboolean result = t.env->CallStaticBooleanMethod(t.classID, t.methodID, stringArg1);
t.env->DeleteLocalRef(stringArg1);
t.env->DeleteLocalRef(t.classID);
return result;
}
return false;
}
geode::Result<ghc::filesystem::path> utils::file::
pickFile(geode::utils::file::PickMode, geode::utils::file::FilePickOptions const&) {
return geode::Err("This function is currently unimplemented");
static utils::MiniFunction<void(ghc::filesystem::path)> s_fileCallback;
static utils::MiniFunction<void(std::vector<ghc::filesystem::path>)> s_filesCallback;
static utils::MiniFunction<void()> s_failedCallback;
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_utils_GeodeUtils_selectFileCallback(
JNIEnv *env,
jobject,
jstring data
) {
auto isCopy = jboolean();
auto dataStr = env->GetStringUTFChars(data, &isCopy);
log::debug("Selected file: {}", dataStr);
s_fileCallback(dataStr);
}
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_utils_GeodeUtils_selectFilesCallback(
JNIEnv *env,
jobject,
jobjectArray datas
) {
auto isCopy = jboolean();
auto count = env->GetArrayLength(datas);
auto result = std::vector<ghc::filesystem::path>();
for (int i = 0; i < count; i++) {
auto data = (jstring)env->GetObjectArrayElement(datas, i);
auto dataStr = env->GetStringUTFChars(data, &isCopy);
result.push_back(dataStr);
log::debug("Selected file {}: {}", i, dataStr);
}
s_filesCallback(result);
}
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_utils_GeodeUtils_failedCallback(
JNIEnv *env,
jobject
) {
if (s_failedCallback) s_failedCallback();
}
Result<ghc::filesystem::path> file::pickFile(file::PickMode mode, file::FilePickOptions const& options) {
return Err("Use the callback version");
}
void file::pickFile(
PickMode mode, FilePickOptions const& options,
MiniFunction<void(ghc::filesystem::path)> callback,
MiniFunction<void()> failed
) {
s_fileCallback = callback;
s_failedCallback = failed;
std::string method;
switch (mode) {
case file::PickMode::OpenFile:
method = "selectFile";
break;
case file::PickMode::SaveFile:
method = "createFile";
break;
case file::PickMode::OpenFolder:
method = "selectFolder";
break;
}
JniMethodInfo t;
if (JniHelper::getStaticMethodInfo(t, "com/geode/launcher/utils/GeodeUtils", method.c_str(), "(Ljava/lang/String;)Z")) {
jstring stringArg1 = t.env->NewStringUTF(options.defaultPath.value_or(ghc::filesystem::path()).string().c_str());
jboolean result = t.env->CallStaticBooleanMethod(t.classID, t.methodID, stringArg1);
t.env->DeleteLocalRef(stringArg1);
t.env->DeleteLocalRef(t.classID);
if (result) {
return;
}
}
if (s_failedCallback) s_failedCallback();
}
Result<std::vector<ghc::filesystem::path>> file::pickFiles(file::FilePickOptions const& options) {
return Err("Use the callback version");
}
void file::pickFiles(
FilePickOptions const& options,
MiniFunction<void(std::vector<ghc::filesystem::path>)> callback,
MiniFunction<void()> failed
) {
s_filesCallback = callback;
s_failedCallback = failed;
JniMethodInfo t;
if (JniHelper::getStaticMethodInfo(t, "com/geode/launcher/utils/GeodeUtils", "selectFiles", "(Ljava/lang/String;)Z")) {
jstring stringArg1 = t.env->NewStringUTF(options.defaultPath.value_or(ghc::filesystem::path()).string().c_str());
jboolean result = t.env->CallStaticBooleanMethod(t.classID, t.methodID, stringArg1);
t.env->DeleteLocalRef(stringArg1);
t.env->DeleteLocalRef(t.classID);
if (result) {
return;
}
}
if (s_failedCallback) s_failedCallback();
}
void geode::utils::game::launchLoaderUninstaller(bool deleteSaveData) {
log::error("Launching Geode uninstaller is not supported on android");
}
void geode::utils::game::exit() {
if (CCApplication::sharedApplication() &&
(GameManager::get()->m_playLayer || GameManager::get()->m_levelEditorLayer)) {
log::error("Cannot exit in PlayLayer or LevelEditorLayer!");
return;
}
AppDelegate::get()->trySaveGame();
// AppDelegate::get()->showLoadingCircle(false, true);
CCDirector::get()->getActionManager()->addAction(CCSequence::create(
CCDelayTime::create(0.5f),
CCCallFunc::create(nullptr, callfunc_selector(MenuLayer::endGame)),
nullptr
), CCDirector::get()->getRunningScene(), false);
}
void geode::utils::game::restart() {
if (CCApplication::sharedApplication() &&
(GameManager::get()->m_playLayer || GameManager::get()->m_levelEditorLayer)) {
log::error("Cannot restart in PlayLayer or LevelEditorLayer!");
return;
}
class Exit : public CCObject {
public:
void restart() {
JniMethodInfo t;
if (JniHelper::getStaticMethodInfo(t, "com/geode/launcher/utils/GeodeUtils", "restartGame", "()V")) {
t.env->CallStaticVoidMethod(t.classID, t.methodID);
t.env->DeleteLocalRef(t.classID);
}
}
};
// Not implemented
// log::error("Restarting the game is not implemented on android");
AppDelegate::get()->trySaveGame();
// AppDelegate::get()->showLoadingCircle(false, true);
CCDirector::get()->getActionManager()->addAction(CCSequence::create(
CCDelayTime::create(0.5f),
CCCallFunc::create(nullptr, callfunc_selector(Exit::restart)),
nullptr
), CCDirector::get()->getRunningScene(), false);
}
#endif

View file

@ -382,6 +382,8 @@ bool crashlog::setupPlatformHandler() {
return true;
}
void crashlog::setupPlatformHandlerPost() {}
bool crashlog::didLastLaunchCrash() {
return s_lastLaunchCrashed;
}

View file

@ -145,7 +145,7 @@ void utils::web::openLinkInBrowser(std::string const& url) {
}
@end
Result<ghc::filesystem::path> utils::file::pickFile(
Result<ghc::filesystem::path> file::pickFile(
file::PickMode mode, file::FilePickOptions const& options
) {
auto result = [FileDialog filePickerWithMode:mode options:options multiple: false];
@ -157,13 +157,41 @@ Result<ghc::filesystem::path> utils::file::pickFile(
}
}
Result<std::vector<ghc::filesystem::path>> utils::file::pickFiles(
GEODE_DLL void file::pickFile(
PickMode mode, FilePickOptions const& options,
MiniFunction<void(ghc::filesystem::path)> callback,
MiniFunction<void()> failed
) {
auto result = file::pickFile(mode, options);
if (result.isOk()) {
callback(std::move(result.unwrap()));
} else {
failed();
}
}
Result<std::vector<ghc::filesystem::path>> file::pickFiles(
file::FilePickOptions const& options
) {
//return Err("utils::file::pickFiles is not implemented");
return [FileDialog filePickerWithMode: file::PickMode::OpenFile options:options multiple:true];
}
GEODE_DLL void file::pickFiles(
FilePickOptions const& options,
MiniFunction<void(std::vector<ghc::filesystem::path>)> callback,
MiniFunction<void()> failed
) {
auto result = file::pickFiles(options);
if (result.isOk()) {
callback(std::move(result.unwrap()));
} else {
failed();
}
}
CCPoint cocos::getMousePos() {
auto windowFrame = NSApp.mainWindow.frame;
auto viewFrame = NSApp.mainWindow.contentView.frame;
@ -200,10 +228,14 @@ ghc::filesystem::path dirs::getSaveDir() {
return path;
}
ghc::filesystem::path dirs::getModRuntimeDir() {
return dirs::getGeodeDir() / "unzipped";
}
void geode::utils::game::exit() {
if (CCApplication::sharedApplication() &&
(GameManager::get()->m_playLayer || GameManager::get()->m_levelEditorLayer)) {
log::error("Cannot restart in PlayLayer or LevelEditorLayer!");
log::error("Cannot exit in PlayLayer or LevelEditorLayer!");
return;
}

View file

@ -20,24 +20,37 @@ void Loader::Impl::platformMessageBox(char const* title, std::string const& info
bool hasAnsiColorSupport = false;
void Loader::Impl::logConsoleMessageWithSeverity(std::string const& msg, Severity severity) {
if (m_platformConsoleOpen) {
if (hasAnsiColorSupport) {
int color = 0;
switch (severity) {
case Severity::Debug: color = 243; break;
case Severity::Info: color = 33; break;
case Severity::Warning: color = 229; break;
case Severity::Error: color = 9; break;
default: color = 7; break;
}
auto const colorStr = fmt::format("\x1b[38;5;{}m", color);
auto const newMsg = fmt::format("{}{}\x1b[0m{}", colorStr, msg.substr(0, 8), msg.substr(8));
if (!m_platformConsoleOpen)
return;
std::cout << newMsg << "\n" << std::flush;
} else {
std::cout << msg << "\n" << std::flush;
}
if (!hasAnsiColorSupport) {
std::cout << msg << "\n" << std::flush;
return;
}
int color = 0;
switch (severity) {
case Severity::Debug:
color = 243;
break;
case Severity::Info:
color = 33;
break;
case Severity::Warning:
color = 229;
break;
case Severity::Error:
color = 9;
break;
default:
color = 7;
break;
}
auto const colorStr = fmt::format("\x1b[38;5;{}m", color);
auto const newMsg = fmt::format("{}{}\x1b[0m{}", colorStr, msg.substr(0, 12),
msg.substr(12));
std::cout << newMsg << "\n" << std::flush;
}
void Loader::Impl::openPlatformConsole() {

View file

@ -20,6 +20,7 @@
#include <iostream>
#include <string>
#include <fmt/core.h>
#include "ehdata_structs.hpp"
using namespace geode::prelude;
@ -199,24 +200,61 @@ static std::string getRegisters(PCONTEXT context) {
static std::string getInfo(LPEXCEPTION_POINTERS info, Mod* faultyMod) {
std::stringstream stream;
stream << "Faulty Module: "
<< getModuleName(handleFromAddress(info->ExceptionRecord->ExceptionAddress), true)
<< "\n"
<< "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n"
<< "Exception Code: " << std::hex << info->ExceptionRecord->ExceptionCode << " ("
<< getExceptionCodeString(info->ExceptionRecord->ExceptionCode) << ")" << std::dec
<< "\n"
<< "Exception Flags: " << info->ExceptionRecord->ExceptionFlags << "\n"
<< "Exception Address: " << info->ExceptionRecord->ExceptionAddress << " (";
printAddr(stream, info->ExceptionRecord->ExceptionAddress, false);
stream << ")"
<< "\n"
<< "Number Parameters: " << info->ExceptionRecord->NumberParameters << "\n";
if (info->ExceptionRecord->ExceptionCode == EH_EXCEPTION_NUMBER) {
// This executes when a C++ exception was thrown and not handled.
// https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273
// since you can throw virtually anything, we need to figure out if it's an std::exception* or not
bool isStdException = false;
auto* exceptionRecord = info->ExceptionRecord;
auto exceptionObject = exceptionRecord->ExceptionInformation[1];
auto* throwInfo = reinterpret_cast<_ThrowInfo*>(exceptionRecord->ExceptionInformation[2]);
auto* catchableTypeArray = reinterpret_cast<_CatchableTypeArray*>(throwInfo->pCatchableTypeArray);
auto ctaSize = catchableTypeArray->nCatchableTypes;
for (int i = 0; i < ctaSize; i++) {
auto* catchableType = reinterpret_cast<_CatchableType*>(catchableTypeArray->arrayOfCatchableTypes[i]);
auto* ctDescriptor = reinterpret_cast<_TypeDescriptor*>(catchableType->pType);
const char* classname = ctDescriptor->name;
if (strcmp(classname, ".?AVexception@std@@") == 0) {
isStdException = true;
break;
}
}
if (isStdException) {
std::exception* excObject = reinterpret_cast<std::exception*>(exceptionObject);
// stream << "C++ Exception Type: " << typeid(excObject).name() << "\n"; // always const std::exception *
stream << "C++ Exception: " << excObject->what() << "\n";
} else {
stream << "C++ Exception: <Unknown type>\n";
}
stream << "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n";
} else {
stream << "Faulty Module: "
<< getModuleName(handleFromAddress(info->ExceptionRecord->ExceptionAddress), true)
<< "\n"
<< "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n"
<< "Exception Code: " << std::hex << info->ExceptionRecord->ExceptionCode << " ("
<< getExceptionCodeString(info->ExceptionRecord->ExceptionCode) << ")" << std::dec
<< "\n"
<< "Exception Flags: " << info->ExceptionRecord->ExceptionFlags << "\n"
<< "Exception Address: " << info->ExceptionRecord->ExceptionAddress << " (";
printAddr(stream, info->ExceptionRecord->ExceptionAddress, false);
stream << ")"
<< "\n"
<< "Number Parameters: " << info->ExceptionRecord->NumberParameters << "\n";
}
return stream.str();
}
static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
// init symbols so we can get some juicy debug info
@ -226,16 +264,22 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
auto text = crashlog::writeCrashlog(faultyMod, getInfo(info, faultyMod), getStacktrace(info->ContextRecord), getRegisters(info->ContextRecord));
// show message box on debug mode
#ifdef GEODE_DEBUG
MessageBoxA(nullptr, text.c_str(), "Geode Crashed", MB_ICONERROR);
#endif
return EXCEPTION_CONTINUE_SEARCH;
}
bool crashlog::setupPlatformHandler() {
static LONG WINAPI exceptionHandlerDummy(LPEXCEPTION_POINTERS info) {
SetUnhandledExceptionFilter(exceptionHandler);
return EXCEPTION_CONTINUE_SEARCH;
}
bool crashlog::setupPlatformHandler() {
// for some reason, on exceptions windows seems to clear SetUnhandledExceptionFilter
// so we attach a VE handler (which runs *earlier*) and inside set our crash handler
AddVectoredExceptionHandler(0, exceptionHandlerDummy);
SetUnhandledExceptionFilter(exceptionHandler);
auto lastCrashedFile = crashlog::getCrashLogDirectory() / "last-crashed";
if (ghc::filesystem::exists(lastCrashedFile)) {
g_lastLaunchCrashed = true;
@ -252,6 +296,8 @@ bool crashlog::didLastLaunchCrash() {
return g_lastLaunchCrashed;
}
void crashlog::setupPlatformHandlerPost() {}
ghc::filesystem::path crashlog::getCrashLogDirectory() {
return dirs::getGeodeDir() / "crashlogs";
}

View file

@ -0,0 +1,55 @@
#pragma once
// _ThrowInfo and all of those other structs are hardcoded into MSVC (the compiler itself, unavailable in any header),
// but don't exist in other compilers like Clang, causing <ehdata.h> to not compile.
//
// We define them manually in order to be able to use them.
// source: https://www.geoffchappell.com/studies/msvc/language/predefined/index.htm
#if defined(__GNUC__) || defined(__clang__) || defined(__INTELLISENSE__)
typedef struct _PMD
{
int mdisp;
int pdisp;
int vdisp;
} _PMD;
typedef void (*_PMFN) (void);
#pragma warning (disable:4200)
#pragma pack (push, _TypeDescriptor, 8)
typedef struct _TypeDescriptor
{
const void *pVFTable;
void *spare;
char name [];
} _TypeDescriptor;
#pragma pack (pop, _TypeDescriptor)
#pragma warning (default:4200)
typedef const struct _s__CatchableType {
unsigned int properties;
_TypeDescriptor *pType;
_PMD thisDisplacement;
int sizeOrOffset;
_PMFN copyFunction;
} _CatchableType;
#pragma warning (disable:4200)
typedef const struct _s__CatchableTypeArray {
int nCatchableTypes;
_CatchableType *arrayOfCatchableTypes [];
} _CatchableTypeArray;
#pragma warning (default:4200)
typedef const struct _s__ThrowInfo {
unsigned int attributes;
_PMFN pmfnUnwind;
int (__cdecl *pForwardCompat) (...);
_CatchableTypeArray *pCatchableTypeArray;
} _ThrowInfo;
#endif // defined(__GNUC__) || defined(__clang__) || defined(__INTELLISENSE__)
#include <ehdata.h> // for EH_EXCEPTION_NUMBER

View file

@ -97,6 +97,20 @@ Result<ghc::filesystem::path> utils::file::pickFile(
return Ok(path);
}
GEODE_DLL void file::pickFile(
PickMode mode, FilePickOptions const& options,
MiniFunction<void(ghc::filesystem::path)> callback,
MiniFunction<void()> failed
) {
auto result = file::pickFile(mode, options);
if (result.isOk()) {
callback(std::move(result.unwrap()));
} else {
failed();
}
}
Result<std::vector<ghc::filesystem::path>> utils::file::pickFiles(
file::FilePickOptions const& options
) {
@ -105,6 +119,20 @@ Result<std::vector<ghc::filesystem::path>> utils::file::pickFiles(
return Ok(paths);
}
GEODE_DLL void file::pickFiles(
FilePickOptions const& options,
MiniFunction<void(std::vector<ghc::filesystem::path>)> callback,
MiniFunction<void()> failed
) {
auto result = file::pickFiles(options);
if (result.isOk()) {
callback(std::move(result.unwrap()));
} else {
failed();
}
}
void utils::web::openLinkInBrowser(std::string const& url) {
ShellExecuteA(0, 0, url.c_str(), 0, 0, SW_SHOW);
}
@ -158,6 +186,10 @@ ghc::filesystem::path dirs::getSaveDir() {
return path;
}
ghc::filesystem::path dirs::getModRuntimeDir() {
return dirs::getGeodeDir() / "unzipped";
}
void geode::utils::game::exit() {
if (CCApplication::sharedApplication() &&
(GameManager::get()->m_playLayer || GameManager::get()->m_levelEditorLayer)) {

View file

@ -17,18 +17,13 @@ void geode::openIssueReportPopup(Mod* mod) {
if (mod->getMetadata().getIssues()) {
MDPopup::create(
"Issue Report",
mod->getMetadata().getIssues().value().info +
"\n\n"
"Please report the issue to the mod that caused the crash.\n"
"If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
"latest crash log(s) from `" +
dirs::getCrashlogsDir().string() + "`",
"OK", (mod->getMetadata().getIssues().value().url ? "Open URL" : ""),
"OK", "Open Folder",
[mod](bool btn2) {
if (btn2) {
web::openLinkInBrowser(
mod->getMetadata().getIssues().value().url.value()
);
}
file::openFolder(dirs::getCrashlogsDir());
}
)->show();
}

View file

@ -5,8 +5,9 @@
#include "../list/ModListCell.hpp"
#include "../list/ModListLayer.hpp"
bool DevProfilePopup::setup(std::string const& developer) {
bool DevProfilePopup::setup(std::string const& developer, ModListLayer* list) {
m_noElasticity = true;
m_layer = list;
this->setTitle("Mods by " + developer);
@ -18,7 +19,7 @@ bool DevProfilePopup::setup(std::string const& developer) {
for (auto& mod : Loader::get()->getAllMods()) {
if (mod->getDeveloper() == developer) {
auto cell = ModCell::create(
mod, nullptr, ModListDisplay::Concise, { 358.f, 40.f }
mod, m_layer, ModListDisplay::Concise, { 358.f, 40.f }
);
cell->disableDeveloperButton();
items->addObject(cell);
@ -31,7 +32,7 @@ bool DevProfilePopup::setup(std::string const& developer) {
continue;
}
auto cell = IndexItemCell::create(
item, nullptr, ModListDisplay::Concise, { 358.f, 40.f }
item, m_layer, ModListDisplay::Concise, { 358.f, 40.f }
);
cell->disableDeveloperButton();
items->addObject(cell);
@ -39,18 +40,18 @@ bool DevProfilePopup::setup(std::string const& developer) {
// mods list
auto listSize = CCSize { 358.f, 160.f };
auto list = ListView::create(items, 40.f, listSize.width, listSize.height);
list->setPosition(winSize / 2 - listSize / 2);
m_mainLayer->addChild(list);
auto cellList = ListView::create(items, 40.f, listSize.width, listSize.height);
cellList->setPosition(winSize / 2 - listSize / 2);
m_mainLayer->addChild(cellList);
addListBorders(m_mainLayer, winSize / 2, listSize);
return true;
}
DevProfilePopup* DevProfilePopup::create(std::string const& developer) {
DevProfilePopup* DevProfilePopup::create(std::string const& developer, ModListLayer* list) {
auto ret = new DevProfilePopup();
if (ret && ret->init(420.f, 260.f, developer)) {
if (ret && ret->init(420.f, 260.f, developer, list)) {
ret->autorelease();
return ret;
}

View file

@ -4,10 +4,14 @@
using namespace geode::prelude;
class DevProfilePopup : public Popup<std::string const&> {
class ModListLayer;
class DevProfilePopup : public Popup<std::string const&, ModListLayer*> {
protected:
bool setup(std::string const& developer) override;
ModListLayer* m_layer;
bool setup(std::string const& developer, ModListLayer* list) override;
public:
static DevProfilePopup* create(std::string const& developer);
static DevProfilePopup* create(std::string const& developer, ModListLayer* list);
};

View file

@ -4,6 +4,7 @@
#include "../list/ModListLayer.hpp"
#include "../settings/ModSettingsPopup.hpp"
#include <Geode/loader/Dirs.hpp>
#include <about.hpp>
#include <Geode/binding/ButtonSprite.hpp>
#include <Geode/binding/CCTextInputNode.hpp>

View file

@ -53,24 +53,21 @@ void InstallListCell::setupInfo(
}
this->addChild(m_titleLabel);
m_developerBtn = nullptr;
m_creatorLabel = nullptr;
if (developer) {
auto creatorStr = "by " + *developer;
auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
creatorLabel->setScale(.34f);
m_creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
m_creatorLabel->setScale(.34f);
if (inactive) {
creatorLabel->setColor({ 163, 163, 163 });
m_creatorLabel->setColor({ 163, 163, 163 });
}
m_developerBtn = CCMenuItemSpriteExtra::create(
creatorLabel, this, menu_selector(InstallListCell::onViewDev)
);
m_developerBtn->setPosition(
m_creatorLabel->setPosition(
m_titleLabel->getPositionX() + m_titleLabel->getScaledContentSize().width + 3.f +
creatorLabel->getScaledContentSize().width / 2,
m_creatorLabel->getScaledContentSize().width / 2,
m_height / 2
);
m_menu->addChild(m_developerBtn);
m_menu->addChild(m_creatorLabel);
}
this->setupVersion(version);
@ -96,7 +93,7 @@ void InstallListCell::setupVersion(std::variant<VersionInfo, ComparableVersionIn
m_versionLabel->setScale(.2f);
m_versionLabel->setPosition(
m_titleLabel->getPositionX() + m_titleLabel->getScaledContentSize().width + 3.f +
(m_developerBtn ? m_developerBtn->getScaledContentSize().width + 3.f : 0.f),
(m_creatorLabel ? m_creatorLabel->getScaledContentSize().width + 3.f : 0.f),
m_titleLabel->getPositionY() - 1.f
);
m_versionLabel->setColor({ 0, 255, 0 });
@ -123,7 +120,7 @@ void InstallListCell::setupInfo(ModMetadata const& metadata, bool inactive) {
}
void InstallListCell::onViewDev(CCObject*) {
DevProfilePopup::create(getDeveloper())->show();
// DevProfilePopup::create(getDeveloper(), m_layer)->show();
}
bool InstallListCell::init(InstallListPopup* list, CCSize const& size) {

View file

@ -21,7 +21,7 @@ protected:
float m_height;
InstallListPopup* m_layer = nullptr;
CCMenu* m_menu = nullptr;
CCMenuItemSpriteExtra* m_developerBtn = nullptr;
CCLabelBMFont* m_creatorLabel = nullptr;
CCLabelBMFont* m_titleLabel = nullptr;
CCLabelBMFont* m_versionLabel = nullptr;
TagNode* m_tagLabel = nullptr;

View file

@ -113,7 +113,7 @@ CCArray* InstallListPopup::createCells(std::unordered_map<std::string, InstallLi
// installed
// TODO: we should be able to select a different version even if its installed
if (/*item.mod && !item.mod->isUninstalled()*/item.mod->getMetadata().getID() == "geode.loader") {
if (item.mod && /*!item.mod->isUninstalled()*/item.mod->getMetadata().getID() == "geode.loader") {
bottom.push_back(ModInstallListCell::create(item.mod, this, this->getCellSize()));
for (auto const& dep : item.mod->getMetadata().getDependencies()) {
queue.push(dep);

View file

@ -186,7 +186,7 @@ void ModListCell::updateCellLayout() {
}
void ModListCell::onViewDev(CCObject*) {
DevProfilePopup::create(this->getDeveloper())->show();
DevProfilePopup::create(this->getDeveloper(), m_layer)->show();
}
bool ModListCell::init(ModListLayer* list, CCSize const& size) {
@ -233,7 +233,9 @@ void ModCell::onEnable(CCObject* sender) {
else {
tryOrAlert(m_mod->disable(), "Error disabling mod");
}
m_layer->reloadList();
if (m_layer) {
m_layer->reloadList();
}
}
void ModCell::onUnresolvedInfo(CCObject*) {

View file

@ -638,6 +638,10 @@ void ModListLayer::onExit(CCObject*) {
);
}
void ModListLayer::keyBackClicked() {
this->onExit(nullptr);
}
void ModListLayer::onReload(CCObject*) {
this->reloadList();
}

View file

@ -82,6 +82,9 @@ protected:
void createSearchControl();
void onIndexUpdate(IndexUpdateEvent* event);
// most requested feature of all time
void keyBackClicked() override;
CCArray* createModCells(ModListType type, ModListQuery const& query);
CCSize getCellSize() const;
CCSize getListSize() const;

View file

@ -87,6 +87,11 @@ bool ProblemsListCell::init(LoadProblem problem, ProblemsListPopup* list, CCSize
icon = "info-alert.png"_spr;
message = fmt::format("{} is incompatible with {}", cause, problem.message);
break;
case LoadProblem::Type::UnzipFailed:
icon = "info-alert.png"_spr;
message = fmt::format("{} has failed unzipping", cause);
m_longMessage = problem.message;
break;
}
m_problem = std::move(problem);

View file

@ -324,6 +324,11 @@ bool StringSettingNode::setup(StringSettingValue* setting, float width) {
m_input = InputNode::create(width / 2 - 10.f, "Text", "chatFont.fnt");
m_input->setPosition({ -(width / 2 - 70.f) / 2, .0f });
m_input->setScale(.65f);
if (setting->castDefinition().filter.has_value()) {
m_input->getInput()->setAllowedChars(setting->castDefinition().filter.value());
}
m_input->getInput()->setDelegate(this);
m_menu->addChild(m_input);
@ -350,16 +355,17 @@ void FileSettingNode::valueChanged(bool updateText) {
}
void FileSettingNode::onPickFile(CCObject*) {
if (auto path = file::pickFile(
file::pickFile(
file::PickMode::OpenFile,
{
dirs::getGameDir(),
setting()->castDefinition().controls.filters
},
[&](auto path) {
m_uncommittedValue = path;
this->valueChanged(true);
}
)) {
m_uncommittedValue = path.unwrap();
this->valueChanged(true);
}
);
}
bool FileSettingNode::setup(FileSettingValue* setting, float width) {

View file

@ -109,8 +109,8 @@ bool MDTextArea::init(std::string const& str, CCSize const& size) {
if (!CCLayer::init()) return false;
m_text = str;
m_size = size;
this->setContentSize(size);
m_size = size - CCSize { 15.f, 0.f };
this->setContentSize(m_size);
m_renderer = TextRenderer::create();
CC_SAFE_RETAIN(m_renderer);
@ -118,8 +118,8 @@ bool MDTextArea::init(std::string const& str, CCSize const& size) {
m_bgSprite->setScale(.5f);
m_bgSprite->setColor({ 0, 0, 0 });
m_bgSprite->setOpacity(75);
m_bgSprite->setContentSize(size * 2 + CCSize { 25.f, 25.f });
m_bgSprite->setPosition(size / 2);
m_bgSprite->setContentSize(size * 2);
m_bgSprite->setPosition(m_size / 2);
this->addChild(m_bgSprite);
m_scrollLayer = ScrollLayer::create({ 0, 0, m_size.width, m_size.height }, true);
@ -695,7 +695,16 @@ void MDTextArea::updateLabel() {
m_renderer->end();
m_scrollLayer->m_contentLayer->setContentSize(m_content->getContentSize());
if (m_content->getContentSize().height > m_size.height) {
// Generate bottom padding
m_scrollLayer->m_contentLayer->setContentSize(m_content->getContentSize() + CCSize { 0.f, 12.5 });
m_content->setPositionY(10.f);
} else {
m_scrollLayer->m_contentLayer->setContentSize(m_content->getContentSize());
m_content->setPositionY(-2.5f);
}
m_scrollLayer->moveToTop();
}

View file

@ -173,7 +173,6 @@ void Notification::show() {
auto winSize = CCDirector::get()->getWinSize();
this->setPosition(winSize.width / 2, winSize.height / 4);
this->setZOrder(CCScene::get()->getHighestChildZ() + 100);
CCScene::get()->addChild(this);
}
SceneManager::get()->keepAcrossScenes(this);
m_showing = true;

View file

@ -23,6 +23,10 @@ SceneManager::~SceneManager() {
}
void SceneManager::keepAcrossScenes(CCNode* node) {
if (m_lastScene) {
node->removeFromParentAndCleanup(false);
m_lastScene->addChild(node);
}
m_persistedNodes->addObject(node);
}
@ -36,4 +40,5 @@ void SceneManager::willSwitchToScene(CCScene* scene) {
node->removeFromParentAndCleanup(false);
scene->addChild(node);
}
m_lastScene = scene;
}

View file

@ -0,0 +1,300 @@
#include <Geode/ui/TextArea.hpp>
using namespace geode::prelude;
SimpleTextArea* SimpleTextArea::create(const std::string& text, const std::string& font, const float scale) {
return SimpleTextArea::create(font, text, scale, CCDirector::sharedDirector()->getWinSize().width / 2, false);
}
SimpleTextArea* SimpleTextArea::create(const std::string& text, const std::string& font, const float scale, const float width) {
return SimpleTextArea::create(font, text, scale, width, true);
}
SimpleTextArea* SimpleTextArea::create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth) {
SimpleTextArea* instance = new SimpleTextArea(font, text, scale, width, artificialWidth);
if (instance && instance->init()) {
instance->autorelease();
return instance;
} else {
CC_SAFE_DELETE(instance);
return nullptr;
}
}
SimpleTextArea::SimpleTextArea(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth) {
m_font = font;
m_text = text;
m_maxLines = 0;
m_scale = scale;
m_linePadding = 0;
m_color = { 0xFF, 0xFF, 0xFF, 0xFF };
m_alignment = kCCTextAlignmentLeft;
m_wrappingMode = WORD_WRAP;
m_artificialWidth = artificialWidth;
m_container = CCMenu::create();
m_shouldUpdate = true;
this->setAnchorPoint({ 0.5f, 0.5f });
m_container->setPosition({ 0, 0 });
m_container->setAnchorPoint({ 0, 1 });
m_container->setContentSize({ width, 0 });
this->addChild(m_container);
}
void SimpleTextArea::setFont(const std::string& font) {
m_font = font;
m_shouldUpdate = true;
}
std::string SimpleTextArea::getFont() {
return m_font;
}
void SimpleTextArea::setColor(const ccColor4B& color) {
m_color = color;
m_shouldUpdate = true;
}
ccColor4B SimpleTextArea::getColor() {
return m_color;
}
void SimpleTextArea::setAlignment(const CCTextAlignment alignment) {
m_alignment = alignment;
m_shouldUpdate = true;
}
CCTextAlignment SimpleTextArea::getAlignment() {
return m_alignment;
}
void SimpleTextArea::setWrappingMode(const WrappingMode mode) {
m_wrappingMode = mode;
m_shouldUpdate = true;
}
WrappingMode SimpleTextArea::getWrappingMode() {
return m_wrappingMode;
}
void SimpleTextArea::setText(const std::string& text) {
m_text = text;
m_shouldUpdate = true;
}
std::string SimpleTextArea::getText() {
return m_text;
}
void SimpleTextArea::setMaxLines(const size_t maxLines) {
m_maxLines = maxLines;
m_shouldUpdate = true;
}
size_t SimpleTextArea::getMaxLines() {
return m_maxLines;
}
void SimpleTextArea::setWidth(const float width) {
m_artificialWidth = true;
m_shouldUpdate = true;
this->setContentSize({ width, this->getContentSize().height });
m_container->setContentSize(this->getContentSize());
}
float SimpleTextArea::getWidth() {
return m_container->getContentSize().width;
}
void SimpleTextArea::setScale(const float scale) {
m_scale = scale;
m_shouldUpdate = true;
}
float SimpleTextArea::getScale() {
return m_scale;
}
void SimpleTextArea::setLinePadding(const float padding) {
m_linePadding = padding;
m_shouldUpdate = true;
}
float SimpleTextArea::getLinePadding() {
return m_linePadding;
}
std::vector<CCLabelBMFont*> SimpleTextArea::getLines() {
return m_lines;
}
float SimpleTextArea::getHeight() {
return m_container->getContentSize().height;
}
float SimpleTextArea::getLineHeight() {
return m_lineHeight;
}
CCLabelBMFont* SimpleTextArea::createLabel(const std::string& text, const float top) {
CCLabelBMFont* label = CCLabelBMFont::create(text.c_str(), m_font.c_str());
label->setScale(m_scale);
label->setPosition({ 0, top });
label->setColor({ m_color.r, m_color.g, m_color.b });
label->setOpacity(m_color.a);
return label;
}
float SimpleTextArea::calculateOffset(CCLabelBMFont* label) {
return m_linePadding + label->getContentSize().height * m_scale;
}
void SimpleTextArea::charIteration(const std::function<CCLabelBMFont*(CCLabelBMFont* line, const char c, const float top)>& overflowHandling) {
float top = 0;
CCLabelBMFont* line = this->createLabel("", top);
m_lines = { line };
for (const char c : m_text) {
if (m_maxLines && m_lines.size() > m_maxLines) {
CCLabelBMFont* last = m_lines.at(m_maxLines - 1);
const std::string text = last->getString();
m_lines.pop_back();
last->setString(text.substr(0, text.size() - 3).append("...").c_str());
break;
} else if (c == '\n') {
m_lines.push_back(line = this->createLabel("", top -= this->calculateOffset(line)));
} else if (m_artificialWidth && line->getContentSize().width * m_scale >= this->getWidth()) {
m_lines.push_back(line = overflowHandling(line, c, top -= this->calculateOffset(line)));
} else {
line->setString((std::string(line->getString()) + c).c_str());
}
}
}
void SimpleTextArea::updateLinesNoWrap() {
std::stringstream stream(m_text);
std::string part;
float top = 0;
while (std::getline(stream, part)) {
if (m_maxLines && m_lines.size() >= m_maxLines) {
CCLabelBMFont* last = m_lines.at(m_maxLines - 1);
const std::string text = last->getString();
last->setString(text.substr(0, text.size() - 3).append("...").c_str());
break;
} else {
CCLabelBMFont* line = this->createLabel(part, 0);
top -= this->calculateOffset(line);
m_lines.push_back(line);
}
}
}
void SimpleTextArea::updateLinesWordWrap() {
this->charIteration([this](CCLabelBMFont* line, const char c, const float top) {
static std::string delimiters(" `~!@#$%^&*()-_=+[{}];:'\",<.>/?\\|");
if (delimiters.find(c) == std::string_view::npos) {
const std::string text = line->getString();
const size_t position = text.find_last_of(delimiters) + 1;
line->setString(text.substr(0, position).c_str());
return this->createLabel(text.substr(position) + c, top);
} else {
return this->createLabel(std::string(c, c != ' '), top);
}
});
}
void SimpleTextArea::updateLinesCutoffWrap() {
this->charIteration([this](CCLabelBMFont* line, const char c, const float top) {
const std::string text = line->getString();
const char back = text.back();
const bool lastIsSpace = back == ' ';
CCLabelBMFont* newLine = this->createLabel(std::string(!lastIsSpace, back).append(std::string(c != ' ', c)), top);
if (!lastIsSpace) {
if (text[text.size() - 2] == ' ') {
line->setString(text.substr(0, text.size() - 1).c_str());
} else {
line->setString((text.substr(0, text.size() - 1) + '-').c_str());
}
}
return newLine;
});
}
void SimpleTextArea::updateContainer() {
switch (m_wrappingMode) {
case NO_WRAP: {
this->updateLinesNoWrap();
} break;
case WORD_WRAP: {
this->updateLinesWordWrap();
} break;
case CUTOFF_WRAP: {
this->updateLinesCutoffWrap();
} break;
}
const size_t lineCount = m_lines.size();
const float width = this->getWidth();
if (lineCount > 0) {
m_lineHeight = m_lines.back()->getContentSize().height * m_scale;
} else {
m_lineHeight = 0;
}
float height = m_lineHeight * lineCount + m_linePadding * (lineCount - 1);
this->setContentSize({ width, height });
m_container->setContentSize(this->getContentSize());
m_container->removeAllChildren();
for (CCLabelBMFont* line : m_lines) {
const float y = height + line->getPositionY();
switch (m_alignment) {
case kCCTextAlignmentLeft: {
line->setAnchorPoint({ 0, 1 });
line->setPosition({ 0, y });
} break;
case kCCTextAlignmentCenter: {
line->setAnchorPoint({ 0, 1 });
line->setPosition({ width / 2, y });
} break;
case kCCTextAlignmentRight: {
line->setAnchorPoint({ 1, 1 });
line->setPosition({ width, y });
} break;
}
m_container->addChild(line);
}
}
void SimpleTextArea::draw() {
CCNode::draw();
if (m_shouldUpdate) {
this->updateContainer();
m_shouldUpdate = false;
}
}

View file

@ -68,6 +68,19 @@ Addresser::MultipleInheritance* Addresser::instance() {
intptr_t Addresser::followThunkFunction(intptr_t address) {
#ifdef GEODE_IS_WINDOWS
// if theres a jmp at the start
if (*reinterpret_cast<uint8_t*>(address) == 0xE9) {
auto relative = *reinterpret_cast<uint32_t*>(address + 1);
auto newAddress = address + relative + 5;
// and if that jmp leads to a jmp dword ptr, only then follow it,
// because otherwise its just a hook.
// For some reason this [jmp -> jmp dword ptr] chain happens with a few cocos functions,
// but not all. For example: cocos2d::ZipUtils::decompressString2
if (*reinterpret_cast<uint8_t*>(newAddress) == 0xFF && *reinterpret_cast<uint8_t*>(newAddress + 1) == 0x25) {
address = newAddress;
}
}
// check if first instruction is a jmp dword ptr [....], i.e. if the func is a thunk
if (*reinterpret_cast<uint8_t*>(address) == 0xFF && *reinterpret_cast<uint8_t*>(address + 1) == 0x25) {
// read where the jmp reads from

View file

@ -21,48 +21,51 @@ using namespace geode::prelude;
using namespace geode::utils::file;
Result<std::string> utils::file::readString(ghc::filesystem::path const& path) {
if (!ghc::filesystem::exists(path))
return Err("File does not exist");
#if _WIN32
std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
#else
std::ifstream in(path.string(), std::ios::in | std::ios::binary);
#endif
if (in) {
std::string contents;
in.seekg(0, std::ios::end);
contents.resize((const size_t)in.tellg());
in.seekg(0, std::ios::beg);
in.read(&contents[0], contents.size());
in.close();
return Ok(contents);
}
return Err("Unable to open file");
if (!in)
return Err("Unable to open file");
std::string contents;
in.seekg(0, std::ios::end);
contents.resize((const size_t)in.tellg());
in.seekg(0, std::ios::beg);
in.read(&contents[0], contents.size());
in.close();
return Ok(contents);
}
Result<json::Value> utils::file::readJson(ghc::filesystem::path const& path) {
auto str = utils::file::readString(path);
if (str) {
try {
return Ok(json::parse(str.value()));
} catch(std::exception const& e) {
return Err("Unable to parse JSON: " + std::string(e.what()));
}
} else {
return Err("Unable to open file");
if (!str)
return Err(str.unwrapErr());
try {
return Ok(json::parse(str.value()));
}
catch(std::exception const& e) {
return Err("Unable to parse JSON: " + std::string(e.what()));
}
}
Result<ByteVector> utils::file::readBinary(ghc::filesystem::path const& path) {
if (!ghc::filesystem::exists(path))
return Err("File does not exist");
#if _WIN32
std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
#else
std::ifstream in(path.string(), std::ios::in | std::ios::binary);
#endif
if (in) {
return Ok(ByteVector(std::istreambuf_iterator<char>(in), {}));
}
return Err("Unable to open file");
if (!in)
return Err("Unable to open file");
return Ok(ByteVector(std::istreambuf_iterator<char>(in), {}));
}
Result<> utils::file::writeString(ghc::filesystem::path const& path, std::string const& data) {
@ -72,14 +75,14 @@ Result<> utils::file::writeString(ghc::filesystem::path const& path, std::string
#else
file.open(path.string());
#endif
if (file.is_open()) {
file << data;
if (!file.is_open()) {
file.close();
return Ok();
return Err("Unable to open file");
}
file << data;
file.close();
return Err("Unable to open file");
return Ok();
}
Result<> utils::file::writeBinary(ghc::filesystem::path const& path, ByteVector const& data) {
@ -89,14 +92,14 @@ Result<> utils::file::writeBinary(ghc::filesystem::path const& path, ByteVector
#else
file.open(path.string(), std::ios::out | std::ios::binary);
#endif
if (file.is_open()) {
file.write(reinterpret_cast<char const*>(data.data()), data.size());
if (!file.is_open()) {
file.close();
return Ok();
return Err("Unable to open file");
}
file.write(reinterpret_cast<char const*>(data.data()), data.size());
file.close();
return Err("Unable to open file");
return Ok();
}
Result<> utils::file::createDirectory(ghc::filesystem::path const& path) {

View file

@ -79,6 +79,7 @@ Result<ByteVector> web::fetchBytes(std::string const& url) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBytes);
auto res = curl_easy_perform(curl);
if (res != CURLE_OK) {
@ -118,6 +119,7 @@ Result<std::string> web::fetch(std::string const& url) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeString);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
auto res = curl_easy_perform(curl);
if (res != CURLE_OK) {
curl_easy_cleanup(curl);
@ -185,7 +187,7 @@ static std::unordered_map<std::string, SentAsyncWebRequestHandle> RUNNING_REQUES
static std::mutex RUNNING_REQUESTS_MUTEX;
SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const& req, std::string const& id) :
m_id(id), m_url(req.m_url), m_target(req.m_target), m_extra(req.extra()), m_httpHeaders(req.m_httpHeaders) {
m_self(self), m_id(id), m_url(req.m_url), m_target(req.m_target), m_extra(req.extra()), m_httpHeaders(req.m_httpHeaders) {
#define AWAIT_RESUME() \
{\
@ -269,7 +271,7 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
// Follow redirects
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
// Fail if response code is 4XX or 5XX
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0L); // we will handle http errors manually
// Headers end
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
@ -294,10 +296,13 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
}
return 1;
}
Loader::get()->queueInMainThread([self = data->self, now, total]() {
std::lock_guard _(self->m_mutex);
std::unique_lock<std::mutex> l(self->m_mutex);
for (auto& prog : self->m_progresses) {
l.unlock();
prog(*self->m_self, now, total);
l.lock();
}
});
return 0;
@ -305,12 +310,17 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
);
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &data);
auto res = curl_easy_perform(curl);
long code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
if (res != CURLE_OK) {
long code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
curl_easy_cleanup(curl);
return this->error("Fetch failed: " + std::string(curl_easy_strerror(res)), code);
}
if (code >= 400 && code < 600) {
std::string response_str(ret.begin(), ret.end());
curl_easy_cleanup(curl);
return this->error(response_str, code);
}
curl_easy_cleanup(curl);
AWAIT_RESUME();
@ -320,9 +330,11 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
m_finished = true;
Loader::get()->queueInMainThread([this, ret]() {
std::lock_guard _(m_mutex);
std::unique_lock<std::mutex> l(m_mutex);
for (auto& then : m_thens) {
l.unlock();
then(*m_self, ret);
l.lock();
}
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
RUNNING_REQUESTS.erase(m_id);
@ -347,9 +359,11 @@ void SentAsyncWebRequest::Impl::doCancel() {
}
Loader::get()->queueInMainThread([this]() {
std::lock_guard _(m_mutex);
std::unique_lock<std::mutex> l(m_mutex);
for (auto& canc : m_cancelleds) {
l.unlock();
canc(*m_self);
l.lock();
}
});
@ -385,9 +399,11 @@ void SentAsyncWebRequest::Impl::error(std::string const& error, int code) {
});
Loader::get()->queueInMainThread([this, error, code]() {
{
std::lock_guard _(m_mutex);
std::unique_lock<std::mutex> l(m_mutex);
for (auto& expect : m_expects) {
l.unlock();
expect(error, code);
l.lock();
}
}
std::lock_guard _(RUNNING_REQUESTS_MUTEX);

View file

@ -0,0 +1,39 @@
#include "Common.hpp"
#ifdef GEODE_IS_ANDROID
// Add known android struct members here
// needed classes are ones in the ids folder and some generic ones (i think they are already done though so only ids)
GEODE_MEMBER_CHECK(GameManager, m_playLayer, 0x138);
GEODE_MEMBER_CHECK(GameManager, m_levelEditorLayer, 0x13c);
GEODE_MEMBER_CHECK(GameManager, m_canGetLevelSaveData, 0x28c);
static_assert(sizeof(GJBaseGameLayer) == 0x2cc);
GEODE_MEMBER_CHECK(PlayLayer, unknown4e8, 0x2e8);
GEODE_MEMBER_CHECK(PlayLayer, m_endPortal, 0x324);
GEODE_MEMBER_CHECK(PlayLayer, m_bottomGround, 0x37c);
GEODE_MEMBER_CHECK(PlayLayer, m_topGround, 0x380);
GEODE_MEMBER_CHECK(PlayLayer, m_level, 0x470);
GEODE_MEMBER_CHECK(PlayLayer, m_shouldTryToKick, 0x4e0);
static_assert(sizeof(GameObject) == 0x42c);
GEODE_MEMBER_CHECK(GameStatsManager, m_dailyChests, 0x110);
GEODE_MEMBER_CHECK(GameStatsManager, m_completedLevels, 0x164);
GEODE_MEMBER_CHECK(DailyLevelPage, m_weekly, 0x1ed);
GEODE_MEMBER_CHECK(TeleportPortalObject, m_orangePortal, 0x430);
GEODE_MEMBER_CHECK(EditorUI, m_rotationControl, 0x16c);
GEODE_MEMBER_CHECK(EditorUI, m_updateTimeMarkers, 0x1a4);
GEODE_MEMBER_CHECK(EditorUI, m_selectedObjects, 0x1bc);
GEODE_MEMBER_CHECK(EditorUI, m_selectedObject, 0x2c4);
GEODE_MEMBER_CHECK(MoreSearchLayer, m_enterSongID, 0x1E4);
GEODE_MEMBER_CHECK(MoreSearchLayer, m_songLeftBtn, 0x1D4);
#endif

View file

@ -4,7 +4,7 @@ set(PROJECT_NAME TestMembers)
project(${PROJECT_NAME} VERSION 1.0.0)
add_library(${PROJECT_NAME} SHARED MacOS.cpp Windows.cpp)
add_library(${PROJECT_NAME} SHARED MacOS.cpp Windows.cpp Android.cpp)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
target_include_directories(${PROJECT_NAME} PRIVATE