Merge remote-tracking branch 'refs/remotes/origin/1.4.0-dev' into 1.4.0-dev

This commit is contained in:
camila314 2023-10-22 10:40:08 -05:00
commit acaa8ca4b7
63 changed files with 3059 additions and 1078 deletions

View file

@ -2,17 +2,16 @@ name: Crash Report
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: input
id: geode-confirmation
- 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, after you do that type in "confirm" in the input field above.
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*.
placeholder: "Please, read the text below."
validations:
required: true
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

@ -20,6 +20,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 +35,7 @@ jobs:
- name: macOS
os: macos-latest
id: mac
cli_id: mac
extra_flags: >
-DCMAKE_C_COMPILER=clang
-DCMAKE_CXX_COMPILER=clang++
@ -43,6 +45,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 +74,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 +82,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 +132,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,6 +179,12 @@ 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
- name: Zip Android Artifacts
uses: vimtor/action-zip@v1.1
with:
files: geode-android/Geode.so
dest: geode-${{ steps.ref.outputs.hash }}-android.zip
- name: Zip Resources
uses: vimtor/action-zip@v1.1
with:
@ -166,4 +205,5 @@ jobs:
./geode-installer-${{ steps.ref.outputs.hash }}-win.exe
./geode-${{ steps.ref.outputs.hash }}-mac.zip
./geode-${{ steps.ref.outputs.hash }}-win.zip
./geode-${{ steps.ref.outputs.hash }}-android.zip
./resources.zip

View file

@ -28,6 +28,7 @@ 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/geode-*-android.zip geode-v${{ steps.ref.outputs.version }}-android.zip
mv dev/resources.zip resources.zip
- name: Create Draft Release
@ -48,4 +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
./geode-v${{ steps.ref.outputs.version }}-android.zip
./resources.zip

View file

@ -1,5 +1,32 @@
# 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.5
* Follow redirect in web::utils functions (a942a45)
* Lots of bindings
@ -221,7 +248,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`
@ -289,7 +316,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
@ -191,6 +191,9 @@ 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)

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,7 +128,7 @@ class cocos2d::CCClippingNode {
// void updateConnected() = win 0xc7fb0;
//}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCConfiguration {
void gatherGPUInfo() = mac 0x2a6e10;
}
@ -131,7 +138,7 @@ 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;
@ -147,7 +154,7 @@ class cocos2d::CCDictionary {
auto valueForKey(gd::string const&) = mac 0x1907a0;
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCDirector {
CCDirector() {}
~CCDirector() {}
@ -190,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;
@ -206,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;
@ -218,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;
@ -234,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;
}
@ -288,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;
@ -299,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();
@ -319,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);
@ -343,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;
@ -352,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;
@ -361,21 +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
@ -384,7 +391,7 @@ class cocos2d::CCKeypadHandler {
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCLabelBMFont {
CCLabelBMFont() = mac 0x347b60;
static cocos2d::CCLabelBMFont* create(char const*, char const*) = mac 0x347660;
@ -425,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;
@ -433,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;
@ -474,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;
@ -492,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;
@ -513,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;
@ -537,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;
@ -553,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;
@ -572,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;
@ -592,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;
@ -603,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;
@ -752,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;
@ -771,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;
@ -787,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;
@ -798,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;
@ -807,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;
@ -828,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) {
@ -881,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;
@ -901,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;
@ -983,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;
@ -1008,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;
@ -1045,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
@ -1053,7 +1060,7 @@ class cocos2d::CCTargetedTouchHandler {
}
[[link(win)]]
[[link(win, android)]]
class cocos2d::CCTexture2D {
CCTexture2D() = mac 0x246280, win 0xe9300;
~CCTexture2D() = mac 0x246350, win 0xe93f0;
@ -1078,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;
@ -1086,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;
@ -1099,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;
@ -1119,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;
@ -1129,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;
@ -1139,20 +1146,20 @@ 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 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;
@ -1176,27 +1183,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;
@ -1242,7 +1256,7 @@ 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;
@ -1262,7 +1276,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;
@ -1278,17 +1292,6 @@ class cocos2d {
static void CCMessageBox(const char* msg, const char* title) = mac 0xbabc0;
}
//uintptr_t macNumberOfDraws() {
// return geode::base::get() + 0x69ae90;
//}
//void ccIncrementGLDraws(int n) {
//#ifdef GEODE_IS_MACOS
// *reinterpret_cast<int*>(macNumberOfDraws()) += n;
//#else
// CC_INCREMENT_GL_DRAWS(n);
//#endif
//}
[[link(win)]]
class DS_Dictionary {
DS_Dictionary() = mac 0xbe9a0;
@ -1302,7 +1305,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()
@ -59,7 +59,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 +130,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 +187,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()
@ -205,6 +207,17 @@ function(setup_geode_mod proname)
set_target_properties(${proname} PROPERTIES PREFIX "")
set_target_properties(${proname} PROPERTIES OUTPUT_NAME ${MOD_ID})
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()
endfunction()
function(create_geode_file proname)
@ -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})

View file

@ -78,10 +78,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

@ -4,7 +4,7 @@ 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;
@ -202,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;
@ -221,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;
@ -234,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

@ -149,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

@ -295,7 +295,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

@ -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

@ -32,7 +32,8 @@ namespace geode {
LoadFailed,
EnableFailed,
MissingDependency,
PresentIncompatibility
PresentIncompatibility,
UnzipFailed
};
Type type;
std::variant<ghc::filesystem::path, ModMetadata, Mod*> cause;

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>
@ -49,8 +49,7 @@ namespace geode::modifier {
}
};
[[deprecated("Will be removed in 1.0.0")]]
GEODE_DLL size_t getFieldIndexForClass(size_t hash);
[[deprecated("Will be removed in 1.0.0")]] GEODE_DLL size_t getFieldIndexForClass(size_t hash);
GEODE_DLL size_t getFieldIndexForClass(char const* name);
template <class Parent, class Base>
@ -58,7 +57,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.
@ -120,7 +119,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

@ -51,8 +51,8 @@ namespace geode {
size_t getMaxLines();
void setWidth(const float width);
float getWidth();
void setScale(const float scale);
float getScale();
void setScale(const float scale) override;
float getScale() override;
void setLinePadding(const float padding);
float getLinePadding();
std::vector<cocos2d::CCLabelBMFont*> getLines();

View file

@ -244,16 +244,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

@ -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

@ -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

@ -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

@ -88,6 +88,8 @@ int geodeEntry(void* platformData) {
return 1;
}
crashlog::setupPlatformHandlerPost();
log::info("Set up loader");
// download and install new loader update in the background

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

@ -252,7 +252,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);
@ -296,7 +296,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();
@ -307,7 +307,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 {
@ -333,6 +333,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
@ -387,8 +391,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) {
@ -591,7 +594,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;
}
}

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());
@ -388,7 +385,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;
}
@ -402,32 +399,83 @@ 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;
}
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() {
@ -568,16 +616,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:
@ -585,7 +646,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;
@ -595,6 +656,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;
@ -603,13 +669,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();
});
}
@ -742,14 +804,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(
@ -770,65 +830,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.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.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() {
@ -846,7 +908,7 @@ bool Loader::Impl::verifyLoaderResources() {
ghc::filesystem::is_directory(resourcesDir)
)) {
log::debug("Resources directory does not exist");
this->downloadLoaderResources();
this->downloadLoaderResources(true);
return false;
}

View file

@ -58,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;
@ -83,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();

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;
}
@ -568,21 +569,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) {

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

@ -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

@ -252,6 +252,8 @@ bool crashlog::didLastLaunchCrash() {
return g_lastLaunchCrashed;
}
void crashlog::setupPlatformHandlerPost() {}
ghc::filesystem::path crashlog::getCrashLogDirectory() {
return dirs::getGeodeDir() / "crashlogs";
}

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

@ -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

@ -355,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

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,36 @@
#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);
#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