mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-22 23:48:08 -05:00
Merge branch '1.4.0-dev' into 2.0.0-dev
This commit is contained in:
commit
442789e61a
100 changed files with 3777 additions and 790 deletions
12
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
12
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
|
@ -1,7 +1,17 @@
|
|||
name: Bug Report
|
||||
description: Report a bug where something is not working as expected in Geode Loader (not specific mods), which does not crash the game.
|
||||
description: Report a Geode bug (not mods themselves) where something is not working as expected in Geode Loader (not mods created by others), which does not crash the game.
|
||||
labels: [ "unverified", "bug" ]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Geode Issue
|
||||
description: |
|
||||
The Geode repository is for issues of *Geode Loader*, not individual mods created by other developers.
|
||||
When submitting a bug report, please make sure that the bug is *actually* related to ***Geode Loader itself*** and not to a mod or mod combination.
|
||||
Failing to do this will get your issue *closed without explanation*.
|
||||
options:
|
||||
- label: I confirm that this bug is NOT related to a mod but directly to Geode Loader itself.
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
|
|
12
.github/ISSUE_TEMPLATE/crash-report.yml
vendored
12
.github/ISSUE_TEMPLATE/crash-report.yml
vendored
|
@ -1,7 +1,17 @@
|
|||
name: Crash Report
|
||||
description: Report a bug that crashes the game or prevents startup caused by Geode Loader (not individual mods).
|
||||
description: Report a Geode bug (not mods themselves) that crashes the game or prevents startup caused by Geode Loader (not mods created by others).
|
||||
labels: [ "unverified", "crash" ]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Geode Issue
|
||||
description: |
|
||||
The Geode repository is for issues of *Geode Loader*, not individual mods created by other developers.
|
||||
When submitting a crash report, please make sure that the crash is *actually* related to ***Geode Loader itself*** and not to a mod or mod combination.
|
||||
Failing to do this will get your issue *closed without explanation*.
|
||||
options:
|
||||
- label: I confirm that this crash is NOT related to a mod but directly to Geode Loader itself.
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
|
|
58
.github/workflows/build.yml
vendored
58
.github/workflows/build.yml
vendored
|
@ -2,6 +2,7 @@ name: Build Binaries
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- '**' # every branch
|
||||
|
@ -20,6 +21,7 @@ jobs:
|
|||
- name: Windows
|
||||
os: windows-latest
|
||||
id: win
|
||||
cli_id: win
|
||||
extra_flags: -T host=x64 -A win32 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DGEODE_DEBUG=On
|
||||
# uncomment to use vs clang-cl and ninja
|
||||
#extra_flags: >
|
||||
|
@ -34,6 +36,7 @@ jobs:
|
|||
- name: macOS
|
||||
os: macos-latest
|
||||
id: mac
|
||||
cli_id: mac
|
||||
extra_flags: >
|
||||
-DCMAKE_C_COMPILER=clang
|
||||
-DCMAKE_CXX_COMPILER=clang++
|
||||
|
@ -43,6 +46,21 @@ jobs:
|
|||
cli_cmd: 'chmod +x $GITHUB_WORKSPACE/cli/geode'
|
||||
package_cmd: './installer/mac/package.sh ./bin/nightly ./installer/mac/geode-installer-mac.pkg'
|
||||
installer_path: './installer/mac/geode-installer-mac.pkg'
|
||||
|
||||
- name: Android
|
||||
os: ubuntu-latest
|
||||
id: android
|
||||
cli_id: linux
|
||||
extra_flags: >
|
||||
-DCMAKE_TOOLCHAIN_FILE=$NDK_HOME/build/cmake/android.toolchain.cmake
|
||||
-DANDROID_ABI=armeabi-v7a
|
||||
-DANDROID_PLATFORM=android-23
|
||||
-DGEODE_DONT_BUILD_TEST_MODS=1
|
||||
-G Ninja
|
||||
cli_cmd: 'chmod +x $GITHUB_WORKSPACE/cli/geode'
|
||||
package_cmd: ''
|
||||
installer_path: ''
|
||||
|
||||
|
||||
name: Build ${{ matrix.config.name }}
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
|
@ -57,7 +75,7 @@ jobs:
|
|||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: ${{ matrix.config.os }}
|
||||
if: matrix.config.id == 'mac'
|
||||
if: matrix.config.id != 'win'
|
||||
|
||||
- name: Setup MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1.12.1
|
||||
|
@ -65,22 +83,36 @@ jobs:
|
|||
arch: amd64_x86
|
||||
if: matrix.config.id == 'win'
|
||||
|
||||
- name: Setup NDK
|
||||
uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r26b
|
||||
add-to-path: false
|
||||
if: matrix.config.id == 'android'
|
||||
|
||||
- name: Download CLI
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
with:
|
||||
repository: geode-sdk/cli
|
||||
latest: true
|
||||
fileName: '*-${{ matrix.config.id }}.zip'
|
||||
fileName: '*-${{ matrix.config.cli_id }}.zip'
|
||||
tarBall: false
|
||||
zipBall: false
|
||||
out-file-path: "cli"
|
||||
|
||||
- name: Setup CLI
|
||||
run: |
|
||||
7z x "${{ github.workspace }}/cli/*-${{ matrix.config.id }}.zip" -o"${{ github.workspace }}/cli"
|
||||
7z x "${{ github.workspace }}/cli/*-${{ matrix.config.cli_id }}.zip" -o"${{ github.workspace }}/cli"
|
||||
${{ matrix.config.cli_cmd }}
|
||||
echo "${{ github.workspace }}/cli" >> $GITHUB_PATH
|
||||
|
||||
- name: Setup Android Env
|
||||
run: |
|
||||
echo "NDK_HOME=${{ steps.setup-ndk.outputs.ndk-path }}" >> "$GITHUB_ENV"
|
||||
sudo apt install ninja-build
|
||||
if: matrix.config.id == 'android'
|
||||
|
||||
- name: Configure
|
||||
run: >
|
||||
cmake -B ${{ github.workspace }}/build
|
||||
|
@ -101,12 +133,14 @@ jobs:
|
|||
|
||||
- name: Package Installer
|
||||
run: ${{ matrix.config.package_cmd }}
|
||||
if: matrix.config.id != 'android'
|
||||
|
||||
- name: Upload Installer
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: geode-installer-${{ matrix.config.id }}
|
||||
path: ${{ matrix.config.installer_path }}
|
||||
if: matrix.config.id != 'android'
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
|
@ -146,21 +180,19 @@ jobs:
|
|||
files: geode-win/XInput9_1_0.dll geode-win/Geode.dll geode-win/GeodeUpdater.exe geode-win/Geode.lib geode-win/Geode.pdb
|
||||
dest: geode-${{ steps.ref.outputs.hash }}-win.zip
|
||||
|
||||
# TODO change in 2.0.0
|
||||
- name: Zip Windows Resources
|
||||
- name: Zip Android Artifacts
|
||||
uses: vimtor/action-zip@v1.1
|
||||
with:
|
||||
files: geode-win/resources
|
||||
dest: resources-win.zip
|
||||
files: geode-android/Geode.so
|
||||
dest: geode-${{ steps.ref.outputs.hash }}-android.zip
|
||||
|
||||
# This is basically a hack because of line endings. Blame windows.
|
||||
- name: Zip MacOS Resources
|
||||
- name: Zip Resources
|
||||
uses: vimtor/action-zip@v1.1
|
||||
with:
|
||||
files: geode-mac/resources
|
||||
dest: resources-mac.zip
|
||||
dest: resources.zip
|
||||
|
||||
- name: Update Nightly Release
|
||||
- name: Update Development Release
|
||||
uses: andelf/nightly-release@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@ -174,5 +206,5 @@ jobs:
|
|||
./geode-installer-${{ steps.ref.outputs.hash }}-win.exe
|
||||
./geode-${{ steps.ref.outputs.hash }}-mac.zip
|
||||
./geode-${{ steps.ref.outputs.hash }}-win.zip
|
||||
./resources-win.zip
|
||||
./resources-mac.zip
|
||||
./geode-${{ steps.ref.outputs.hash }}-android.zip
|
||||
./resources.zip
|
||||
|
|
8
.github/workflows/draft.yml
vendored
8
.github/workflows/draft.yml
vendored
|
@ -28,8 +28,8 @@ jobs:
|
|||
mv dev/geode-installer-*-win.exe geode-installer-v${{ steps.ref.outputs.version }}-win.exe
|
||||
mv dev/geode-*-mac.zip geode-v${{ steps.ref.outputs.version }}-mac.zip
|
||||
mv dev/geode-*-win.zip geode-v${{ steps.ref.outputs.version }}-win.zip
|
||||
mv dev/resources-win.zip resources-win.zip
|
||||
mv dev/resources-mac.zip resources-mac.zip
|
||||
mv dev/geode-*-android.zip geode-v${{ steps.ref.outputs.version }}-android.zip
|
||||
mv dev/resources.zip resources.zip
|
||||
|
||||
- name: Create Draft Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
|
@ -49,5 +49,5 @@ jobs:
|
|||
./geode-installer-v${{ steps.ref.outputs.version }}-win.exe
|
||||
./geode-v${{ steps.ref.outputs.version }}-mac.zip
|
||||
./geode-v${{ steps.ref.outputs.version }}-win.zip
|
||||
./resources-win.zip
|
||||
./resources-mac.zip
|
||||
./geode-v${{ steps.ref.outputs.version }}-android.zip
|
||||
./resources.zip
|
||||
|
|
4
.github/workflows/test-offsets.yml
vendored
4
.github/workflows/test-offsets.yml
vendored
|
@ -2,7 +2,11 @@ name: Test Offsets
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
paths:
|
||||
- 'bindings/**' # only when adjusting bindings
|
||||
- 'loader/test/members/**'
|
||||
branches:
|
||||
- '**' # every branch
|
||||
- '!no-build-**' # unless marked as no-build
|
||||
|
|
18
.gitignore
vendored
18
.gitignore
vendored
|
@ -46,20 +46,32 @@ build2
|
|||
build-docs/
|
||||
bin
|
||||
|
||||
# Ignore docs folders
|
||||
docs/**
|
||||
docs
|
||||
|
||||
# Ignore codegenned files
|
||||
loader/src/internal/about.hpp
|
||||
loader/src/internal/resources.hpp
|
||||
loader/resources/mod.json
|
||||
loader/resources/version
|
||||
loader/resources/blanks/rename.js
|
||||
loader/resources/about.md
|
||||
loader/resources/changelog.md
|
||||
loader/resources/support.md
|
||||
|
||||
# Ignore generated files
|
||||
installer/mac/*.pkg
|
||||
installer/windows/*.exe
|
||||
|
||||
# Ignore fod's include directories which are stored in this funny file
|
||||
fods-catgirl-hideout.txt
|
||||
|
||||
# Ignore I don't even know what that is probably fod's flash testing script
|
||||
test-docs.bat
|
||||
|
||||
# krita files too because alk is funny
|
||||
# Ignore krita files too because we don't want our project files shaking my head
|
||||
**/*.kra
|
||||
|
||||
installer/mac/*.pkg
|
||||
installer/windows/*.exe
|
||||
# Ignore clangd cache
|
||||
.cache
|
||||
|
|
88
CHANGELOG.md
88
CHANGELOG.md
|
@ -1,5 +1,89 @@
|
|||
# Geode Changelog
|
||||
|
||||
## v1.4.0
|
||||
* Add Android support !!!!
|
||||
* Implement every Geode functionality except `utils::file::openFolder`
|
||||
* Requires the Geode launcher in order to be used
|
||||
* Fixes the text input node allowing typing for 1 less character
|
||||
* Uses `logcat` in order to get crash reports, so reopening the game is required to generate them
|
||||
* Broma requires classes to be added `[[link(android)]]` in order to be linked
|
||||
* All Geode and GD files are stored in `Android/data/com.geode.launcher/files`
|
||||
* Game files in `game`, save files in `save`
|
||||
* Allow logging to be disabled per mod (6d599a5)
|
||||
* Mod cells use layouts (114fa46)
|
||||
* MacOS console is now separate (182984d)
|
||||
* Add uninstall button to Geode mod (Only functional in Windows currently) (a738320)
|
||||
* Make new version label invisible on download (0f179da)
|
||||
* Fix the toggling of disabled dependencies (cd89ef1)
|
||||
* Fix spritesheet issues (ef47647)
|
||||
* Change `LoadingLayer` (ef47647)
|
||||
* Make mod info popup top a layout (dd806e0)
|
||||
* Add `GEODE_HIDDEN` to inline unique functions (71a79ab)
|
||||
* Fix big mod icons (26a6c7e)
|
||||
* Fix `CCNode::removeChildByID` export (23cd456)
|
||||
* Make `MDTextArea` fit its size (140f38b)
|
||||
* Enable ESC/Back to go back in Geode mod list (2847bee)
|
||||
* Add `SimpleTextArea` (7f277a7)
|
||||
* Check modified date when unzipping `.geode` files (5c765c6)
|
||||
* Only hash markdown files on resource checking (f563c46)
|
||||
|
||||
## v1.3.10
|
||||
* Panic if decompressString2 fails, to prevent data loss (0787b8f4)
|
||||
|
||||
## v1.3.9
|
||||
* Fix followThunkFunction (4b766301)
|
||||
|
||||
## v1.3.8
|
||||
* Implement a save file fix for Windows (391f63ed)
|
||||
* Recursively follow jumps in followThunkFunction (44a018cd)
|
||||
* Remove need for GEODE_DEBUG for crashlogs (e8a326f7)
|
||||
* Some bindings (f18335fa)
|
||||
|
||||
## v1.3.7
|
||||
* Fix web download deadlock (16418562)
|
||||
* Set max time for updating index notification (f7962246)
|
||||
* Log geode version on startup (c5550a67)
|
||||
* Fix logic error in addHook (5cf0f3c2)
|
||||
* Improve logging + minor refactors (5083017b)
|
||||
* Some bindings changes
|
||||
|
||||
## v1.3.6
|
||||
* Allow error responses in our WebRequest classes (237128bf)
|
||||
* Display unhandled C++ exceptions in crash log (fdd78aca, 0d091626, 52421d8c, 0472075f)
|
||||
* Fix GEODE_CLI force caching when not found (0a113744)
|
||||
* Only write checksum file after unzipping, fixes inconsistent index state (b4fbea51)
|
||||
* Fix the index notif staying on all the time (c967b520)
|
||||
* Bump minimum required CMake version in codegen (27ed63e7)
|
||||
* Only show update indicator if mod is enabled (8762714c)
|
||||
* Fix FLAlertLayer m_scrollingLayer not being a ScrollingLayer (9694b35d)
|
||||
* Fix gnustl vector dtor (b55e6465, 0bdb0df7)
|
||||
* Loads of bindings changes
|
||||
|
||||
## v1.3.5
|
||||
* Follow redirect in web::utils functions (a942a45)
|
||||
* Lots of bindings
|
||||
* Make codegen symbols private visibility (696a2ca)
|
||||
* Add deadstrip to macos (0d62940)
|
||||
* Readd the nullptr check in InstallListPopup::createCells (499f256)
|
||||
* Fix garagelayer ids on not logged in users (dd0179c)
|
||||
|
||||
## v1.3.4
|
||||
* Implement string setting character filters (cf8fbba)
|
||||
* Update bindings
|
||||
|
||||
## v1.3.3
|
||||
* Reunify resources.zip (81de161)
|
||||
|
||||
## v1.3.2
|
||||
* Fix alignment of some textures (8f39c38)
|
||||
* Bring back unknown problems (0663569)
|
||||
* Fix some Windows 7 incompatibility (2d2bdd1)
|
||||
* Remove enabled from the crashlogs (5b7d318)
|
||||
* Make index unzipping async (7c582f1)
|
||||
* Fix mods by developer crashing when mod was toggled (a6a47bf)
|
||||
* Fix nested lists in the markdown (2723588)
|
||||
* Fix search paths (8f39c38, aa55ebe)
|
||||
|
||||
## v1.3.1
|
||||
* Fix TulipHook not relocating RIP relative operands on MacOS (6cad19d)
|
||||
|
||||
|
@ -196,7 +280,7 @@ Thank you to [Fleeym](https://github.com/Fleeym/Fleeym) for contributing to this
|
|||
* Fix recursive comparison in VersionTag
|
||||
* `geode/unzipped` is now deleted on startup if it exists
|
||||
|
||||
## v1.0.0-beta.9
|
||||
## v1.0.0-beta.9
|
||||
* Fix multiple modifiers not being able to have fields on same class due to having same field index
|
||||
* Add `Result::ok` and `Result::err` for converting the `Result` into `std::optional`
|
||||
|
||||
|
@ -264,7 +348,7 @@ Thank you to [Fleeym](https://github.com/Fleeym/Fleeym) for contributing to this
|
|||
- Something related to codegen and addresser? I have no clue what it does, so you probably won't have either
|
||||
- MacOS minimum version bumped to 10.14
|
||||
|
||||
## v1.0.0-beta.2
|
||||
## v1.0.0-beta.2
|
||||
* Fixed bug where `Mod::getSavedValue` would cause a crash due to trying operator on a null JSON value
|
||||
* Fixed bug where loading would crash if one of the mods' binaries failed to load
|
||||
|
||||
|
|
|
@ -71,8 +71,8 @@ set(GEODE_BIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
|
|||
set(GEODE_LOADER_PATH ${CMAKE_CURRENT_SOURCE_DIR}/loader)
|
||||
set(GEODE_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
include(cmake/GeodeFile.cmake)
|
||||
include(cmake/Platform.cmake)
|
||||
include(cmake/GeodeFile.cmake)
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
if (PROJECT_IS_TOP_LEVEL AND NOT GEODE_BUILDING_DOCS)
|
||||
|
@ -96,7 +96,7 @@ if (PROJECT_IS_TOP_LEVEL AND NOT GEODE_BUILDING_DOCS)
|
|||
set(TULIP_LINK_SOURCE ON)
|
||||
endif()
|
||||
set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE)
|
||||
CPMAddPackage("gh:geode-sdk/TulipHook#3423a29")
|
||||
CPMAddPackage("gh:geode-sdk/TulipHook#d2132de")
|
||||
set(CMAKE_WARN_DEPRECATED ON CACHE BOOL "" FORCE)
|
||||
|
||||
# Silence warnings from dependencies
|
||||
|
@ -185,7 +185,17 @@ target_include_directories(GeodeCodegenSources PRIVATE
|
|||
${GEODE_LOADER_PATH}/include/Geode/cocos/extensions
|
||||
${GEODE_LOADER_PATH}/include/Geode/fmod
|
||||
)
|
||||
set_target_properties(GeodeCodegenSources PROPERTIES CXX_VISIBILITY_PRESET hidden)
|
||||
target_compile_features(GeodeCodegenSources PUBLIC cxx_std_20)
|
||||
|
||||
if (APPLE)
|
||||
target_compile_options(GeodeCodegenSources PUBLIC -ffunction-sections -fdata-sections)
|
||||
target_link_options(GeodeCodegenSources PUBLIC -dead_strip)
|
||||
elseif(ANDROID)
|
||||
target_compile_options(GeodeCodegenSources PUBLIC -ffunction-sections -fdata-sections)
|
||||
target_link_options(GeodeCodegenSources PUBLIC -Wl,--gc-sections)
|
||||
endif()
|
||||
|
||||
if (NOT GEODE_DISABLE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(GeodeCodegenSources INTERFACE
|
||||
"${GEODE_LOADER_PATH}/include/Geode/Bindings.hpp"
|
||||
|
@ -218,6 +228,11 @@ else()
|
|||
set(GEODE_PLATFORM_BIN_PATH ${GEODE_BIN_PATH}/${PROJECT_VERSION}/${GEODE_PLATFORM_BINARY})
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
# This allows you to compile in debug mode
|
||||
add_compile_definitions(_HAS_ITERATOR_DEBUGGING=0)
|
||||
endif()
|
||||
|
||||
|
||||
if (PROJECT_IS_TOP_LEVEL)
|
||||
add_subdirectory(loader)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// clang-format off
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCActionTween {
|
||||
static cocos2d::CCActionTween* create(float, char const*, float, float) = mac 0x447590;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCActionManager {
|
||||
CCActionManager() {
|
||||
m_pTargets = nullptr;
|
||||
|
@ -21,17 +21,17 @@ class cocos2d::CCActionManager {
|
|||
auto resumeTarget(cocos2d::CCObject*) = mac 0x10bd20;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCAnimate {
|
||||
static cocos2d::CCAnimate* create(cocos2d::CCAnimation*) = mac 0x1f8fc0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCAnimation {
|
||||
static auto createWithSpriteFrames(cocos2d::CCArray*, float) = mac 0x140df0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCApplication {
|
||||
virtual auto run();
|
||||
virtual auto getCurrentLanguage() = mac 0x1a3f40, ios 0x10e508;
|
||||
|
@ -39,13 +39,15 @@ class cocos2d::CCApplication {
|
|||
virtual auto openURL(char const*) = mac 0x1a4550, ios 0x10e7a4;
|
||||
virtual auto setAnimationInterval(double) = mac 0x1a3ee0, ios 0x10e494;
|
||||
static auto sharedApplication() = mac 0x1a3f30;
|
||||
|
||||
[[link(win)]]
|
||||
bool getControllerConnected() const = mac 0x27d1b0;
|
||||
// ~CCApplication() = mac 0x1a3d10, ios 0x10e384;
|
||||
CCApplication() {}
|
||||
~CCApplication() {}
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCArray {
|
||||
// auto addObject(cocos2d::CCObject*) = mac 0x419f90, ios 0x16504c;
|
||||
auto addObjectNew(cocos2d::CCObject*) = mac 0x41a450;
|
||||
|
@ -67,27 +69,32 @@ class cocos2d::CCArray {
|
|||
// auto stringAtIndex(unsigned int) = mac 0x41a320;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCBezierTo {
|
||||
static cocos2d::CCBezierTo* create(float, cocos2d::_ccBezierConfig const&) = mac 0x1f6c10;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCBMFontConfiguration {
|
||||
static cocos2d::CCBMFontConfiguration* create(char const*) = mac 0x3450f0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCCallFunc {
|
||||
static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFunc) = mac 0x454d90;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCCallFuncO {
|
||||
static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFuncO, cocos2d::CCObject*) = mac 0x455940;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCCallFuncND {
|
||||
static auto create(cocos2d::CCObject*, cocos2d::SEL_CallFuncND, void*) = mac 0x455470;
|
||||
}
|
||||
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCClippingNode {
|
||||
CCClippingNode() {
|
||||
m_pStencil = nullptr;
|
||||
|
@ -121,12 +128,17 @@ class cocos2d::CCClippingNode {
|
|||
// void updateConnected() = win 0xc7fb0;
|
||||
//}
|
||||
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCConfiguration {
|
||||
void gatherGPUInfo() = mac 0x2a6e10;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
class cocos2d::CCDelayTime {
|
||||
static cocos2d::CCDelayTime* create(float) = mac 0x1f4380;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCDictionary {
|
||||
auto allKeys() = mac 0x190450, ios 0x2de774;
|
||||
auto count() = mac 0x190430;
|
||||
|
@ -142,7 +154,7 @@ class cocos2d::CCDictionary {
|
|||
auto valueForKey(gd::string const&) = mac 0x1907a0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCDirector {
|
||||
CCDirector() {}
|
||||
~CCDirector() {}
|
||||
|
@ -185,7 +197,7 @@ class cocos2d::CCDirector {
|
|||
auto popSceneWithTransition(float, cocos2d::PopTransition) = mac 0x24a8b0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCDrawNode {
|
||||
CCDrawNode() = mac 0x378b40, win 0x6b9f0;
|
||||
auto clear() = mac 0x379e80;
|
||||
|
@ -201,7 +213,7 @@ class cocos2d::CCDrawNode {
|
|||
virtual ~CCDrawNode() = mac 0x378cc0, win 0x6ba60;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCAction {
|
||||
CCAction() = mac 0x35b610, win 0x7a6d0;
|
||||
virtual ~CCAction() = mac 0x35b6b0, win 0x7a7f0;
|
||||
|
@ -213,13 +225,13 @@ class cocos2d::CCAction {
|
|||
auto update(float time) = mac 0x35b890;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCFiniteTimeAction {
|
||||
// same as CCActionInterval::reverse i think
|
||||
auto reverse() = mac 0x1f2720;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCActionInterval {
|
||||
auto copyWithZone(cocos2d::CCZone* zone) = mac 0x1f2550;
|
||||
auto isDone() = mac 0x1f2640;
|
||||
|
@ -229,32 +241,32 @@ class cocos2d::CCActionInterval {
|
|||
bool initWithDuration(float d) = mac 0x1f2510;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCEaseBackIn {
|
||||
static cocos2d::CCEaseBackIn* create(cocos2d::CCActionInterval*) = mac 0x2a41b0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCEaseElasticIn {
|
||||
static cocos2d::CCEaseElasticIn* create(cocos2d::CCActionInterval*, float) = mac 0x2a2e00;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCEaseElasticOut {
|
||||
static cocos2d::CCEaseElasticOut* create(cocos2d::CCActionInterval*, float) = mac 0x2a3080;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCEaseIn {
|
||||
static cocos2d::CCEaseIn* create(cocos2d::CCActionInterval*, float) = mac 0x2a1960;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCEaseInOut {
|
||||
static cocos2d::CCEaseInOut* create(cocos2d::CCActionInterval*, float) = mac 0x2a1d80;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCEaseOut {
|
||||
static cocos2d::CCEaseOut* create(cocos2d::CCActionInterval*, float) = mac 0x2a1b70;
|
||||
}
|
||||
|
@ -283,7 +295,7 @@ class cocos2d::CCEGLView {
|
|||
void onGLFWWindowSizeFunCallback(GLFWwindow* window, int width, int height);
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCEGLViewProtocol {
|
||||
CCEGLViewProtocol() = win 0xbac00;
|
||||
virtual ~CCEGLViewProtocol() = win 0xbacc0;
|
||||
|
@ -294,17 +306,17 @@ class cocos2d::CCEGLViewProtocol {
|
|||
virtual void setFrameSize(float, float) = mac 0x29d960;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCFadeOut {
|
||||
static cocos2d::CCFadeOut* create(float) = mac 0x1f7d80;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCFadeTo {
|
||||
static cocos2d::CCFadeTo* create(float, unsigned char) = mac 0x1f7ff0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCFileUtils : cocos2d::TypeInfo {
|
||||
static cocos2d::CCFileUtils* sharedFileUtils() = mac 0x377030, ios 0x159450;
|
||||
static void purgeFileUtils();
|
||||
|
@ -314,19 +326,19 @@ class cocos2d::CCFileUtils : cocos2d::TypeInfo {
|
|||
void removeAllPaths() = mac 0x241600;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCGLProgram {
|
||||
auto setUniformsForBuiltins() = mac 0x232c70;
|
||||
auto use() = mac 0x231d70;
|
||||
bool compileShader(unsigned int* shader, unsigned int type, const char* source) = mac 0x231a30;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCHide {
|
||||
static cocos2d::CCHide* create() = mac 0x4543e0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCIMEDelegate {
|
||||
~CCIMEDelegate() {
|
||||
CCIMEDispatcher::sharedDispatcher()->removeDelegate(this);
|
||||
|
@ -338,7 +350,7 @@ class cocos2d::CCIMEDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCIMEDispatcher {
|
||||
static auto sharedDispatcher() = mac 0x2773f0, ios 0x12d170;
|
||||
auto addDelegate(cocos2d::CCIMEDelegate*) = mac 0x277480, ios 0x12d204;
|
||||
|
@ -347,7 +359,7 @@ class cocos2d::CCIMEDispatcher {
|
|||
void dispatchDeleteBackward() = mac 0x277af0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCImage {
|
||||
CCImage() = mac 0x24fa00, win 0xc5fd0;
|
||||
virtual ~CCImage() = mac 0x24fa80, win 0xc6100;
|
||||
|
@ -356,20 +368,21 @@ class cocos2d::CCImage {
|
|||
auto initWithImageData(void*, int, cocos2d::CCImage::EImageFormat, int, int, int) = mac 0x24fcb0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCKeyboardDispatcher {
|
||||
bool dispatchKeyboardMSG(cocos2d::enumKeyCodes, bool) = mac 0xe8190;
|
||||
const char* keyToString(cocos2d::enumKeyCodes) = mac 0xe8450;
|
||||
void updateModifierKeys(bool shft, bool ctrl, bool alt, bool cmd) = mac 0xe8430;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCKeyboardHandler {
|
||||
static cocos2d::CCKeyboardHandler* handlerWithDelegate(cocos2d::CCKeyboardDelegate*) = mac 0x242030;
|
||||
virtual auto initWithDelegate(cocos2d::CCKeyboardDelegate*) = mac 0x241ff0, ios 0x13f8b8;
|
||||
~CCKeyboardHandler() = mac 0x241e90, ios 0x13f87c, win 0x99a10;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCKeypadHandler {
|
||||
static cocos2d::CCKeypadHandler* handlerWithDelegate(cocos2d::CCKeypadDelegate*) = mac 0x1ff2d0;
|
||||
virtual auto initWithDelegate(cocos2d::CCKeypadDelegate*) = mac 0x1ff290, ios 0x69; // iOS stub
|
||||
|
@ -378,8 +391,9 @@ class cocos2d::CCKeypadHandler {
|
|||
}
|
||||
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCLabelBMFont {
|
||||
CCLabelBMFont() = mac 0x347b60;
|
||||
static cocos2d::CCLabelBMFont* create(char const*, char const*) = mac 0x347660;
|
||||
auto limitLabelWidth(float, float, float) = mac 0x34a6e0, ios 0x21b740;
|
||||
auto setFntFile(char const*) = mac 0x34a5f0;
|
||||
|
@ -387,7 +401,7 @@ class cocos2d::CCLabelBMFont {
|
|||
static auto create() = mac 0x3473f0;
|
||||
|
||||
virtual auto init() = mac 0x347b10, ios 0x2198e0;
|
||||
bool initWithString(const char* str, const char* fnt, float width, cocos2d::CCTextAlignment align, cocos2d::CCPoint offset);
|
||||
bool initWithString(const char* str, const char* fnt, float width, cocos2d::CCTextAlignment align, cocos2d::CCPoint offset) = mac 0x347710;
|
||||
virtual auto setScaleX(float) = mac 0x34a5b0, ios 0x21b6e8;
|
||||
virtual auto setScaleY(float) = mac 0x34a5d0, ios 0x21b714;
|
||||
virtual auto setScale(float) = mac 0x34a590, ios 0x21b6bc;
|
||||
|
@ -418,7 +432,7 @@ class cocos2d::CCLabelBMFont {
|
|||
virtual ~CCLabelBMFont() = mac 0x347e80, win 0x9be70;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCLabelTTF {
|
||||
static cocos2d::CCLabelTTF* create() = mac 0x1fa7e0;
|
||||
static cocos2d::CCLabelTTF* create(char const*, char const*, float) = mac 0x1fa840;
|
||||
|
@ -426,7 +440,7 @@ class cocos2d::CCLabelTTF {
|
|||
virtual auto setString(char const*) = mac 0x1fad70;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCLayer {
|
||||
CCLayer() = mac 0x2725b0, ios 0xc7708, win 0xa15e0;
|
||||
virtual auto ccTouchBegan(cocos2d::CCTouch*, cocos2d::CCEvent*) = mac 0x2734d0, ios 0xc810c;
|
||||
|
@ -467,7 +481,7 @@ class cocos2d::CCLayer {
|
|||
virtual ~CCLayer() = mac 0x2727b0, ios 0xc7848, win 0xa1940;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCLayerColor {
|
||||
CCLayerColor() = mac 0x274320, ios 0xc8aec, win 0xa1710;
|
||||
static cocos2d::CCLayerColor* create(cocos2d::_ccColor4B const&, float, float) = mac 0x2745e0;
|
||||
|
@ -485,7 +499,7 @@ class cocos2d::CCLayerColor {
|
|||
virtual ~CCLayerColor() = mac 0x2743d0, ios 0x2743e0, win 0xa1a20;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCLayerRGBA {
|
||||
CCLayerRGBA() = mac 0x2738d0, ios 0xc85cc, win 0xa1890;
|
||||
virtual auto init() = mac 0x273b40, ios 0xc8de8;
|
||||
|
@ -506,7 +520,7 @@ class cocos2d::CCLayerRGBA {
|
|||
virtual ~CCLayerRGBA() = mac 0x273aa0, ios 0xc77b0, win 0xa1b20;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMenu {
|
||||
auto alignItemsHorizontallyWithPadding(float) = mac 0x4393e0, ios 0x132508;
|
||||
auto alignItemsVerticallyWithPadding(float) = mac 0x439190;
|
||||
|
@ -530,7 +544,7 @@ class cocos2d::CCMenu {
|
|||
cocos2d::CCMenuItem* itemForTouch(cocos2d::CCTouch*) = mac 0x438dd0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMenuItem {
|
||||
bool initWithTarget(cocos2d::CCObject*, cocos2d::SEL_MenuHandler) = mac 0x1fb7f0;
|
||||
virtual ~CCMenuItem() = mac 0x1fb8e0, ios 0x2cdf4, win 0xab9c0;
|
||||
|
@ -546,13 +560,13 @@ class cocos2d::CCMenuItem {
|
|||
auto rect() = mac 0x1fbb00, ios 0x2cf3c;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMenuItemImage {
|
||||
// virtual ~CCMenuItemImage() = mac 0x1febb0;
|
||||
virtual auto init() = mac 0x1fd750;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMenuItemLabel {
|
||||
virtual ~CCMenuItemLabel() = mac 0x1fc0d0;
|
||||
virtual auto activate() = mac 0x1fc240;
|
||||
|
@ -565,7 +579,7 @@ class cocos2d::CCMenuItemLabel {
|
|||
virtual auto setLabel(cocos2d::CCNode*) = mac 0x1fbbc0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMenuItemSprite {
|
||||
// virtual ~CCMenuItemSprite() = mac 0x1feab0;
|
||||
virtual auto selected() = mac 0x1fd3f0, ios 0x2d2cc;
|
||||
|
@ -585,7 +599,7 @@ class cocos2d::CCMenuItemSprite {
|
|||
static auto create(cocos2d::CCNode*, cocos2d::CCNode*, cocos2d::CCObject*, cocos2d::SEL_MenuHandler) = mac 0x1fd2d0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMotionStreak {
|
||||
CCMotionStreak() = win 0xae310;
|
||||
virtual ~CCMotionStreak() = win 0xae450;
|
||||
|
@ -596,30 +610,30 @@ class cocos2d::CCMotionStreak {
|
|||
virtual auto draw();
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMouseDispatcher {
|
||||
bool dispatchScrollMSG(float x, float y) = mac 0x2e8f40;
|
||||
void removeDelegate(cocos2d::CCMouseDelegate* delegate);
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMouseHandler {
|
||||
static cocos2d::CCMouseHandler* handlerWithDelegate(cocos2d::CCMouseDelegate*) = mac 0x12ef80;
|
||||
virtual auto initWithDelegate(cocos2d::CCMouseDelegate*) = mac 0x12ef40, ios 0x43798;
|
||||
~CCMouseHandler() = mac 0x12ede0, ios 0x4375c, win 0xb1fd0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMoveBy {
|
||||
static cocos2d::CCMoveBy* create(float, cocos2d::CCPoint const&) = mac 0x1f50e0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCMoveTo {
|
||||
static cocos2d::CCMoveTo* create(float, cocos2d::CCPoint const&) = mac 0x1f54d0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCNode {
|
||||
CCNode() = mac 0x122550, win 0x5e7d0;
|
||||
auto boundingBox() = mac 0x123030;
|
||||
|
@ -631,6 +645,12 @@ class cocos2d::CCNode {
|
|||
virtual auto cleanup() = mac 0x123100, ios 0x15e3a4;
|
||||
auto convertToNodeSpace(cocos2d::CCPoint const&) = mac 0x124750, ios 0x15f55c;
|
||||
auto convertToWorldSpace(cocos2d::CCPoint const&) = mac 0x124790;
|
||||
cocos2d::CCPoint convertToNodeSpaceAR(cocos2d::CCPoint const& worldPoint) {
|
||||
return convertToNodeSpace(worldPoint) - getAnchorPointInPoints();
|
||||
}
|
||||
cocos2d::CCPoint convertToWorldSpaceAR(cocos2d::CCPoint const& nodePoint) {
|
||||
return convertToWorldSpace(nodePoint + getAnchorPointInPoints());
|
||||
}
|
||||
static cocos2d::CCNode* create() = mac 0x1230a0;
|
||||
virtual auto draw() = mac 0x123840, ios 0x15e974;
|
||||
auto getActionByTag(int) = mac 0x123ee0;
|
||||
|
@ -739,7 +759,7 @@ class cocos2d::CCNode {
|
|||
virtual ~CCNode() = mac 0x122750, ios 0x6c98, win 0x5ea40;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCNodeRGBA {
|
||||
CCNodeRGBA() = mac 0x124b30, win 0x5e9d0;
|
||||
virtual ~CCNodeRGBA() = mac 0x124bb0, ios 0x15f748, win 0x5ebb0;
|
||||
|
@ -758,7 +778,7 @@ class cocos2d::CCNodeRGBA {
|
|||
virtual auto setCascadeColorEnabled(bool) = mac 0x125340, ios 0x15fb80;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCObject {
|
||||
CCObject() = mac 0x250ca0, ios 0x43864, win 0x69230;
|
||||
auto acceptVisitor(cocos2d::CCDataVisitor&) = mac 0x250f30, ios 0x439f0;
|
||||
|
@ -774,7 +794,7 @@ class cocos2d::CCObject {
|
|||
~CCObject() = mac 0x250d20, ios 0x6ac0, win 0x69270;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCParticleSystem {
|
||||
CCParticleSystem() = win 0xb6650;
|
||||
virtual ~CCParticleSystem() = win 0xb68e0;
|
||||
|
@ -785,7 +805,7 @@ class cocos2d::CCParticleSystem {
|
|||
auto stopSystem() = mac 0x46bd10;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCParticleSystemQuad {
|
||||
CCParticleSystemQuad() = win 0xb9bd0;
|
||||
virtual ~CCParticleSystemQuad() = win 0xb9c10;
|
||||
|
@ -794,18 +814,18 @@ class cocos2d::CCParticleSystemQuad {
|
|||
auto setupVBO();
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCPoolManager {
|
||||
auto pop() = mac 0x214620;
|
||||
static cocos2d::CCPoolManager* sharedPoolManager() = mac 0x2142c0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCRemoveSelf {
|
||||
static cocos2d::CCRemoveSelf* create(bool) = mac 0x454700;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCRenderTexture {
|
||||
auto begin() = mac 0x35ce10;
|
||||
auto end() = mac 0x35d2c0;
|
||||
|
@ -815,45 +835,45 @@ class cocos2d::CCRenderTexture {
|
|||
auto beginWithClear(float r, float g, float b, float a) = mac 0x35d010;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCRepeat {
|
||||
static cocos2d::CCRepeat* create(cocos2d::CCFiniteTimeAction*, unsigned int) = mac 0x1f3230;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCRepeatForever {
|
||||
static cocos2d::CCRepeatForever* create(cocos2d::CCActionInterval*) = mac 0x1f3920;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCRotateBy {
|
||||
static cocos2d::CCRotateBy* create(float, float) = mac 0x1f4c50;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCRotateTo {
|
||||
static cocos2d::CCRotateTo* create(float, float) = mac 0x1f47b0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCScaleTo {
|
||||
static cocos2d::CCScaleTo* create(float, float) = mac 0x1f6ff0;
|
||||
static cocos2d::CCScaleTo* create(float, float, float) = mac 0x1f70f0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCScaleBy {
|
||||
static cocos2d::CCScaleTo* create(float, float) = mac 0x1f73c0;
|
||||
static cocos2d::CCScaleTo* create(float, float, float) = mac 0x1f7480;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCScene {
|
||||
static cocos2d::CCScene* create() = mac 0x13c140, ios 0x163070;
|
||||
auto getHighestChildZ() = mac 0x13c200, ios 0x1630e4;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCScheduler {
|
||||
auto scheduleSelector(cocos2d::SEL_SCHEDULE, cocos2d::CCObject*, float, unsigned int, float, bool) = mac 0x242b20;
|
||||
void scheduleSelector(cocos2d::SEL_SCHEDULE selector, cocos2d::CCObject* target, float interval, bool paused) {
|
||||
|
@ -868,12 +888,12 @@ class cocos2d::CCScheduler {
|
|||
virtual void update(float delta) = mac 0x2446d0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCSequence {
|
||||
static auto createWithVariableList(cocos2d::CCFiniteTimeAction*, va_list) = mac 0x1f2910;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCSet {
|
||||
CCSet() = mac 0x45ad80, ios 0x10e870, win 0x699e0;
|
||||
static auto create() = mac 0x45b0b0;
|
||||
|
@ -888,14 +908,14 @@ class cocos2d::CCSet {
|
|||
}
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCShaderCache {
|
||||
static auto sharedShaderCache() = mac 0xe6d10;
|
||||
auto programForKey(const char*) = mac 0xe7d40;
|
||||
void reloadDefaultShaders();
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCSprite {
|
||||
virtual ~CCSprite() = mac 0x133430, ios 0x15b92c, win 0xd2f90;
|
||||
virtual auto init() = mac 0x132ef0, ios 0x15b488;
|
||||
|
@ -970,7 +990,7 @@ class cocos2d::CCSprite {
|
|||
void setFlipY(bool) = mac 0x134c30;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCSpriteBatchNode {
|
||||
static cocos2d::CCSpriteBatchNode* create(char const*, unsigned int) = mac 0xbb540;
|
||||
static auto createWithTexture(cocos2d::CCTexture2D*, unsigned int) = mac 0xbb310;
|
||||
|
@ -995,28 +1015,28 @@ class cocos2d::CCSpriteBatchNode {
|
|||
virtual auto getBlendFunc() = mac 0xbcd50, ios 0x131a60;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCSpriteFrame {
|
||||
static auto createWithTexture(cocos2d::CCTexture2D*, cocos2d::CCRect const&, bool, cocos2d::CCPoint const&, cocos2d::CCSize const&) = mac 0x1ac7f0;
|
||||
static auto createWithTexture(cocos2d::CCTexture2D*, cocos2d::CCRect const&) = mac 0x1ac5c0;
|
||||
auto getTexture() = mac 0x1ad250;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCSpriteFrameCache {
|
||||
auto addSpriteFramesWithFile(char const*) = mac 0x199a10, ios 0x29e818;
|
||||
static cocos2d::CCSpriteFrameCache* sharedSpriteFrameCache() = mac 0x198970, ios 0x29dc4c;
|
||||
auto spriteFrameByName(char const*) = mac 0x19a7e0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCStandardTouchHandler {
|
||||
static cocos2d::CCStandardTouchHandler* handlerWithDelegate(cocos2d::CCTouchDelegate*, int) = mac 0x247f30;
|
||||
virtual auto initWithDelegate(cocos2d::CCTouchDelegate*, int) = mac 0x247ed0, ios 0x69; // iOS stub
|
||||
~CCStandardTouchHandler() = mac 0x2482a0, ios 0x6d28, win 0xf5a40;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCString {
|
||||
// virtual ~CCString() = mac 0x44c590;
|
||||
virtual auto isEqual(cocos2d::CCObject const*) = mac 0x44c8f0, ios 0x1a1e6c;
|
||||
|
@ -1032,7 +1052,7 @@ class cocos2d::CCString {
|
|||
auto intValue() const = mac 0x44c780, ios 0x1a1ca8;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTargetedTouchHandler {
|
||||
static cocos2d::CCTargetedTouchHandler* handlerWithDelegate(cocos2d::CCTouchDelegate*, int, bool) = mac 0x248010;
|
||||
auto initWithDelegate(cocos2d::CCTouchDelegate*, int, bool) = mac 0x2480f0, ios 0x69; // iOS stub
|
||||
|
@ -1040,7 +1060,7 @@ class cocos2d::CCTargetedTouchHandler {
|
|||
}
|
||||
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTexture2D {
|
||||
CCTexture2D() = mac 0x246280, win 0xe9300;
|
||||
~CCTexture2D() = mac 0x246350, win 0xe93f0;
|
||||
|
@ -1065,7 +1085,7 @@ class cocos2d::CCTexture2D {
|
|||
auto setTexParameters(cocos2d::_ccTexParams*) = mac 0x247980;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTextureAtlas {
|
||||
CCTextureAtlas() = win 0xea680;
|
||||
virtual ~CCTextureAtlas() = win 0xea6c0;
|
||||
|
@ -1073,12 +1093,12 @@ class cocos2d::CCTextureAtlas {
|
|||
auto mapBuffers();
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTextFieldTTF {
|
||||
static auto textFieldWithPlaceHolder(char const*, char const*, float) = mac 0x126220;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTextureCache {
|
||||
auto addImage(char const*, bool) = mac 0x358120, ios 0xa8388;
|
||||
auto textureForKey(char const*) = mac 0x359050;
|
||||
|
@ -1086,17 +1106,17 @@ class cocos2d::CCTextureCache {
|
|||
static cocos2d::CCTextureCache* sharedTextureCache() = mac 0x356e00, ios 0xa81ec;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTime {
|
||||
static auto gettimeofdayCocos2d(cocos2d::cc_timeval*, void*) = mac 0x19eac0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTintTo {
|
||||
static cocos2d::CCTintTo* create(float, unsigned char, unsigned char, unsigned char) = mac 0x1f82a0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTouch {
|
||||
auto getDelta() const = mac 0x38340;
|
||||
auto getLocationInView() const = mac 0x38250;
|
||||
|
@ -1106,7 +1126,7 @@ class cocos2d::CCTouch {
|
|||
auto getStartLocation() const = mac 0x38310;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTouchDispatcher {
|
||||
auto addTargetedDelegate(cocos2d::CCTouchDelegate*, int, bool) = mac 0x281180;
|
||||
auto addStandardDelegate(cocos2d::CCTouchDelegate*, int) = mac 0x281060;
|
||||
|
@ -1116,7 +1136,7 @@ class cocos2d::CCTouchDispatcher {
|
|||
void touches(cocos2d::CCSet*, cocos2d::CCEvent*, unsigned int) = mac 0x281a60;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTouchHandler {
|
||||
virtual auto initWithDelegate(cocos2d::CCTouchDelegate*, int) = mac 0x247d10, ios 0x69f8;
|
||||
auto getPriority() = mac 0x247c20;
|
||||
|
@ -1126,19 +1146,21 @@ class cocos2d::CCTouchHandler {
|
|||
~CCTouchHandler() = mac 0x247de0, ios 0x6ac0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::CCTransitionFade {
|
||||
static cocos2d::CCTransitionFade* create(float, cocos2d::CCScene*) = mac 0x8ea30, ios 0x12c244;
|
||||
virtual bool initWithDuration(float t, cocos2d::CCScene* scene, cocos2d::ccColor3B const& color) = mac 0x8e930;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::ZipUtils {
|
||||
static auto compressString(gd::string, bool, int) = mac 0xe9a50;
|
||||
static auto decompressString(gd::string, bool, int) = mac 0xea380;
|
||||
static auto decompressString2(unsigned char* data, bool decrypt, int size, int decryptionKey);
|
||||
static int ccDeflateMemory(unsigned char*, unsigned int, unsigned char**) = mac 0xe9cf0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::extension::CCControl {
|
||||
CCControl() {}
|
||||
virtual bool init() = mac 0x1a71c0;
|
||||
|
@ -1162,27 +1184,34 @@ class cocos2d::extension::CCControl {
|
|||
auto isSelected() = mac 0x1a7ec0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
|
||||
class cocos2d::extension::CCControlColourPicker {
|
||||
CCControlColourPicker() {}
|
||||
[[link(win)]]
|
||||
CCControlColourPicker() {}
|
||||
[[link(win)]]
|
||||
~CCControlColourPicker() = mac 0x1aae30;
|
||||
auto setColorValue(cocos2d::_ccColor3B const&) = mac 0x1aac10;
|
||||
[[link(win, android)]]
|
||||
auto setColorValue(cocos2d::_ccColor3B const&) = mac 0x1aac10;
|
||||
[[link(win)]]
|
||||
auto ccTouchBegan(cocos2d::CCTouch*, cocos2d::CCEvent*) = mac 0x1aae10;
|
||||
[[link(win)]]
|
||||
auto init() = mac 0x1aa400;
|
||||
[[link(win)]]
|
||||
static auto colourPicker() = mac 0x1aaa30;
|
||||
[[link(win)]]
|
||||
cocos2d::ccColor3B const& getColorValue() const {
|
||||
return m_rgb;
|
||||
return m_rgb;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::extension::CCControlUtils {
|
||||
static cocos2d::extension::HSV HSVfromRGB(cocos2d::extension::RGBA) = mac 0x1e6750;
|
||||
static cocos2d::extension::RGBA RGBfromHSV(cocos2d::extension::HSV) = mac 0x1e6850;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::extension::CCScale9Sprite {
|
||||
CCScale9Sprite() = mac 0x211330;
|
||||
static cocos2d::extension::CCScale9Sprite* create(char const*) = mac 0x2130d0;
|
||||
|
@ -1228,10 +1257,11 @@ class cocos2d::extension::CCScale9Sprite {
|
|||
virtual auto setSpriteFrame(cocos2d::CCSpriteFrame*) = mac 0x213a90;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d::extension::CCScrollView {
|
||||
CCScrollView() = mac 0x214800;
|
||||
virtual ~CCScrollView() = mac 0x214c30;
|
||||
static auto create(cocos2d::CCSize, cocos2d::CCNode*) = mac 0x214cd0;
|
||||
virtual auto init() = mac 0x214fb0;
|
||||
virtual auto setContentSize(cocos2d::CCSize const&) = mac 0x215eb0;
|
||||
virtual auto getContentSize() const = mac 0x215e90;
|
||||
|
@ -1247,7 +1277,7 @@ class cocos2d::extension::CCScrollView {
|
|||
virtual auto setTouchEnabled(bool) = mac 0x215250;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class cocos2d {
|
||||
static auto FNTConfigLoadFile(char const*) = mac 0x344f10;
|
||||
static auto ccGLUseProgram(GLuint) = mac 0x1ae540;
|
||||
|
@ -1255,7 +1285,9 @@ class cocos2d {
|
|||
static auto ccDrawSolidRect(cocos2d::CCPoint, cocos2d::CCPoint, cocos2d::_ccColor4F) = mac 0xecf00;
|
||||
static auto ccGLEnableVertexAttribs(unsigned int) = mac 0x1ae740;
|
||||
static auto ccGLBindTexture2D(GLuint) = mac 0x1ae610;
|
||||
static auto ccGLBindTexture2DN(GLuint, GLuint) = mac 0x1ae650;
|
||||
static float ccpDistance(cocos2d::CCPoint const&, cocos2d::CCPoint const&) = mac 0x1aaf90;
|
||||
static auto ccDrawLine(cocos2d::CCPoint const&, cocos2d::CCPoint const&) = mac 0xeccc0;
|
||||
static void ccDrawPoly(cocos2d::CCPoint const*, unsigned int, bool) = mac 0xed0a0;
|
||||
static void ccDrawColor4B(GLubyte, GLubyte, GLubyte, GLubyte) = mac 0xeddd0;
|
||||
static void CCMessageBox(const char* msg, const char* title) = mac 0xbabc0;
|
||||
|
@ -1274,7 +1306,7 @@ class DS_Dictionary {
|
|||
auto getObjectForKey(char const*) = mac 0xC4BB0;
|
||||
}
|
||||
|
||||
[[link(win)]]
|
||||
[[link(win, android)]]
|
||||
class pugi::xml_document {
|
||||
xml_document() = mac 0x393a80;
|
||||
~xml_document() = mac 0x393b50;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
set(GEODE_CLI_MINIMUM_VERSION 1.0.5)
|
||||
|
||||
# Find Geode CLI
|
||||
if (NOT DEFINED GEODE_CLI)
|
||||
if (NOT DEFINED GEODE_CLI OR GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
|
||||
find_program(GEODE_CLI NAMES geode.exe geode-cli.exe geode geode-cli PATHS ${CLI_PATH})
|
||||
endif()
|
||||
|
||||
|
@ -51,6 +51,17 @@ function(setup_geode_mod proname)
|
|||
# Link Geode to the mod
|
||||
target_link_libraries(${proname} geode-sdk)
|
||||
|
||||
if (ANDROID)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
add_custom_command(
|
||||
TARGET "${PROJECT_NAME}" POST_BUILD
|
||||
DEPENDS "${PROJECT_NAME}"
|
||||
COMMAND $<$<CONFIG:release>:${CMAKE_STRIP}>
|
||||
ARGS -S $<TARGET_FILE:${PROJECT_NAME}>
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (GEODE_DISABLE_CLI_CALLS)
|
||||
message("Skipping setting up geode mod ${proname}")
|
||||
return()
|
||||
|
@ -59,7 +70,7 @@ function(setup_geode_mod proname)
|
|||
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
|
||||
message(FATAL_ERROR
|
||||
"setup_geode_mod called, but Geode CLI was not found - "
|
||||
"Please install CLI: https://docs.geode-sdk.org/info/installcli/"
|
||||
"Please install CLI: https://docs.geode-sdk.org/"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
@ -130,9 +141,8 @@ function(setup_geode_mod proname)
|
|||
set(HAS_HEADERS Off)
|
||||
endif()
|
||||
|
||||
# todo: figure out how to either not make cmake shit itself and print out --binary path/to/dll "" or
|
||||
# make cli not shit itself when it sees that
|
||||
if (HAS_HEADERS)
|
||||
if (HAS_HEADERS AND WIN32)
|
||||
# this adds the .lib file on windows, which is needed for linking with the headers
|
||||
add_custom_target(${proname}_PACKAGE ALL
|
||||
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
|
||||
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
|
@ -188,6 +198,9 @@ function(setup_geode_mod proname)
|
|||
elseif (APPLE)
|
||||
file(GLOB libs ${dir}/*.dylib)
|
||||
list(APPEND libs_to_link ${libs})
|
||||
elseif (ANDROID)
|
||||
file(GLOB libs ${dir}/*.so)
|
||||
list(APPEND libs_to_link ${libs})
|
||||
else()
|
||||
message(FATAL_ERROR "Library extension not defined on this platform")
|
||||
endif()
|
||||
|
@ -294,9 +307,10 @@ function(package_geode_resources_now proname src dest header_dest)
|
|||
# "LOADER_RESOURCE_FILES {\n"
|
||||
)
|
||||
|
||||
list(APPEND HASHED_EXTENSIONS ".png")
|
||||
list(APPEND HASHED_EXTENSIONS ".mp3")
|
||||
list(APPEND HASHED_EXTENSIONS ".ogg")
|
||||
# yeah don't think we need to check too many stuff
|
||||
# list(APPEND HASHED_EXTENSIONS ".png")
|
||||
# list(APPEND HASHED_EXTENSIONS ".mp3")
|
||||
# list(APPEND HASHED_EXTENSIONS ".ogg")
|
||||
list(APPEND HASHED_EXTENSIONS ".md")
|
||||
|
||||
foreach(file ${RESOURCE_FILES})
|
||||
|
@ -307,6 +321,8 @@ function(package_geode_resources_now proname src dest header_dest)
|
|||
if (NOT FILE_NAME STREQUAL ".geode_cache" AND NOT FILE_SHOULD_HASH EQUAL -1)
|
||||
|
||||
file(SHA256 ${file} COMPUTED_HASH)
|
||||
file(SIZE ${file} FILE_SIZE)
|
||||
message(STATUS "Hashed ${file} to ${COMPUTED_HASH} (${FILE_SIZE} bytes)")
|
||||
list(APPEND HEADER_FILE "\t{ \"${FILE_NAME}\", \"${COMPUTED_HASH}\" },\n")
|
||||
|
||||
# list(APPEND HEADER_FILE "\t\"${FILE_NAME}\",\n")
|
||||
|
|
|
@ -51,6 +51,10 @@ elseif (GEODE_TARGET_PLATFORM STREQUAL "MacOS")
|
|||
${GEODE_LOADER_PATH}/include/link/libfmod.dylib
|
||||
)
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} INTERFACE
|
||||
-DCommentType=CommentTypeDummy
|
||||
)
|
||||
|
||||
set(GEODE_PLATFORM_BINARY "Geode.dylib")
|
||||
|
||||
elseif (GEODE_TARGET_PLATFORM STREQUAL "Win32")
|
||||
|
@ -78,10 +82,10 @@ elseif (GEODE_TARGET_PLATFORM STREQUAL "Android")
|
|||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE
|
||||
${GEODE_LOADER_PATH}/include/link/android/libcocos2dcpp.so
|
||||
${GEODE_LOADER_PATH}/include/link/android/libcurl.a
|
||||
${GEODE_LOADER_PATH}/include/link/android/libssl.a
|
||||
${GEODE_LOADER_PATH}/include/link/android/libcrypto.a
|
||||
${GEODE_LOADER_PATH}/include/link/android/libcocos2dcpp.so
|
||||
log
|
||||
)
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
|
||||
project(Codegen LANGUAGES C CXX)
|
||||
|
||||
include(../cmake/CPM.cmake)
|
||||
|
||||
CPMAddPackage("gh:fmtlib/fmt#9.1.0")
|
||||
CPMAddPackage("gh:geode-sdk/Broma#460f82d")
|
||||
CPMAddPackage("gh:geode-sdk/Broma#38a3bba")
|
||||
|
||||
file(GLOB SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp
|
||||
|
|
|
@ -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(&{}::{}))",
|
||||
|
|
155
codegen/src/AndroidSymbol.hpp
Normal file
155
codegen/src/AndroidSymbol.hpp
Normal 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;
|
||||
}
|
|
@ -131,6 +131,8 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
|
|||
single_output += format_strings::class_includes;
|
||||
|
||||
for (auto& f : root.functions) {
|
||||
if (codegen::getStatus(f) == BindStatus::Missing) continue;
|
||||
|
||||
FunctionProto const* fb = &f.prototype;
|
||||
char const* used_format = format_strings::function_definition;
|
||||
|
||||
|
@ -185,7 +187,8 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
|
|||
|
||||
single_output += fmt::format(::format_strings::class_start,
|
||||
fmt::arg("class_name", cls.name),
|
||||
fmt::arg("base_classes", supers)
|
||||
fmt::arg("base_classes", supers)//,
|
||||
// fmt::arg("hidden", str_if("GEODE_HIDDEN ", (codegen::platform & (Platform::Mac | Platform::iOS)) != Platform::None))
|
||||
);
|
||||
|
||||
// what.
|
||||
|
@ -201,6 +204,8 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
|
|||
|
||||
bool unimplementedField = false;
|
||||
for (auto field : cls.fields) {
|
||||
if (codegen::getStatus(field) == BindStatus::Missing) continue;
|
||||
|
||||
MemberFunctionProto* fb;
|
||||
char const* used_format = format_strings::function_definition;
|
||||
|
||||
|
@ -220,9 +225,13 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
|
|||
} else if (auto p = field.get_as<PadField>()) {
|
||||
auto hardcode = codegen::platformNumber(p->amount);
|
||||
|
||||
if (hardcode) {
|
||||
if (hardcode > 0) {
|
||||
single_output += fmt::format(format_strings::pad_definition, fmt::arg("hardcode", hardcode));
|
||||
} else {
|
||||
}
|
||||
else if (hardcode == 0) {
|
||||
single_output += " // no padding\n";
|
||||
}
|
||||
else {
|
||||
unimplementedField = true;
|
||||
}
|
||||
continue;
|
||||
|
@ -233,7 +242,7 @@ std::string generateBindingHeader(Root const& root, ghc::filesystem::path const&
|
|||
} else if (auto fn = field.get_as<FunctionBindField>()) {
|
||||
fb = &fn->prototype;
|
||||
|
||||
if (!codegen::platformNumber(fn->binds)) {
|
||||
if (codegen::platformNumber(fn->binds) == -1 && codegen::getStatus(field) != BindStatus::Binded) {
|
||||
used_format = format_strings::error_definition;
|
||||
|
||||
if (fb->type != FunctionType::Normal)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -16,5 +16,5 @@ namespace geode {
|
|||
|
||||
namespace {
|
||||
// to make sure the instance is set into the sharedMod<> in load time
|
||||
static auto mod = geode::getMod();
|
||||
static auto mod = geode::getMod();
|
||||
}
|
||||
|
|
|
@ -27,10 +27,13 @@ execute_process(
|
|||
)
|
||||
|
||||
# Package info file for internal representation
|
||||
set(GEODE_RESOURCES_PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources)
|
||||
configure_file(resources/mod.json.in ${CMAKE_CURRENT_SOURCE_DIR}/resources/mod.json)
|
||||
file(READ resources/mod.json LOADER_MOD_JSON)
|
||||
configure_file(${GEODE_ROOT_PATH}/VERSION ${CMAKE_CURRENT_SOURCE_DIR}/resources/version COPYONLY)
|
||||
configure_file(${GEODE_ROOT_PATH}/CHANGELOG.md ${CMAKE_CURRENT_SOURCE_DIR}/resources/changelog.md COPYONLY)
|
||||
configure_file(${GEODE_ROOT_PATH}/VERSION ${GEODE_RESOURCES_PATH}/version COPYONLY)
|
||||
configure_file(${GEODE_RESOURCES_PATH}/about.md.in ${GEODE_RESOURCES_PATH}/about.md NEWLINE_STYLE LF)
|
||||
configure_file(${GEODE_ROOT_PATH}/CHANGELOG.md ${GEODE_RESOURCES_PATH}/changelog.md NEWLINE_STYLE LF)
|
||||
configure_file(${GEODE_RESOURCES_PATH}/support.md.in ${GEODE_RESOURCES_PATH}/support.md NEWLINE_STYLE LF)
|
||||
configure_file(src/internal/about.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/src/internal/about.hpp)
|
||||
|
||||
# Source files
|
||||
|
@ -146,6 +149,11 @@ if (APPLE)
|
|||
file(COPY ${GEODE_LOADER_PATH}/include/link/libfmod.dylib DESTINATION ${GEODE_BIN_PATH}/nightly)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
# needed to define some opengl functions
|
||||
target_link_libraries(${PROJECT_NAME} EGL)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
src/
|
||||
src/loader/
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "DefaultInclude.hpp"
|
||||
|
||||
// thanks pie
|
||||
enum class SearchType {
|
||||
Search = 0,
|
||||
|
@ -54,6 +56,7 @@ enum class GameObjectType {
|
|||
MiniSizePortal = 18,
|
||||
UfoPortal = 19,
|
||||
Modifier = 20,
|
||||
Breakable = 21,
|
||||
SecretCoin = 22,
|
||||
DualPortal = 23,
|
||||
SoloPortal = 24,
|
||||
|
@ -109,6 +112,16 @@ enum class CommentError {
|
|||
enum class BackupAccountError {
|
||||
};
|
||||
|
||||
// Thanks cocoa!
|
||||
#ifdef GEODE_IS_MACOS
|
||||
#undef CommentType
|
||||
#endif
|
||||
|
||||
enum class CommentType {
|
||||
Level = 0,
|
||||
Account = 1,
|
||||
};
|
||||
|
||||
enum class BoomListType {
|
||||
Default = 0x0,
|
||||
User = 0x2,
|
||||
|
@ -295,7 +308,9 @@ enum class GJRewardType
|
|||
{
|
||||
Unknown = 0x0,
|
||||
Small = 0x1,
|
||||
Large = 0x2
|
||||
Large = 0x2,
|
||||
SmallTreasure = 0x3,
|
||||
LargeTreasure = 0x4
|
||||
};
|
||||
|
||||
enum class IconType {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
13
loader/include/Geode/cocos/base_nodes/CCNode.h
vendored
13
loader/include/Geode/cocos/base_nodes/CCNode.h
vendored
|
@ -700,12 +700,6 @@ public:
|
|||
* @param cleanup true if all running actions and callbacks on the child node will be cleanup, false otherwise.
|
||||
*/
|
||||
virtual void removeChildByTag(int tag, bool cleanup);
|
||||
/**
|
||||
* Removes a child from the container by its ID.
|
||||
* @param id The ID of the node
|
||||
* @note Geode addition
|
||||
*/
|
||||
void removeChildByID(std::string const& id);
|
||||
/**
|
||||
* Removes all children from the container with a cleanup.
|
||||
*
|
||||
|
@ -890,6 +884,13 @@ public:
|
|||
*/
|
||||
GEODE_DLL CCNode* getChildByIDRecursive(std::string const& id);
|
||||
|
||||
/**
|
||||
* Removes a child from the container by its ID.
|
||||
* @param id The ID of the node
|
||||
* @note Geode addition
|
||||
*/
|
||||
GEODE_DLL void removeChildByID(std::string const& id);
|
||||
|
||||
/**
|
||||
* Add a child before a specified existing child
|
||||
* @param child The node to add. The node may not be a child of another
|
||||
|
|
|
@ -90,6 +90,7 @@ protected:
|
|||
bool m_bIsSendCleanupToScene;
|
||||
|
||||
public:
|
||||
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCTransitionScene, CCScene)
|
||||
/**
|
||||
* @js ctor
|
||||
*/
|
||||
|
@ -147,6 +148,7 @@ public:
|
|||
* @js ctor
|
||||
*/
|
||||
CCTransitionSceneOriented();
|
||||
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCTransitionSceneOriented, CCTransitionScene)
|
||||
/**
|
||||
* @js NA
|
||||
* @lua NA
|
||||
|
@ -171,6 +173,7 @@ public:
|
|||
* @js ctor
|
||||
*/
|
||||
CCTransitionRotoZoom();
|
||||
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCTransitionRotoZoom, CCTransitionScene)
|
||||
/**
|
||||
* @js NA
|
||||
* @lua NA
|
||||
|
@ -623,6 +626,7 @@ public:
|
|||
* @js ctor
|
||||
*/
|
||||
CCTransitionFade();
|
||||
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCTransitionFade, CCTransitionScene)
|
||||
/**
|
||||
* @js NA
|
||||
* @lua NA
|
||||
|
|
|
@ -34,6 +34,8 @@ class CC_DLL CCEGLView : public CCEGLViewProtocol
|
|||
{
|
||||
GEODE_FRIEND_MODIFY
|
||||
public:
|
||||
GEODE_CUSTOM_CONSTRUCTOR_COCOS(CCEGLView, CCEGLViewProtocol)
|
||||
|
||||
CCEGLView();
|
||||
virtual ~CCEGLView();
|
||||
|
||||
|
|
|
@ -3,12 +3,8 @@
|
|||
|
||||
#include <android/log.h>
|
||||
|
||||
#ifdef GEODE_EXPORTING
|
||||
#define CC_DLL __attribute__((visibility("default")))
|
||||
#else
|
||||
#define CC_DLL
|
||||
#endif
|
||||
#define ACTUAL_CC_DLL CC_DLL
|
||||
#define CC_DLL
|
||||
#define ACTUAL_CC_DLL
|
||||
|
||||
#define CC_NO_MESSAGE_PSEUDOASSERT(cond) \
|
||||
if (!(cond)) { \
|
||||
|
|
|
@ -3,12 +3,8 @@
|
|||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef GEODE_EXPORTING
|
||||
#define CC_DLL __attribute__((visibility("default")))
|
||||
#else
|
||||
#define CC_DLL
|
||||
#endif
|
||||
#define ACTUAL_CC_DLL CC_DLL
|
||||
#define CC_DLL //__attribute__((visibility("hidden")))
|
||||
#define ACTUAL_CC_DLL
|
||||
|
||||
#define CC_ASSERT(cond) assert(cond)
|
||||
|
||||
|
|
|
@ -3,12 +3,8 @@
|
|||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef GEODE_EXPORTING
|
||||
#define CC_DLL __attribute__((visibility("default")))
|
||||
#else
|
||||
#define CC_DLL
|
||||
#endif
|
||||
#define ACTUAL_CC_DLL CC_DLL
|
||||
#define CC_DLL //__attribute__((visibility("hidden")))
|
||||
#define ACTUAL_CC_DLL
|
||||
|
||||
|
||||
#if CC_DISABLE_ASSERT > 0
|
||||
|
|
2
loader/include/Geode/fmod/fmod.h
vendored
2
loader/include/Geode/fmod/fmod.h
vendored
|
@ -31,7 +31,7 @@ FMOD_RESULT F_API FMOD_File_GetDiskBusy (int *busy);
|
|||
/*
|
||||
FMOD System factory functions. Use this to create an FMOD System Instance. below you will see FMOD_System_Init/Close to get started.
|
||||
*/
|
||||
FMOD_RESULT F_API FMOD_System_Create (FMOD_SYSTEM **system);
|
||||
FMOD_RESULT /*F_API*/ FMOD_System_Create (FMOD_SYSTEM **system);
|
||||
FMOD_RESULT F_API FMOD_System_Release (FMOD_SYSTEM *system);
|
||||
|
||||
/*
|
||||
|
|
|
@ -31,7 +31,8 @@ namespace geode {
|
|||
LoadFailed,
|
||||
EnableFailed,
|
||||
MissingDependency,
|
||||
PresentIncompatibility
|
||||
PresentIncompatibility,
|
||||
UnzipFailed
|
||||
};
|
||||
Type type;
|
||||
std::variant<ghc::filesystem::path, ModMetadata, Mod*> cause;
|
||||
|
@ -57,6 +58,7 @@ namespace geode {
|
|||
|
||||
public:
|
||||
// TODO: do we want to expose all of these functions?
|
||||
|
||||
static Loader* get();
|
||||
|
||||
enum class LoadingState : uint8_t {
|
||||
|
@ -70,6 +72,7 @@ namespace geode {
|
|||
Done
|
||||
};
|
||||
|
||||
// TODO: return void
|
||||
Result<> saveData();
|
||||
Result<> loadData();
|
||||
|
||||
|
|
|
@ -96,6 +96,11 @@ namespace geode {
|
|||
*/
|
||||
std::optional<std::string> match;
|
||||
|
||||
/**
|
||||
* The CCTextInputNode's allowed character filter
|
||||
*/
|
||||
std::optional<std::string> filter;
|
||||
|
||||
static Result<StringSetting> parse(JsonMaybeObject& obj);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
namespace geode::modifier {
|
||||
template <uint32_t Id>
|
||||
uintptr_t address();
|
||||
GEODE_HIDDEN uintptr_t address();
|
||||
|
||||
Result<tulip::hook::HandlerMetadata> handlerMetadataForAddress(uintptr_t address);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "../utils/MiniFunction.hpp"
|
||||
#include "Traits.hpp"
|
||||
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include "../utils/MiniFunction.hpp"
|
||||
#include <cocos2d.h>
|
||||
#include <vector>
|
||||
|
||||
|
@ -56,7 +56,7 @@ namespace geode::modifier {
|
|||
using Intermediate = Modify<Parent, Base>;
|
||||
// Padding used for guaranteeing any member of parents
|
||||
// will be in between sizeof(Intermediate) and sizeof(Parent)
|
||||
uintptr_t m_padding;
|
||||
alignas(std::max(alignof(Base), alignof(uintptr_t))) uintptr_t m_padding;
|
||||
|
||||
public:
|
||||
// the constructor that constructs the fields.
|
||||
|
@ -118,7 +118,7 @@ namespace geode::modifier {
|
|||
reinterpret_cast<std::byte*>(offsetField) - sizeof(Intermediate)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Parent* self() {
|
||||
return this->operator Parent*();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace geode {
|
|||
class GEODE_DLL SceneManager {
|
||||
protected:
|
||||
cocos2d::CCArray* m_persistedNodes;
|
||||
cocos2d::CCScene* m_lastScene = nullptr;
|
||||
|
||||
bool setup();
|
||||
|
||||
|
|
77
loader/include/Geode/ui/TextArea.hpp
Normal file
77
loader/include/Geode/ui/TextArea.hpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include <cocos2d.h>
|
||||
|
||||
namespace geode {
|
||||
enum WrappingMode {
|
||||
NO_WRAP,
|
||||
WORD_WRAP,
|
||||
CUTOFF_WRAP
|
||||
};
|
||||
|
||||
/**
|
||||
* A class which provides a textarea with proper alignment and some extra features like:
|
||||
*
|
||||
* - Max lines
|
||||
* - Changing all aspects after creation
|
||||
* - Custom text alignment
|
||||
* - Configurable and automatic word wrapping
|
||||
* - Line padding
|
||||
*
|
||||
* Contact me on Discord (\@smjs) if you have any questions, suggestions or bugs.
|
||||
*/
|
||||
class GEODE_DLL SimpleTextArea : public cocos2d::CCNode {
|
||||
static SimpleTextArea* create(const std::string& text, const std::string& font = "chatFont.fnt", const float scale = 1);
|
||||
static SimpleTextArea* create(const std::string& text, const std::string& font, const float scale, const float width);
|
||||
|
||||
cocos2d::CCMenu* m_container;
|
||||
std::string m_font;
|
||||
std::string m_text;
|
||||
std::vector<cocos2d::CCLabelBMFont*> m_lines;
|
||||
cocos2d::ccColor4B m_color;
|
||||
cocos2d::CCTextAlignment m_alignment;
|
||||
WrappingMode m_wrappingMode;
|
||||
size_t m_maxLines;
|
||||
float m_scale;
|
||||
float m_lineHeight;
|
||||
float m_linePadding;
|
||||
|
||||
void setFont(const std::string& font);
|
||||
std::string getFont();
|
||||
void setColor(const cocos2d::ccColor4B& color);
|
||||
cocos2d::ccColor4B getColor();
|
||||
void setAlignment(const cocos2d::CCTextAlignment alignment);
|
||||
cocos2d::CCTextAlignment getAlignment();
|
||||
void setWrappingMode(const WrappingMode mode);
|
||||
WrappingMode getWrappingMode();
|
||||
void setText(const std::string& text);
|
||||
std::string getText();
|
||||
void setMaxLines(const size_t maxLines);
|
||||
size_t getMaxLines();
|
||||
void setWidth(const float width);
|
||||
float getWidth();
|
||||
void setScale(const float scale) override;
|
||||
float getScale() override;
|
||||
void setLinePadding(const float padding);
|
||||
float getLinePadding();
|
||||
std::vector<cocos2d::CCLabelBMFont*> getLines();
|
||||
float getHeight();
|
||||
float getLineHeight();
|
||||
private:
|
||||
static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth);
|
||||
|
||||
bool m_shouldUpdate;
|
||||
bool m_artificialWidth;
|
||||
|
||||
SimpleTextArea(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth);
|
||||
cocos2d::CCLabelBMFont* createLabel(const std::string& text, const float top);
|
||||
float calculateOffset(cocos2d::CCLabelBMFont* label);
|
||||
void charIteration(const std::function<cocos2d::CCLabelBMFont*(cocos2d::CCLabelBMFont* line, const char c, const float top)>& overflowHandling);
|
||||
void updateLinesNoWrap();
|
||||
void updateLinesWordWrap();
|
||||
void updateLinesCutoffWrap();
|
||||
void updateContainer();
|
||||
virtual void draw() override;
|
||||
};
|
||||
}
|
|
@ -953,7 +953,7 @@ namespace geode::cocos {
|
|||
return m_arr ? m_arr->count() : 0;
|
||||
}
|
||||
|
||||
T operator[](size_t index) {
|
||||
T* operator[](size_t index) {
|
||||
return static_cast<T*>(m_arr->objectAtIndex(index));
|
||||
}
|
||||
|
||||
|
|
|
@ -240,16 +240,31 @@ namespace geode::utils::file {
|
|||
|
||||
/**
|
||||
* Prompt the user to pick a file using the system's file system picker
|
||||
* @note Will not work on Android, use the callback version instead
|
||||
* @param mode Type of file selection prompt to show
|
||||
* @param options Picker options
|
||||
*/
|
||||
GEODE_DLL Result<ghc::filesystem::path> pickFile(PickMode mode, FilePickOptions const& options);
|
||||
|
||||
GEODE_DLL void pickFile(
|
||||
PickMode mode, FilePickOptions const& options,
|
||||
utils::MiniFunction<void(ghc::filesystem::path)> callback,
|
||||
utils::MiniFunction<void()> failed = {}
|
||||
);
|
||||
|
||||
/**
|
||||
* Prompt the user to pick a bunch of files for opening using the system's file system picker
|
||||
* @note Will not work on Android, use the callback version instead
|
||||
* @param options Picker options
|
||||
*/
|
||||
GEODE_DLL Result<std::vector<ghc::filesystem::path>> pickFiles(FilePickOptions const& options);
|
||||
|
||||
GEODE_DLL void pickFiles(
|
||||
FilePickOptions const& options,
|
||||
utils::MiniFunction<void(std::vector<ghc::filesystem::path>)> callback,
|
||||
utils::MiniFunction<void()> failed = {}
|
||||
);
|
||||
|
||||
class GEODE_DLL FileWatchEvent : public Event {
|
||||
protected:
|
||||
ghc::filesystem::path m_path;
|
||||
|
|
|
@ -194,7 +194,7 @@ namespace geode::utils::web {
|
|||
* URL to fetch from the internet asynchronously
|
||||
* @param url URL of the data to download. Redirects will be
|
||||
* automatically followed
|
||||
* @returns Same AsyncWebRequest
|
||||
* @returns An AsyncWebResponse object
|
||||
*/
|
||||
AsyncWebResponse fetch(std::string const& url);
|
||||
/**
|
||||
|
|
29
loader/src/hooks/CCTextInputNodeFix.cpp
Normal file
29
loader/src/hooks/CCTextInputNodeFix.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
// FIXME: This fix ends up breaking some of the vanilla text inputs.
|
||||
#if 0
|
||||
#include <Geode/modify/CCTextInputNode.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
// rob only uses `CCTextInputNode`s in mostly-flat hierarchies, which still
|
||||
// happen to work with the weird vanilla code. this fix makes it work even in
|
||||
// deep hierarchies, because the vanilla code uses `getParent` and manually
|
||||
// calculates the child location in the world space based on that rather than
|
||||
// using `convertToNodeSpace`.
|
||||
|
||||
struct CCTextInputNodeFix : Modify<CCTextInputNodeFix, CCTextInputNode> {
|
||||
bool ccTouchBegan(CCTouch* touch, CCEvent* event) {
|
||||
auto const touchPos = touch->getLocation();
|
||||
auto const size = this->getContentSize();
|
||||
auto const pos = this->convertToNodeSpace(touchPos) + m_textField->getAnchorPoint() * size;
|
||||
|
||||
if (pos.x < 0 || pos.x > size.width || pos.y < 0 || pos.y > size.height)
|
||||
return false;
|
||||
if (m_delegate && !m_delegate->allowTextInput(this))
|
||||
return false;
|
||||
|
||||
this->onClickTrackNode(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#endif
|
|
@ -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
|
||||
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -174,6 +174,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
|||
INDEX_UPDATE_NOTIF = Notification::create(
|
||||
"Updating Index", NotificationIcon::Loading, 0
|
||||
);
|
||||
INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
|
||||
INDEX_UPDATE_NOTIF->show();
|
||||
Index::get()->update();
|
||||
}
|
||||
|
|
78
loader/src/hooks/SaveFileFix.cpp
Normal file
78
loader/src/hooks/SaveFileFix.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#include <Geode/loader/Loader.hpp>
|
||||
|
||||
#if defined(GEODE_IS_WINDOWS) || defined(GEODE_IS_ANDROID)
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
#include <Geode/cocos/support/base64.h>
|
||||
#include "../loader/LoaderImpl.hpp"
|
||||
|
||||
void panic(std::string reason) {
|
||||
LoaderImpl::get()->platformMessageBox("Critical", fmt::format(
|
||||
"Your save file failed to load (reason: {})\n"
|
||||
"As to not lose all of your data, the game will now abort.\n"
|
||||
"Please backup your save files and try opening the game again, it might work.\n"
|
||||
"Please contact the Geode Team about this", reason
|
||||
));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// This function is well known for crashing on certain save files,
|
||||
// causing the game to crash at startup, known as the infamous save file bug.
|
||||
//
|
||||
// Rob ends up relying on strlen for knowing the size of `data`, instead of just using the passed in `size`.
|
||||
// Its a miracle this works most of the time, considering `data` is just binary data
|
||||
//
|
||||
// To fix this, we just rewrite the function.
|
||||
gd::string decompressString2(unsigned char* data, bool decrypt, int size, int decryptionKey) {
|
||||
log::debug("decompressString2 data={} size={}", reinterpret_cast<const void*>(data), size);
|
||||
if (data == nullptr || size == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<unsigned char> copiedData(data, data + size);
|
||||
if (decrypt) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
copiedData[i] ^= decryptionKey;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe not use cocos's base64 and inflateMemory..
|
||||
|
||||
unsigned char* out = nullptr;
|
||||
auto const decodedSize = cocos2d::base64Decode(copiedData.data(), size, &out);
|
||||
std::unique_ptr<unsigned char> b64decoded { out };
|
||||
|
||||
if (decodedSize <= 0) {
|
||||
panic(fmt::format("base64 (size={}) (data={} size={})", decodedSize, reinterpret_cast<const void*>(data), size));
|
||||
return {};
|
||||
}
|
||||
|
||||
out = nullptr;
|
||||
auto const inflatedSize = cocos2d::ZipUtils::ccInflateMemory(b64decoded.get(), decodedSize, &out);
|
||||
|
||||
std::unique_ptr<unsigned char> inflated { out };
|
||||
|
||||
if (inflatedSize <= 0) {
|
||||
panic(fmt::format("inflate (size={}) (data={} size={})", inflatedSize, reinterpret_cast<const void*>(data), size));
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::string(reinterpret_cast<char*>(inflated.get()), inflatedSize);
|
||||
}
|
||||
|
||||
// Modify doesnt want to work for some reason!
|
||||
$execute {
|
||||
(void) Mod::get()->addHook(
|
||||
reinterpret_cast<void*>(
|
||||
geode::addresser::getNonVirtual(
|
||||
&cocos2d::ZipUtils::decompressString2
|
||||
)
|
||||
),
|
||||
&decompressString2,
|
||||
"cocos2d::ZipUtils::decompressString2",
|
||||
tulip::hook::TulipConvention::Cdecl
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
18
loader/src/hooks/TextInputNodeFix.cpp
Normal file
18
loader/src/hooks/TextInputNodeFix.cpp
Normal 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
|
|
@ -6,14 +6,18 @@ using namespace geode::prelude;
|
|||
|
||||
struct SaveLoader : Modify<SaveLoader, AppDelegate> {
|
||||
void trySaveGame() {
|
||||
log::info("Saving...");
|
||||
log::info("Saving mod data...");
|
||||
log::pushNest();
|
||||
|
||||
auto r = Loader::get()->saveData();
|
||||
if (!r) {
|
||||
log::info("{}", r.unwrapErr());
|
||||
}
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
|
||||
log::info("Saved");
|
||||
(void)Loader::get()->saveData();
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
|
||||
log::info("Took {}s", static_cast<float>(time) / 1000.f);
|
||||
|
||||
log::popNest();
|
||||
|
||||
return AppDelegate::trySaveGame();
|
||||
}
|
||||
|
|
|
@ -7,8 +7,15 @@
|
|||
using namespace geode::prelude;
|
||||
|
||||
$register_ids(GJGarageLayer) {
|
||||
setIDSafe(this, 2, "username-label");
|
||||
setIDSafe(this, 6, "player-icon");
|
||||
// the lock does not exist for not logged in users
|
||||
auto loggedInOffset = GJAccountManager::get()->m_accountID == GJAccountManager::get()->m_playerID ? -1 : 0;
|
||||
if (loggedInOffset == -1 && !GameManager::get()->m_clickedName) {
|
||||
// adjusts for the sprite asking for your name
|
||||
loggedInOffset++;
|
||||
}
|
||||
|
||||
setIDSafe<CCTextInputNode>(this, 0, "username-label");
|
||||
setIDSafe<SimplePlayer>(this, 0, "player-icon");
|
||||
|
||||
auto winSize = CCDirector::get()->getWinSize();
|
||||
|
||||
|
@ -39,7 +46,7 @@ $register_ids(GJGarageLayer) {
|
|||
|
||||
setIDs(
|
||||
this,
|
||||
10,
|
||||
10 + loggedInOffset,
|
||||
"cube-selection-menu",
|
||||
"ship-selection-menu",
|
||||
"ball-selection-menu",
|
||||
|
|
|
@ -170,4 +170,4 @@ struct LevelBrowserLayerIDs : Modify<LevelBrowserLayerIDs, LevelBrowserLayer> {
|
|||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -134,4 +134,4 @@ struct LevelInfoLayerIDs : Modify<LevelInfoLayerIDs, LevelInfoLayer> {
|
|||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -58,8 +58,17 @@ $execute {
|
|||
}
|
||||
|
||||
int geodeEntry(void* platformData) {
|
||||
log::Logger::setup();
|
||||
|
||||
log::info("Running {} {}", Mod::get()->getName(), Mod::get()->getVersion());
|
||||
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// set up internal mod, settings and data
|
||||
log::info("Setting up internal mod");
|
||||
log::pushNest();
|
||||
auto internalSetupRes = LoaderImpl::get()->setupInternalMod();
|
||||
log::popNest();
|
||||
if (!internalSetupRes) {
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
"Unable to Load Geode!",
|
||||
|
@ -72,30 +81,42 @@ int geodeEntry(void* platformData) {
|
|||
|
||||
// open console
|
||||
if (Mod::get()->getSettingValue<bool>("show-platform-console")) {
|
||||
log::debug("Opening console");
|
||||
Loader::get()->openPlatformConsole();
|
||||
}
|
||||
|
||||
// set up loader, load mods, etc.
|
||||
log::info("Setting up loader");
|
||||
log::pushNest();
|
||||
auto setupRes = LoaderImpl::get()->setup();
|
||||
log::popNest();
|
||||
if (!setupRes) {
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
"Unable to Load Geode!",
|
||||
"There was an unknown fatal error setting up "
|
||||
"the loader and Geode can not be loaded. "
|
||||
"the loader and Geode can not be loaded. "
|
||||
"(" + setupRes.unwrapErr() + ")"
|
||||
);
|
||||
LoaderImpl::get()->forceReset();
|
||||
return 1;
|
||||
}
|
||||
|
||||
crashlog::setupPlatformHandlerPost();
|
||||
|
||||
log::info("Set up loader");
|
||||
|
||||
// download and install new loader update in the background
|
||||
if (Mod::get()->getSettingValue<bool>("auto-check-updates")) {
|
||||
log::info("Starting loader update check");
|
||||
LoaderImpl::get()->checkForLoaderUpdates();
|
||||
}
|
||||
else {
|
||||
log::info("Skipped loader update check");
|
||||
}
|
||||
|
||||
log::debug("Entry done.");
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
|
||||
log::info("Entry took {}s", static_cast<float>(time) / 1000.f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -44,28 +44,35 @@ tulip::hook::HookMetadata Hook::Impl::getHookMetadata() const {
|
|||
}
|
||||
|
||||
Result<> Hook::Impl::enable() {
|
||||
if (!m_enabled) {
|
||||
if (!LoaderImpl::get()->hasHandler(m_address)) {
|
||||
GEODE_UNWRAP(LoaderImpl::get()->createHandler(m_address, m_handlerMetadata));
|
||||
}
|
||||
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
|
||||
if (m_enabled)
|
||||
return Ok();
|
||||
|
||||
m_handle = tulip::hook::createHook(handler, m_detour, m_hookMetadata);
|
||||
log::debug("Enabling hook at function {} with address {}", m_displayName, m_address);
|
||||
m_enabled = true;
|
||||
if (!LoaderImpl::get()->hasHandler(m_address)) {
|
||||
GEODE_UNWRAP(LoaderImpl::get()->createHandler(m_address, m_handlerMetadata));
|
||||
}
|
||||
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
|
||||
|
||||
m_handle = tulip::hook::createHook(handler, m_detour, m_hookMetadata);
|
||||
if (m_owner)
|
||||
log::debug("Enabled {} hook at {} for {}", m_displayName, m_address, m_owner->getID());
|
||||
else
|
||||
log::debug("Enabled {} hook at {}", m_displayName, m_address);
|
||||
|
||||
m_enabled = true;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Hook::Impl::disable() {
|
||||
if (m_enabled) {
|
||||
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
|
||||
if (!m_enabled)
|
||||
return Ok();
|
||||
|
||||
tulip::hook::removeHook(handler, m_handle);
|
||||
GEODE_UNWRAP_INTO(auto handler, LoaderImpl::get()->getHandler(m_address));
|
||||
|
||||
log::debug("Disabling hook at function {}", m_displayName);
|
||||
m_enabled = false;
|
||||
}
|
||||
tulip::hook::removeHook(handler, m_handle);
|
||||
|
||||
log::debug("Disabled {} hook", m_displayName);
|
||||
|
||||
m_enabled = false;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
@ -101,4 +108,4 @@ bool Hook::Impl::getAutoEnable() const {
|
|||
|
||||
void Hook::Impl::setAutoEnable(bool autoEnable) {
|
||||
m_autoEnable = autoEnable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -248,7 +248,7 @@ private:
|
|||
friend class Index;
|
||||
|
||||
void cleanupItems();
|
||||
void downloadIndex();
|
||||
void downloadIndex(std::string commitHash = "");
|
||||
void checkForUpdates();
|
||||
void updateFromLocalTree();
|
||||
void installNext(size_t index, IndexInstallList const& list);
|
||||
|
@ -292,7 +292,7 @@ bool Index::hasTriedToUpdate() const {
|
|||
return m_impl->m_triedToUpdate;
|
||||
}
|
||||
|
||||
void Index::Impl::downloadIndex() {
|
||||
void Index::Impl::downloadIndex(std::string commitHash) {
|
||||
log::debug("Downloading index");
|
||||
|
||||
IndexUpdateEvent(UpdateProgress(0, "Beginning download")).post();
|
||||
|
@ -303,7 +303,7 @@ void Index::Impl::downloadIndex() {
|
|||
.join("index-download")
|
||||
.fetch("https://github.com/geode-sdk/mods/zipball/main")
|
||||
.into(targetFile)
|
||||
.then([this, targetFile](auto) {
|
||||
.then([this, targetFile, commitHash](auto) {
|
||||
auto targetDir = dirs::getIndexDir() / "v0";
|
||||
// delete old unzipped index
|
||||
try {
|
||||
|
@ -329,6 +329,10 @@ void Index::Impl::downloadIndex() {
|
|||
|
||||
// remove the directory github adds to the root of the zip
|
||||
(void)flattenGithubRepo(targetDir);
|
||||
if (!commitHash.empty()) {
|
||||
auto const checksumPath = dirs::getIndexDir() / ".checksum";
|
||||
(void)file::writeString(checksumPath, commitHash);
|
||||
}
|
||||
|
||||
Loader::get()->queueInMainThread([this] {
|
||||
// update index
|
||||
|
@ -383,8 +387,7 @@ void Index::Impl::checkForUpdates() {
|
|||
}
|
||||
// otherwise save hash and download source
|
||||
else {
|
||||
(void)file::writeString(checksum, newSHA);
|
||||
this->downloadIndex();
|
||||
this->downloadIndex(newSHA);
|
||||
}
|
||||
})
|
||||
.expect([](std::string const& err) {
|
||||
|
@ -396,6 +399,8 @@ void Index::Impl::checkForUpdates() {
|
|||
|
||||
void Index::Impl::updateFromLocalTree() {
|
||||
log::debug("Updating local index cache");
|
||||
log::pushNest();
|
||||
|
||||
IndexUpdateEvent(UpdateProgress(100, "Updating local cache")).post();
|
||||
// delete old items
|
||||
m_items.clear();
|
||||
|
@ -435,6 +440,8 @@ void Index::Impl::updateFromLocalTree() {
|
|||
// mark source as finished
|
||||
m_isUpToDate = true;
|
||||
IndexUpdateEvent(UpdateFinished()).post();
|
||||
|
||||
log::popNest();
|
||||
}
|
||||
|
||||
void Index::update(bool force) {
|
||||
|
@ -583,7 +590,7 @@ bool Index::isUpdateAvailable(IndexItemHandle item) const {
|
|||
bool Index::areUpdatesAvailable() const {
|
||||
for (auto& mod : Loader::get()->getAllMods()) {
|
||||
auto item = this->getMajorItem(mod->getID());
|
||||
if (item && item->getMetadata().getVersion() > mod->getVersion()) {
|
||||
if (item && item->getMetadata().getVersion() > mod->getVersion() && mod->isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -658,7 +665,10 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
|||
}
|
||||
// recursively add dependencies
|
||||
GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem));
|
||||
ranges::push(list.list, deps.list);
|
||||
for (auto& dep : deps.list) {
|
||||
if (ranges::contains(list.list, dep)) continue;
|
||||
list.list.push_back(dep);
|
||||
}
|
||||
}
|
||||
// otherwise user must get this dependency manually from somewhere
|
||||
else {
|
||||
|
@ -730,6 +740,7 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
|
|||
|
||||
auto item = list.list.at(index);
|
||||
auto tempFile = dirs::getTempDir() / (item->getMetadata().getID() + ".index");
|
||||
log::debug("Installing {}", item->getMetadata().getID());
|
||||
m_runningInstallations[list.target] = web::AsyncWebRequest()
|
||||
.join("install_item_" + item->getMetadata().getID())
|
||||
.fetch(item->getDownloadURL())
|
||||
|
@ -765,6 +776,8 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
|
|||
|
||||
item->setIsInstalled(true);
|
||||
|
||||
log::debug("Installed {}", item->getMetadata().getID());
|
||||
|
||||
// Install next item in queue
|
||||
this->installNext(index + 1, list);
|
||||
})
|
||||
|
|
|
@ -41,15 +41,12 @@ void Loader::Impl::createDirectories() {
|
|||
ghc::filesystem::create_directory(dirs::getSaveDir());
|
||||
#endif
|
||||
|
||||
// try deleting geode/unzipped if it already exists
|
||||
try { ghc::filesystem::remove_all(dirs::getModRuntimeDir()); } catch(...) {}
|
||||
|
||||
(void) utils::file::createDirectoryAll(dirs::getGeodeResourcesDir());
|
||||
(void) utils::file::createDirectory(dirs::getModConfigDir());
|
||||
(void) utils::file::createDirectory(dirs::getModsDir());
|
||||
(void) utils::file::createDirectory(dirs::getGeodeLogDir());
|
||||
(void) utils::file::createDirectory(dirs::getTempDir());
|
||||
(void) utils::file::createDirectory(dirs::getModRuntimeDir());
|
||||
(void) utils::file::createDirectoryAll(dirs::getModConfigDir());
|
||||
(void) utils::file::createDirectoryAll(dirs::getModsDir());
|
||||
(void) utils::file::createDirectoryAll(dirs::getGeodeLogDir());
|
||||
(void) utils::file::createDirectoryAll(dirs::getTempDir());
|
||||
(void) utils::file::createDirectoryAll(dirs::getModRuntimeDir());
|
||||
|
||||
if (!ranges::contains(m_modSearchDirectories, dirs::getModsDir())) {
|
||||
m_modSearchDirectories.push_back(dirs::getModsDir());
|
||||
|
@ -61,33 +58,30 @@ Result<> Loader::Impl::setup() {
|
|||
return Ok();
|
||||
}
|
||||
|
||||
log::Logger::setup();
|
||||
|
||||
if (crashlog::setupPlatformHandler()) {
|
||||
log::debug("Set up platform crash logger");
|
||||
}
|
||||
else {
|
||||
log::debug("Unable to set up platform crash logger");
|
||||
log::debug("Setting up crash handler");
|
||||
log::pushNest();
|
||||
if (!crashlog::setupPlatformHandler()) {
|
||||
log::debug("Failed to set up crash handler");
|
||||
}
|
||||
log::popNest();
|
||||
|
||||
log::debug("Setting up Loader...");
|
||||
|
||||
log::debug("Set up internal mod representation");
|
||||
log::debug("Loading hooks... ");
|
||||
|
||||
log::debug("Loading hooks");
|
||||
log::pushNest();
|
||||
if (!this->loadHooks()) {
|
||||
return Err("There were errors loading some hooks, see console for details");
|
||||
}
|
||||
log::popNest();
|
||||
|
||||
log::debug("Loaded hooks");
|
||||
|
||||
log::debug("Setting up IPC...");
|
||||
|
||||
log::debug("Setting up IPC");
|
||||
log::pushNest();
|
||||
this->setupIPC();
|
||||
log::popNest();
|
||||
|
||||
log::debug("Setting up directories");
|
||||
log::pushNest();
|
||||
this->createDirectories();
|
||||
|
||||
this->addSearchPaths();
|
||||
log::popNest();
|
||||
|
||||
this->refreshModGraph();
|
||||
|
||||
|
@ -107,14 +101,14 @@ void Loader::Impl::updateResources() {
|
|||
|
||||
void Loader::Impl::updateResources(bool forceReload) {
|
||||
log::debug("Adding resources");
|
||||
|
||||
// add mods' spritesheets
|
||||
log::pushNest();
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
if (forceReload || !ModImpl::getImpl(mod)->m_resourcesLoaded) {
|
||||
this->updateModResources(mod);
|
||||
ModImpl::getImpl(mod)->m_resourcesLoaded = true;
|
||||
}
|
||||
if (!forceReload && ModImpl::getImpl(mod)->m_resourcesLoaded)
|
||||
continue;
|
||||
this->updateModResources(mod);
|
||||
ModImpl::getImpl(mod)->m_resourcesLoaded = true;
|
||||
}
|
||||
log::popNest();
|
||||
}
|
||||
|
||||
std::vector<Mod*> Loader::Impl::getAllMods() {
|
||||
|
@ -149,25 +143,27 @@ bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
|
|||
// Data saving
|
||||
|
||||
Result<> Loader::Impl::saveData() {
|
||||
// save mods' data
|
||||
for (auto& [id, mod] : m_mods) {
|
||||
log::debug("{}", mod->getID());
|
||||
log::pushNest();
|
||||
auto r = mod->saveData();
|
||||
if (!r) {
|
||||
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||
}
|
||||
log::popNest();
|
||||
}
|
||||
// save loader data
|
||||
GEODE_UNWRAP(Mod::get()->saveData());
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Loader::Impl::loadData() {
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
log::debug("{}", mod->getID());
|
||||
log::pushNest();
|
||||
auto r = mod->loadData();
|
||||
if (!r) {
|
||||
log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||
}
|
||||
log::popNest();
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
@ -210,9 +206,9 @@ void Loader::Impl::updateModResources(Mod* mod) {
|
|||
if (mod->getMetadata().getSpritesheets().empty())
|
||||
return;
|
||||
|
||||
log::debug("Adding resources for {}", mod->getID());
|
||||
log::debug("{}", mod->getID());
|
||||
log::pushNest();
|
||||
|
||||
// add spritesheets
|
||||
for (auto const& sheet : mod->getMetadata().getSpritesheets()) {
|
||||
log::debug("Adding sheet {}", sheet);
|
||||
auto png = sheet + ".png";
|
||||
|
@ -231,6 +227,8 @@ void Loader::Impl::updateModResources(Mod* mod) {
|
|||
CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
log::popNest();
|
||||
}
|
||||
|
||||
// Dependencies and refreshing
|
||||
|
@ -353,7 +351,7 @@ void Loader::Impl::buildModGraph() {
|
|||
|
||||
void Loader::Impl::loadModGraph(Mod* node, bool early) {
|
||||
if (early && !node->needsEarlyLoad()) {
|
||||
m_modsToLoad.push(node);
|
||||
m_modsToLoad.push_back(node);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -367,32 +365,86 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
|
|||
|
||||
if (node->isEnabled()) {
|
||||
for (auto const& dep : node->m_impl->m_dependants) {
|
||||
this->loadModGraph(dep, early);
|
||||
m_modsToLoad.push_front(dep);
|
||||
}
|
||||
log::popNest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->shouldLoad()) {
|
||||
log::debug("Load");
|
||||
auto res = node->m_impl->loadBinary();
|
||||
m_currentlyLoadingMod = node;
|
||||
m_refreshingModCount += 1;
|
||||
m_refreshedModCount += 1;
|
||||
m_lateRefreshedModCount += early ? 0 : 1;
|
||||
|
||||
auto unzipFunction = [this, node]() {
|
||||
log::debug("Unzip");
|
||||
auto res = node->m_impl->unzipGeodeFile(node->getMetadata());
|
||||
return res;
|
||||
};
|
||||
|
||||
auto loadFunction = [this, node, early]() {
|
||||
if (node->shouldLoad()) {
|
||||
log::debug("Load");
|
||||
auto res = node->m_impl->loadBinary();
|
||||
if (!res) {
|
||||
m_problems.push_back({
|
||||
LoadProblem::Type::LoadFailed,
|
||||
node,
|
||||
res.unwrapErr()
|
||||
});
|
||||
log::error("Failed to load binary: {}", res.unwrapErr());
|
||||
log::popNest();
|
||||
m_refreshingModCount -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->getID() == "absolllute.megahack")
|
||||
log::debug("megahack creepypasta");
|
||||
|
||||
for (auto const& dep : node->m_impl->m_dependants) {
|
||||
m_modsToLoad.push_front(dep);
|
||||
}
|
||||
}
|
||||
|
||||
m_refreshingModCount -= 1;
|
||||
|
||||
log::popNest();
|
||||
};
|
||||
|
||||
if (early) {
|
||||
auto res = unzipFunction();
|
||||
if (!res) {
|
||||
m_problems.push_back({
|
||||
LoadProblem::Type::LoadFailed,
|
||||
LoadProblem::Type::UnzipFailed,
|
||||
node,
|
||||
res.unwrapErr()
|
||||
});
|
||||
log::error("Failed to load binary: {}", res.unwrapErr());
|
||||
log::error("Failed to unzip: {}", res.unwrapErr());
|
||||
log::popNest();
|
||||
m_refreshingModCount -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const& dep : node->m_impl->m_dependants) {
|
||||
this->loadModGraph(dep, early);
|
||||
}
|
||||
loadFunction();
|
||||
}
|
||||
else {
|
||||
std::thread([=]() {
|
||||
auto res = unzipFunction();
|
||||
queueInMainThread([=]() {
|
||||
if (!res) {
|
||||
m_problems.push_back({
|
||||
LoadProblem::Type::UnzipFailed,
|
||||
node,
|
||||
res.unwrapErr()
|
||||
});
|
||||
log::error("Failed to unzip: {}", res.unwrapErr());
|
||||
log::popNest();
|
||||
m_refreshingModCount -= 1;
|
||||
return;
|
||||
}
|
||||
loadFunction();
|
||||
});
|
||||
}).detach();
|
||||
}
|
||||
|
||||
log::popNest();
|
||||
}
|
||||
|
||||
void Loader::Impl::findProblems() {
|
||||
|
@ -481,17 +533,17 @@ void Loader::Impl::findProblems() {
|
|||
}
|
||||
|
||||
void Loader::Impl::refreshModGraph() {
|
||||
log::info("Refreshing mod graph...");
|
||||
log::info("Refreshing mod graph");
|
||||
log::pushNest();
|
||||
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if (m_isSetup) {
|
||||
log::error("Cannot refresh mod graph after startup");
|
||||
log::popNest();
|
||||
return;
|
||||
}
|
||||
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
|
||||
m_problems.clear();
|
||||
|
||||
m_loadingState = LoadingState::Queue;
|
||||
|
@ -533,16 +585,29 @@ void Loader::Impl::refreshModGraph() {
|
|||
else
|
||||
m_loadingState = LoadingState::Mods;
|
||||
|
||||
queueInMainThread([]() {
|
||||
Loader::get()->m_impl->continueRefreshModGraph();
|
||||
queueInMainThread([&]() {
|
||||
this->continueRefreshModGraph();
|
||||
});
|
||||
}
|
||||
|
||||
void Loader::Impl::continueRefreshModGraph() {
|
||||
if (m_refreshingModCount != 0) {
|
||||
queueInMainThread([&]() {
|
||||
this->continueRefreshModGraph();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_lateRefreshedModCount > 0) {
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_timerBegin).count();
|
||||
log::info("Took {}s", static_cast<float>(time) / 1000.f);
|
||||
}
|
||||
|
||||
log::info("Continuing mod graph refresh...");
|
||||
log::pushNest();
|
||||
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
m_timerBegin = std::chrono::high_resolution_clock::now();
|
||||
|
||||
switch (m_loadingState) {
|
||||
case LoadingState::Mods:
|
||||
|
@ -550,7 +615,7 @@ void Loader::Impl::continueRefreshModGraph() {
|
|||
log::pushNest();
|
||||
this->loadModGraph(m_modsToLoad.front(), false);
|
||||
log::popNest();
|
||||
m_modsToLoad.pop();
|
||||
m_modsToLoad.pop_front();
|
||||
if (m_modsToLoad.empty())
|
||||
m_loadingState = LoadingState::Problems;
|
||||
break;
|
||||
|
@ -560,6 +625,11 @@ void Loader::Impl::continueRefreshModGraph() {
|
|||
this->findProblems();
|
||||
log::popNest();
|
||||
m_loadingState = LoadingState::Done;
|
||||
{
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_timerBegin).count();
|
||||
log::info("Took {}s", static_cast<float>(time) / 1000.f);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
m_loadingState = LoadingState::Done;
|
||||
|
@ -568,13 +638,9 @@ void Loader::Impl::continueRefreshModGraph() {
|
|||
break;
|
||||
}
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
|
||||
log::info("Took {}s", static_cast<float>(time) / 1000.f);
|
||||
|
||||
if (m_loadingState != LoadingState::Done) {
|
||||
queueInMainThread([]() {
|
||||
Loader::get()->m_impl->continueRefreshModGraph();
|
||||
queueInMainThread([&]() {
|
||||
this->continueRefreshModGraph();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -610,23 +676,22 @@ bool Loader::Impl::isReadyToHook() const {
|
|||
return m_readyToHook;
|
||||
}
|
||||
|
||||
void Loader::Impl::addInternalHook(Hook* hook, Mod* mod) {
|
||||
m_internalHooks.emplace_back(hook, mod);
|
||||
void Loader::Impl::addUninitializedHook(Hook* hook, Mod* mod) {
|
||||
m_uninitializedHooks.emplace_back(hook, mod);
|
||||
}
|
||||
|
||||
bool Loader::Impl::loadHooks() {
|
||||
m_readyToHook = true;
|
||||
auto thereWereErrors = false;
|
||||
for (auto const& hook : m_internalHooks) {
|
||||
bool hadErrors = false;
|
||||
for (auto const& hook : m_uninitializedHooks) {
|
||||
auto res = hook.second->addHook(hook.first);
|
||||
if (!res) {
|
||||
log::internalLog(Severity::Error, hook.second, "{}", res.unwrapErr());
|
||||
thereWereErrors = true;
|
||||
hadErrors = true;
|
||||
}
|
||||
}
|
||||
// free up memory
|
||||
m_internalHooks.clear();
|
||||
return !thereWereErrors;
|
||||
m_uninitializedHooks.clear();
|
||||
return !hadErrors;
|
||||
}
|
||||
|
||||
void Loader::Impl::queueInMainThread(ScheduledFunction func) {
|
||||
|
@ -707,14 +772,12 @@ void Loader::Impl::tryDownloadLoaderResources(
|
|||
.expect([this, tryLatestOnError](std::string const& info, int code) {
|
||||
// if the url was not found, try downloading latest release instead
|
||||
// (for development versions)
|
||||
if (code == 404 && tryLatestOnError) {
|
||||
this->downloadLoaderResources(true);
|
||||
}
|
||||
else {
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to download resources: " + info)
|
||||
).post();
|
||||
if (code == 404) {
|
||||
log::warn("Unable to download resources: {}", info);
|
||||
}
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to download resources: " + info)
|
||||
).post();
|
||||
})
|
||||
.progress([](auto&, double now, double total) {
|
||||
ResourceDownloadEvent(
|
||||
|
@ -735,65 +798,67 @@ void Loader::Impl::updateSpecialFiles() {
|
|||
}
|
||||
|
||||
void Loader::Impl::downloadLoaderResources(bool useLatestRelease) {
|
||||
if (!useLatestRelease) {
|
||||
web::AsyncWebRequest()
|
||||
.join("loader-tag-exists-check")
|
||||
.userAgent("github_api/1.0")
|
||||
.fetch(fmt::format(
|
||||
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
|
||||
web::AsyncWebRequest()
|
||||
.join("loader-tag-exists-check")
|
||||
.userAgent("github_api/1.0")
|
||||
.fetch(fmt::format(
|
||||
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
|
||||
this->getVersion().toString()
|
||||
))
|
||||
.json()
|
||||
.then([this](json::Value const& json) {
|
||||
this->tryDownloadLoaderResources(fmt::format(
|
||||
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip",
|
||||
this->getVersion().toString()
|
||||
))
|
||||
.json()
|
||||
.then([this](json::Value const& json) {
|
||||
this->tryDownloadLoaderResources(fmt::format(
|
||||
"https://github.com/geode-sdk/geode/releases/download/{}/resources-" GEODE_PLATFORM_SHORT_IDENTIFIER ".zip",
|
||||
this->getVersion().toString()
|
||||
), true);
|
||||
})
|
||||
.expect([this](std::string const& info, int code) {
|
||||
if (code == 404) {
|
||||
log::debug("Loader version {} does not exist on Github, not downloading the resources", this->getVersion().toString());
|
||||
ResourceDownloadEvent(
|
||||
UpdateFinished()
|
||||
).post();
|
||||
), true);
|
||||
})
|
||||
.expect([=](std::string const& info, int code) {
|
||||
if (code == 404) {
|
||||
if (useLatestRelease) {
|
||||
log::debug("Loader version {} does not exist on Github, downloading latest resources", this->getVersion().toString());
|
||||
fetchLatestGithubRelease(
|
||||
[this](json::Value const& raw) {
|
||||
auto json = raw;
|
||||
JsonChecker checker(json);
|
||||
auto root = checker.root("[]").obj();
|
||||
|
||||
// find release asset
|
||||
for (auto asset : root.needs("assets").iterate()) {
|
||||
auto obj = asset.obj();
|
||||
if (obj.needs("name").template get<std::string>() == "resources.zip") {
|
||||
this->tryDownloadLoaderResources(
|
||||
obj.needs("browser_download_url").template get<std::string>(),
|
||||
false
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to find resources in latest GitHub release")
|
||||
).post();
|
||||
},
|
||||
[this](std::string const& info) {
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to download resources: " + info)
|
||||
).post();
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to check if tag exists: " + info)
|
||||
).post();
|
||||
log::debug("Loader version {} does not exist on Github, not downloading the resources", this->getVersion().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
fetchLatestGithubRelease(
|
||||
[this](json::Value const& raw) {
|
||||
auto json = raw;
|
||||
JsonChecker checker(json);
|
||||
auto root = checker.root("[]").obj();
|
||||
|
||||
// find release asset
|
||||
for (auto asset : root.needs("assets").iterate()) {
|
||||
auto obj = asset.obj();
|
||||
if (obj.needs("name").template get<std::string>() == "resources-" GEODE_PLATFORM_SHORT_IDENTIFIER ".zip") {
|
||||
this->tryDownloadLoaderResources(
|
||||
obj.needs("browser_download_url").template get<std::string>(),
|
||||
false
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to find resources in latest GitHub release")
|
||||
).post();
|
||||
},
|
||||
[this](std::string const& info) {
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to download resources: " + info)
|
||||
UpdateFinished()
|
||||
).post();
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to check if tag exists: " + info)
|
||||
).post();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Loader::Impl::verifyLoaderResources() {
|
||||
|
@ -811,7 +876,7 @@ bool Loader::Impl::verifyLoaderResources() {
|
|||
ghc::filesystem::is_directory(resourcesDir)
|
||||
)) {
|
||||
log::debug("Resources directory does not exist");
|
||||
this->downloadLoaderResources();
|
||||
this->downloadLoaderResources(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1006,21 +1071,16 @@ void Loader::Impl::provideNextMod(Mod* mod) {
|
|||
}
|
||||
|
||||
Mod* Loader::Impl::takeNextMod() {
|
||||
if (!m_nextMod) {
|
||||
m_nextMod = this->createInternalMod();
|
||||
log::debug("Created internal mod {}", m_nextMod->getName());
|
||||
}
|
||||
auto ret = m_nextMod;
|
||||
return ret;
|
||||
if (!m_nextMod)
|
||||
m_nextMod = this->getInternalMod();
|
||||
return m_nextMod;
|
||||
}
|
||||
|
||||
void Loader::Impl::releaseNextMod() {
|
||||
m_nextMod = nullptr;
|
||||
|
||||
m_nextModLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
Result<> Loader::Impl::createHandler(void* address, tulip::hook::HandlerMetadata const& metadata) {
|
||||
if (m_handlerHandles.count(address)) {
|
||||
return Err("Handler already exists at address");
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <Geode/utils/ranges.hpp>
|
||||
#include <Geode/utils/MiniFunction.hpp>
|
||||
#include "ModImpl.hpp"
|
||||
#include <about.hpp>
|
||||
#include <crashlog.hpp>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
@ -59,7 +58,7 @@ namespace geode {
|
|||
std::vector<ghc::filesystem::path> m_modSearchDirectories;
|
||||
std::vector<LoadProblem> m_problems;
|
||||
std::unordered_map<std::string, Mod*> m_mods;
|
||||
std::queue<Mod*> m_modsToLoad;
|
||||
std::deque<Mod*> m_modsToLoad;
|
||||
std::vector<ghc::filesystem::path> m_texturePaths;
|
||||
bool m_isSetup = false;
|
||||
|
||||
|
@ -72,7 +71,7 @@ namespace geode {
|
|||
|
||||
std::vector<utils::MiniFunction<void(void)>> m_gdThreadQueue;
|
||||
mutable std::mutex m_gdThreadMutex;
|
||||
std::vector<std::pair<Hook*, Mod*>> m_internalHooks;
|
||||
std::vector<std::pair<Hook*, Mod*>> m_uninitializedHooks;
|
||||
bool m_readyToHook = false;
|
||||
|
||||
bool m_platformConsoleOpen = false;
|
||||
|
@ -84,6 +83,14 @@ namespace geode {
|
|||
std::mutex m_nextModAccessMutex;
|
||||
Mod* m_nextMod = nullptr;
|
||||
|
||||
Mod* m_currentlyLoadingMod = nullptr;
|
||||
|
||||
int m_refreshingModCount = 0;
|
||||
int m_refreshedModCount = 0;
|
||||
int m_lateRefreshedModCount = 0;
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_timerBegin;
|
||||
|
||||
void provideNextMod(Mod* mod);
|
||||
Mod* takeNextMod();
|
||||
void releaseNextMod();
|
||||
|
@ -166,9 +173,9 @@ namespace geode {
|
|||
bool isNewUpdateDownloaded() const;
|
||||
|
||||
bool isReadyToHook() const;
|
||||
void addInternalHook(Hook* hook, Mod* mod);
|
||||
void addUninitializedHook(Hook* hook, Mod* mod);
|
||||
|
||||
Mod* createInternalMod();
|
||||
Mod* getInternalMod();
|
||||
Result<> setupInternalMod();
|
||||
|
||||
bool userTriedToLoadDLLs() const;
|
||||
|
|
|
@ -113,6 +113,36 @@ std::string Log::toString(bool logTime, uint32_t nestLevel) const {
|
|||
res += fmt::format("{:%H:%M:%S}", m_time);
|
||||
}
|
||||
|
||||
switch (m_severity.m_value) {
|
||||
case Severity::Debug:
|
||||
res += " DBG";
|
||||
break;
|
||||
case Severity::Info:
|
||||
res += " INF";
|
||||
break;
|
||||
case Severity::Notice:
|
||||
res += " NTC";
|
||||
break;
|
||||
case Severity::Warning:
|
||||
res += " WRN";
|
||||
break;
|
||||
case Severity::Error:
|
||||
res += " ERR";
|
||||
break;
|
||||
case Severity::Critical:
|
||||
res += " CRT";
|
||||
break;
|
||||
case Severity::Alert:
|
||||
res += " ALR";
|
||||
break;
|
||||
case Severity::Emergency:
|
||||
res += " FAT";
|
||||
break;
|
||||
default:
|
||||
res += " UNK";
|
||||
break;
|
||||
}
|
||||
|
||||
res += fmt::format(" [{}]: ", m_sender ? m_sender->getName() : "Geode?");
|
||||
|
||||
for (uint32_t i = 0; i < nestLevel; i++) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "ModMetadataImpl.hpp"
|
||||
#include "about.hpp"
|
||||
|
||||
#include <hash/hash.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/Hook.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
|
@ -44,7 +45,7 @@ Result<> Mod::Impl::setup() {
|
|||
}
|
||||
if (!m_resourcesLoaded) {
|
||||
auto searchPathRoot = dirs::getModRuntimeDir() / m_metadata.getID() / "resources";
|
||||
CCFileUtils::get()->addSearchPath(searchPathRoot.string().c_str());
|
||||
// CCFileUtils::get()->addSearchPath(searchPathRoot.string().c_str());
|
||||
|
||||
m_resourcesLoaded = true;
|
||||
}
|
||||
|
@ -225,7 +226,7 @@ Result<> Mod::Impl::saveData() {
|
|||
log::debug("Check covered");
|
||||
for (auto& [key, value] : m_savedSettingsData.as_object()) {
|
||||
log::debug("Check if {} is saved", key);
|
||||
if (!coveredSettings.count(key)) {
|
||||
if (!coveredSettings.contains(key)) {
|
||||
json[key] = value;
|
||||
}
|
||||
}
|
||||
|
@ -470,18 +471,21 @@ Result<> Mod::Impl::disableHook(Hook* hook) {
|
|||
}
|
||||
|
||||
Result<Hook*> Mod::Impl::addHook(Hook* hook) {
|
||||
m_hooks.push_back(hook);
|
||||
if (LoaderImpl::get()->isReadyToHook()) {
|
||||
if (this->isEnabled() && hook->getAutoEnable()) {
|
||||
auto res = this->enableHook(hook);
|
||||
if (!res) {
|
||||
delete hook;
|
||||
return Err("Can't create hook: " + res.unwrapErr());
|
||||
}
|
||||
}
|
||||
if (!ranges::contains(m_hooks, [&](auto const& h) { return h == hook; }))
|
||||
m_hooks.push_back(hook);
|
||||
|
||||
if (!LoaderImpl::get()->isReadyToHook()) {
|
||||
LoaderImpl::get()->addUninitializedHook(hook, m_self);
|
||||
return Ok(hook);
|
||||
}
|
||||
else {
|
||||
LoaderImpl::get()->addInternalHook(hook, m_self);
|
||||
|
||||
if (!this->isEnabled() || !hook->getAutoEnable())
|
||||
return Ok(hook);
|
||||
|
||||
auto res = this->enableHook(hook);
|
||||
if (!res) {
|
||||
delete hook;
|
||||
return Err("Can't create hook: " + res.unwrapErr());
|
||||
}
|
||||
|
||||
return Ok(hook);
|
||||
|
@ -554,21 +558,52 @@ Result<> Mod::Impl::createTempDir() {
|
|||
return Err("Unable to create mod runtime directory");
|
||||
}
|
||||
|
||||
// Unzip .geode file into temp dir
|
||||
GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_metadata.getPath()));
|
||||
if (!unzip.hasEntry(m_metadata.getBinaryName())) {
|
||||
return Err(
|
||||
fmt::format("Unable to find platform binary under the name \"{}\"", m_metadata.getBinaryName())
|
||||
);
|
||||
}
|
||||
GEODE_UNWRAP(unzip.extractAllTo(tempPath));
|
||||
|
||||
// Mark temp dir creation as succesful
|
||||
m_tempDirName = tempPath;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Mod::Impl::unzipGeodeFile(ModMetadata metadata) {
|
||||
// Unzip .geode file into temp dir
|
||||
auto tempDir = dirs::getModRuntimeDir() / metadata.getID();
|
||||
|
||||
auto datePath = tempDir / "modified-at";
|
||||
std::string currentHash = file::readString(datePath).unwrapOr("");
|
||||
|
||||
auto modifiedDate = ghc::filesystem::last_write_time(metadata.getPath());
|
||||
auto modifiedCount = std::chrono::duration_cast<std::chrono::milliseconds>(modifiedDate.time_since_epoch());
|
||||
auto modifiedHash = std::to_string(modifiedCount.count());
|
||||
if (currentHash == modifiedHash) {
|
||||
log::debug("Same hash detected, skipping unzip");
|
||||
return Ok();
|
||||
}
|
||||
log::debug("Hash mismatch detected, unzipping");
|
||||
|
||||
std::error_code ec;
|
||||
ghc::filesystem::remove_all(tempDir, ec);
|
||||
if (ec) {
|
||||
return Err("Unable to delete temp dir: " + ec.message());
|
||||
}
|
||||
|
||||
(void)utils::file::createDirectoryAll(tempDir);
|
||||
auto res = file::writeString(datePath, modifiedHash);
|
||||
if (!res) {
|
||||
log::warn("Failed to write modified date of geode zip: {}", res.unwrapErr());
|
||||
}
|
||||
|
||||
|
||||
GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(metadata.getPath()));
|
||||
if (!unzip.hasEntry(metadata.getBinaryName())) {
|
||||
return Err(
|
||||
fmt::format("Unable to find platform binary under the name \"{}\"", metadata.getBinaryName())
|
||||
);
|
||||
}
|
||||
GEODE_UNWRAP(unzip.extractAllTo(tempDir));
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
ghc::filesystem::path Mod::Impl::getConfigDir(bool create) const {
|
||||
auto dir = dirs::getModConfigDir() / m_metadata.getID();
|
||||
if (create) {
|
||||
|
@ -578,7 +613,6 @@ ghc::filesystem::path Mod::Impl::getConfigDir(bool create) const {
|
|||
}
|
||||
|
||||
char const* Mod::Impl::expandSpriteName(char const* name) {
|
||||
log::debug("Expanding sprite name {} for {}", name, m_metadata.getID());
|
||||
if (m_expandedSprites.count(name)) return m_expandedSprites[name];
|
||||
|
||||
auto exp = new char[strlen(name) + 2 + m_metadata.getID().size()];
|
||||
|
@ -638,9 +672,15 @@ static Result<ModMetadata> getModImplInfo() {
|
|||
return Ok(info);
|
||||
}
|
||||
|
||||
Mod* Loader::Impl::createInternalMod() {
|
||||
Mod* Loader::Impl::getInternalMod() {
|
||||
auto& mod = Mod::sharedMod<>;
|
||||
if (mod) return mod;
|
||||
if (mod)
|
||||
return mod;
|
||||
if (m_mods.contains("geode.loader")) {
|
||||
log::warn("Something went wrong and Mod::sharedMod<> got unset after the internal mod was created! Setting sharedMod back...");
|
||||
mod = m_mods["geode.loader"];
|
||||
return mod;
|
||||
}
|
||||
auto infoRes = getModImplInfo();
|
||||
if (!infoRes) {
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -62,6 +62,7 @@ Result<StringSetting> StringSetting::parse(JsonMaybeObject& obj) {
|
|||
StringSetting sett;
|
||||
parseCommon(sett, obj);
|
||||
obj.has("match").into(sett.match);
|
||||
obj.has("filter").into(sett.filter);
|
||||
return Ok(sett);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
55
loader/src/platform/android/backtrace/ScopedFd.hpp
Normal file
55
loader/src/platform/android/backtrace/ScopedFd.hpp
Normal 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_;
|
||||
};
|
191
loader/src/platform/android/backtrace/execinfo.hpp
Normal file
191
loader/src/platform/android/backtrace/execinfo.hpp
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
68
loader/src/platform/android/gdstdlib.cpp
Normal file
68
loader/src/platform/android/gdstdlib.cpp
Normal 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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -382,6 +382,8 @@ bool crashlog::setupPlatformHandler() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void crashlog::setupPlatformHandlerPost() {}
|
||||
|
||||
bool crashlog::didLastLaunchCrash() {
|
||||
return s_lastLaunchCrashed;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,24 +20,37 @@ void Loader::Impl::platformMessageBox(char const* title, std::string const& info
|
|||
bool hasAnsiColorSupport = false;
|
||||
|
||||
void Loader::Impl::logConsoleMessageWithSeverity(std::string const& msg, Severity severity) {
|
||||
if (m_platformConsoleOpen) {
|
||||
if (hasAnsiColorSupport) {
|
||||
int color = 0;
|
||||
switch (severity) {
|
||||
case Severity::Debug: color = 243; break;
|
||||
case Severity::Info: color = 33; break;
|
||||
case Severity::Warning: color = 229; break;
|
||||
case Severity::Error: color = 9; break;
|
||||
default: color = 7; break;
|
||||
}
|
||||
auto const colorStr = fmt::format("\x1b[38;5;{}m", color);
|
||||
auto const newMsg = fmt::format("{}{}\x1b[0m{}", colorStr, msg.substr(0, 8), msg.substr(8));
|
||||
if (!m_platformConsoleOpen)
|
||||
return;
|
||||
|
||||
std::cout << newMsg << "\n" << std::flush;
|
||||
} else {
|
||||
std::cout << msg << "\n" << std::flush;
|
||||
}
|
||||
if (!hasAnsiColorSupport) {
|
||||
std::cout << msg << "\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
int color = 0;
|
||||
switch (severity) {
|
||||
case Severity::Debug:
|
||||
color = 243;
|
||||
break;
|
||||
case Severity::Info:
|
||||
color = 33;
|
||||
break;
|
||||
case Severity::Warning:
|
||||
color = 229;
|
||||
break;
|
||||
case Severity::Error:
|
||||
color = 9;
|
||||
break;
|
||||
default:
|
||||
color = 7;
|
||||
break;
|
||||
}
|
||||
auto const colorStr = fmt::format("\x1b[38;5;{}m", color);
|
||||
auto const newMsg = fmt::format("{}{}\x1b[0m{}", colorStr, msg.substr(0, 12),
|
||||
msg.substr(12));
|
||||
|
||||
std::cout << newMsg << "\n" << std::flush;
|
||||
}
|
||||
|
||||
void Loader::Impl::openPlatformConsole() {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <iostream>
|
||||
#include <string>
|
||||
#include <fmt/core.h>
|
||||
#include "ehdata_structs.hpp"
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
|
@ -199,24 +200,61 @@ static std::string getRegisters(PCONTEXT context) {
|
|||
|
||||
static std::string getInfo(LPEXCEPTION_POINTERS info, Mod* faultyMod) {
|
||||
std::stringstream stream;
|
||||
stream << "Faulty Module: "
|
||||
<< getModuleName(handleFromAddress(info->ExceptionRecord->ExceptionAddress), true)
|
||||
<< "\n"
|
||||
<< "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n"
|
||||
<< "Exception Code: " << std::hex << info->ExceptionRecord->ExceptionCode << " ("
|
||||
<< getExceptionCodeString(info->ExceptionRecord->ExceptionCode) << ")" << std::dec
|
||||
<< "\n"
|
||||
<< "Exception Flags: " << info->ExceptionRecord->ExceptionFlags << "\n"
|
||||
<< "Exception Address: " << info->ExceptionRecord->ExceptionAddress << " (";
|
||||
printAddr(stream, info->ExceptionRecord->ExceptionAddress, false);
|
||||
stream << ")"
|
||||
<< "\n"
|
||||
<< "Number Parameters: " << info->ExceptionRecord->NumberParameters << "\n";
|
||||
|
||||
if (info->ExceptionRecord->ExceptionCode == EH_EXCEPTION_NUMBER) {
|
||||
// This executes when a C++ exception was thrown and not handled.
|
||||
// https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273
|
||||
|
||||
// since you can throw virtually anything, we need to figure out if it's an std::exception* or not
|
||||
bool isStdException = false;
|
||||
|
||||
auto* exceptionRecord = info->ExceptionRecord;
|
||||
auto exceptionObject = exceptionRecord->ExceptionInformation[1];
|
||||
|
||||
auto* throwInfo = reinterpret_cast<_ThrowInfo*>(exceptionRecord->ExceptionInformation[2]);
|
||||
auto* catchableTypeArray = reinterpret_cast<_CatchableTypeArray*>(throwInfo->pCatchableTypeArray);
|
||||
auto ctaSize = catchableTypeArray->nCatchableTypes;
|
||||
|
||||
for (int i = 0; i < ctaSize; i++) {
|
||||
auto* catchableType = reinterpret_cast<_CatchableType*>(catchableTypeArray->arrayOfCatchableTypes[i]);
|
||||
auto* ctDescriptor = reinterpret_cast<_TypeDescriptor*>(catchableType->pType);
|
||||
const char* classname = ctDescriptor->name;
|
||||
|
||||
if (strcmp(classname, ".?AVexception@std@@") == 0) {
|
||||
isStdException = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isStdException) {
|
||||
std::exception* excObject = reinterpret_cast<std::exception*>(exceptionObject);
|
||||
// stream << "C++ Exception Type: " << typeid(excObject).name() << "\n"; // always const std::exception *
|
||||
stream << "C++ Exception: " << excObject->what() << "\n";
|
||||
} else {
|
||||
stream << "C++ Exception: <Unknown type>\n";
|
||||
}
|
||||
|
||||
stream << "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n";
|
||||
} else {
|
||||
stream << "Faulty Module: "
|
||||
<< getModuleName(handleFromAddress(info->ExceptionRecord->ExceptionAddress), true)
|
||||
<< "\n"
|
||||
<< "Faulty Mod: " << (faultyMod ? faultyMod->getID() : "<Unknown>") << "\n"
|
||||
<< "Exception Code: " << std::hex << info->ExceptionRecord->ExceptionCode << " ("
|
||||
<< getExceptionCodeString(info->ExceptionRecord->ExceptionCode) << ")" << std::dec
|
||||
<< "\n"
|
||||
<< "Exception Flags: " << info->ExceptionRecord->ExceptionFlags << "\n"
|
||||
<< "Exception Address: " << info->ExceptionRecord->ExceptionAddress << " (";
|
||||
printAddr(stream, info->ExceptionRecord->ExceptionAddress, false);
|
||||
stream << ")"
|
||||
<< "\n"
|
||||
<< "Number Parameters: " << info->ExceptionRecord->NumberParameters << "\n";
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
|
||||
|
||||
|
||||
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
|
||||
|
||||
// init symbols so we can get some juicy debug info
|
||||
|
@ -226,16 +264,22 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
|
|||
|
||||
auto text = crashlog::writeCrashlog(faultyMod, getInfo(info, faultyMod), getStacktrace(info->ContextRecord), getRegisters(info->ContextRecord));
|
||||
|
||||
// show message box on debug mode
|
||||
#ifdef GEODE_DEBUG
|
||||
MessageBoxA(nullptr, text.c_str(), "Geode Crashed", MB_ICONERROR);
|
||||
#endif
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool crashlog::setupPlatformHandler() {
|
||||
static LONG WINAPI exceptionHandlerDummy(LPEXCEPTION_POINTERS info) {
|
||||
SetUnhandledExceptionFilter(exceptionHandler);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool crashlog::setupPlatformHandler() {
|
||||
// for some reason, on exceptions windows seems to clear SetUnhandledExceptionFilter
|
||||
// so we attach a VE handler (which runs *earlier*) and inside set our crash handler
|
||||
AddVectoredExceptionHandler(0, exceptionHandlerDummy);
|
||||
SetUnhandledExceptionFilter(exceptionHandler);
|
||||
|
||||
auto lastCrashedFile = crashlog::getCrashLogDirectory() / "last-crashed";
|
||||
if (ghc::filesystem::exists(lastCrashedFile)) {
|
||||
g_lastLaunchCrashed = true;
|
||||
|
@ -252,6 +296,8 @@ bool crashlog::didLastLaunchCrash() {
|
|||
return g_lastLaunchCrashed;
|
||||
}
|
||||
|
||||
void crashlog::setupPlatformHandlerPost() {}
|
||||
|
||||
ghc::filesystem::path crashlog::getCrashLogDirectory() {
|
||||
return dirs::getGeodeDir() / "crashlogs";
|
||||
}
|
||||
|
|
55
loader/src/platform/windows/ehdata_structs.hpp
Normal file
55
loader/src/platform/windows/ehdata_structs.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
// _ThrowInfo and all of those other structs are hardcoded into MSVC (the compiler itself, unavailable in any header),
|
||||
// but don't exist in other compilers like Clang, causing <ehdata.h> to not compile.
|
||||
//
|
||||
// We define them manually in order to be able to use them.
|
||||
// source: https://www.geoffchappell.com/studies/msvc/language/predefined/index.htm
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__) || defined(__INTELLISENSE__)
|
||||
|
||||
typedef struct _PMD
|
||||
{
|
||||
int mdisp;
|
||||
int pdisp;
|
||||
int vdisp;
|
||||
} _PMD;
|
||||
|
||||
typedef void (*_PMFN) (void);
|
||||
|
||||
#pragma warning (disable:4200)
|
||||
#pragma pack (push, _TypeDescriptor, 8)
|
||||
typedef struct _TypeDescriptor
|
||||
{
|
||||
const void *pVFTable;
|
||||
void *spare;
|
||||
char name [];
|
||||
} _TypeDescriptor;
|
||||
#pragma pack (pop, _TypeDescriptor)
|
||||
#pragma warning (default:4200)
|
||||
|
||||
typedef const struct _s__CatchableType {
|
||||
unsigned int properties;
|
||||
_TypeDescriptor *pType;
|
||||
_PMD thisDisplacement;
|
||||
int sizeOrOffset;
|
||||
_PMFN copyFunction;
|
||||
} _CatchableType;
|
||||
|
||||
#pragma warning (disable:4200)
|
||||
typedef const struct _s__CatchableTypeArray {
|
||||
int nCatchableTypes;
|
||||
_CatchableType *arrayOfCatchableTypes [];
|
||||
} _CatchableTypeArray;
|
||||
#pragma warning (default:4200)
|
||||
|
||||
typedef const struct _s__ThrowInfo {
|
||||
unsigned int attributes;
|
||||
_PMFN pmfnUnwind;
|
||||
int (__cdecl *pForwardCompat) (...);
|
||||
_CatchableTypeArray *pCatchableTypeArray;
|
||||
} _ThrowInfo;
|
||||
|
||||
#endif // defined(__GNUC__) || defined(__clang__) || defined(__INTELLISENSE__)
|
||||
|
||||
#include <ehdata.h> // for EH_EXCEPTION_NUMBER
|
|
@ -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)) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
#include "../list/ModListCell.hpp"
|
||||
#include "../list/ModListLayer.hpp"
|
||||
|
||||
bool DevProfilePopup::setup(std::string const& developer) {
|
||||
bool DevProfilePopup::setup(std::string const& developer, ModListLayer* list) {
|
||||
m_noElasticity = true;
|
||||
m_layer = list;
|
||||
|
||||
this->setTitle("Mods by " + developer);
|
||||
|
||||
|
@ -18,7 +19,7 @@ bool DevProfilePopup::setup(std::string const& developer) {
|
|||
for (auto& mod : Loader::get()->getAllMods()) {
|
||||
if (mod->getDeveloper() == developer) {
|
||||
auto cell = ModCell::create(
|
||||
mod, nullptr, ModListDisplay::Concise, { 358.f, 40.f }
|
||||
mod, m_layer, ModListDisplay::Concise, { 358.f, 40.f }
|
||||
);
|
||||
cell->disableDeveloperButton();
|
||||
items->addObject(cell);
|
||||
|
@ -31,7 +32,7 @@ bool DevProfilePopup::setup(std::string const& developer) {
|
|||
continue;
|
||||
}
|
||||
auto cell = IndexItemCell::create(
|
||||
item, nullptr, ModListDisplay::Concise, { 358.f, 40.f }
|
||||
item, m_layer, ModListDisplay::Concise, { 358.f, 40.f }
|
||||
);
|
||||
cell->disableDeveloperButton();
|
||||
items->addObject(cell);
|
||||
|
@ -39,18 +40,18 @@ bool DevProfilePopup::setup(std::string const& developer) {
|
|||
|
||||
// mods list
|
||||
auto listSize = CCSize { 358.f, 160.f };
|
||||
auto list = ListView::create(items, 40.f, listSize.width, listSize.height);
|
||||
list->setPosition(winSize / 2 - listSize / 2);
|
||||
m_mainLayer->addChild(list);
|
||||
auto cellList = ListView::create(items, 40.f, listSize.width, listSize.height);
|
||||
cellList->setPosition(winSize / 2 - listSize / 2);
|
||||
m_mainLayer->addChild(cellList);
|
||||
|
||||
addListBorders(m_mainLayer, winSize / 2, listSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DevProfilePopup* DevProfilePopup::create(std::string const& developer) {
|
||||
DevProfilePopup* DevProfilePopup::create(std::string const& developer, ModListLayer* list) {
|
||||
auto ret = new DevProfilePopup();
|
||||
if (ret && ret->init(420.f, 260.f, developer)) {
|
||||
if (ret && ret->init(420.f, 260.f, developer, list)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
|
||||
using namespace geode::prelude;
|
||||
|
||||
class DevProfilePopup : public Popup<std::string const&> {
|
||||
class ModListLayer;
|
||||
|
||||
class DevProfilePopup : public Popup<std::string const&, ModListLayer*> {
|
||||
protected:
|
||||
bool setup(std::string const& developer) override;
|
||||
ModListLayer* m_layer;
|
||||
|
||||
bool setup(std::string const& developer, ModListLayer* list) override;
|
||||
|
||||
public:
|
||||
static DevProfilePopup* create(std::string const& developer);
|
||||
static DevProfilePopup* create(std::string const& developer, ModListLayer* list);
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "../list/ModListLayer.hpp"
|
||||
#include "../settings/ModSettingsPopup.hpp"
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <about.hpp>
|
||||
|
||||
#include <Geode/binding/ButtonSprite.hpp>
|
||||
#include <Geode/binding/CCTextInputNode.hpp>
|
||||
|
|
|
@ -53,24 +53,21 @@ void InstallListCell::setupInfo(
|
|||
}
|
||||
this->addChild(m_titleLabel);
|
||||
|
||||
m_developerBtn = nullptr;
|
||||
m_creatorLabel = nullptr;
|
||||
if (developer) {
|
||||
auto creatorStr = "by " + *developer;
|
||||
auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
|
||||
creatorLabel->setScale(.34f);
|
||||
m_creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
|
||||
m_creatorLabel->setScale(.34f);
|
||||
if (inactive) {
|
||||
creatorLabel->setColor({ 163, 163, 163 });
|
||||
m_creatorLabel->setColor({ 163, 163, 163 });
|
||||
}
|
||||
|
||||
m_developerBtn = CCMenuItemSpriteExtra::create(
|
||||
creatorLabel, this, menu_selector(InstallListCell::onViewDev)
|
||||
);
|
||||
m_developerBtn->setPosition(
|
||||
m_creatorLabel->setPosition(
|
||||
m_titleLabel->getPositionX() + m_titleLabel->getScaledContentSize().width + 3.f +
|
||||
creatorLabel->getScaledContentSize().width / 2,
|
||||
m_creatorLabel->getScaledContentSize().width / 2,
|
||||
m_height / 2
|
||||
);
|
||||
m_menu->addChild(m_developerBtn);
|
||||
m_menu->addChild(m_creatorLabel);
|
||||
}
|
||||
|
||||
this->setupVersion(version);
|
||||
|
@ -96,7 +93,7 @@ void InstallListCell::setupVersion(std::variant<VersionInfo, ComparableVersionIn
|
|||
m_versionLabel->setScale(.2f);
|
||||
m_versionLabel->setPosition(
|
||||
m_titleLabel->getPositionX() + m_titleLabel->getScaledContentSize().width + 3.f +
|
||||
(m_developerBtn ? m_developerBtn->getScaledContentSize().width + 3.f : 0.f),
|
||||
(m_creatorLabel ? m_creatorLabel->getScaledContentSize().width + 3.f : 0.f),
|
||||
m_titleLabel->getPositionY() - 1.f
|
||||
);
|
||||
m_versionLabel->setColor({ 0, 255, 0 });
|
||||
|
@ -123,7 +120,7 @@ void InstallListCell::setupInfo(ModMetadata const& metadata, bool inactive) {
|
|||
}
|
||||
|
||||
void InstallListCell::onViewDev(CCObject*) {
|
||||
DevProfilePopup::create(getDeveloper())->show();
|
||||
// DevProfilePopup::create(getDeveloper(), m_layer)->show();
|
||||
}
|
||||
|
||||
bool InstallListCell::init(InstallListPopup* list, CCSize const& size) {
|
||||
|
|
|
@ -21,7 +21,7 @@ protected:
|
|||
float m_height;
|
||||
InstallListPopup* m_layer = nullptr;
|
||||
CCMenu* m_menu = nullptr;
|
||||
CCMenuItemSpriteExtra* m_developerBtn = nullptr;
|
||||
CCLabelBMFont* m_creatorLabel = nullptr;
|
||||
CCLabelBMFont* m_titleLabel = nullptr;
|
||||
CCLabelBMFont* m_versionLabel = nullptr;
|
||||
TagNode* m_tagLabel = nullptr;
|
||||
|
|
|
@ -113,7 +113,7 @@ CCArray* InstallListPopup::createCells(std::unordered_map<std::string, InstallLi
|
|||
|
||||
// installed
|
||||
// TODO: we should be able to select a different version even if its installed
|
||||
if (/*item.mod && !item.mod->isUninstalled()*/item.mod->getMetadata().getID() == "geode.loader") {
|
||||
if (item.mod && /*!item.mod->isUninstalled()*/item.mod->getMetadata().getID() == "geode.loader") {
|
||||
bottom.push_back(ModInstallListCell::create(item.mod, this, this->getCellSize()));
|
||||
for (auto const& dep : item.mod->getMetadata().getDependencies()) {
|
||||
queue.push(dep);
|
||||
|
|
|
@ -186,7 +186,7 @@ void ModListCell::updateCellLayout() {
|
|||
}
|
||||
|
||||
void ModListCell::onViewDev(CCObject*) {
|
||||
DevProfilePopup::create(this->getDeveloper())->show();
|
||||
DevProfilePopup::create(this->getDeveloper(), m_layer)->show();
|
||||
}
|
||||
|
||||
bool ModListCell::init(ModListLayer* list, CCSize const& size) {
|
||||
|
@ -233,7 +233,9 @@ void ModCell::onEnable(CCObject* sender) {
|
|||
else {
|
||||
tryOrAlert(m_mod->disable(), "Error disabling mod");
|
||||
}
|
||||
m_layer->reloadList();
|
||||
if (m_layer) {
|
||||
m_layer->reloadList();
|
||||
}
|
||||
}
|
||||
|
||||
void ModCell::onUnresolvedInfo(CCObject*) {
|
||||
|
|
|
@ -638,6 +638,10 @@ void ModListLayer::onExit(CCObject*) {
|
|||
);
|
||||
}
|
||||
|
||||
void ModListLayer::keyBackClicked() {
|
||||
this->onExit(nullptr);
|
||||
}
|
||||
|
||||
void ModListLayer::onReload(CCObject*) {
|
||||
this->reloadList();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -324,6 +324,11 @@ bool StringSettingNode::setup(StringSettingValue* setting, float width) {
|
|||
m_input = InputNode::create(width / 2 - 10.f, "Text", "chatFont.fnt");
|
||||
m_input->setPosition({ -(width / 2 - 70.f) / 2, .0f });
|
||||
m_input->setScale(.65f);
|
||||
|
||||
if (setting->castDefinition().filter.has_value()) {
|
||||
m_input->getInput()->setAllowedChars(setting->castDefinition().filter.value());
|
||||
}
|
||||
|
||||
m_input->getInput()->setDelegate(this);
|
||||
m_menu->addChild(m_input);
|
||||
|
||||
|
@ -350,16 +355,17 @@ void FileSettingNode::valueChanged(bool updateText) {
|
|||
}
|
||||
|
||||
void FileSettingNode::onPickFile(CCObject*) {
|
||||
if (auto path = file::pickFile(
|
||||
file::pickFile(
|
||||
file::PickMode::OpenFile,
|
||||
{
|
||||
dirs::getGameDir(),
|
||||
setting()->castDefinition().controls.filters
|
||||
},
|
||||
[&](auto path) {
|
||||
m_uncommittedValue = path;
|
||||
this->valueChanged(true);
|
||||
}
|
||||
)) {
|
||||
m_uncommittedValue = path.unwrap();
|
||||
this->valueChanged(true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
bool FileSettingNode::setup(FileSettingValue* setting, float width) {
|
||||
|
|
|
@ -109,8 +109,8 @@ bool MDTextArea::init(std::string const& str, CCSize const& size) {
|
|||
if (!CCLayer::init()) return false;
|
||||
|
||||
m_text = str;
|
||||
m_size = size;
|
||||
this->setContentSize(size);
|
||||
m_size = size - CCSize { 15.f, 0.f };
|
||||
this->setContentSize(m_size);
|
||||
m_renderer = TextRenderer::create();
|
||||
CC_SAFE_RETAIN(m_renderer);
|
||||
|
||||
|
@ -118,8 +118,8 @@ bool MDTextArea::init(std::string const& str, CCSize const& size) {
|
|||
m_bgSprite->setScale(.5f);
|
||||
m_bgSprite->setColor({ 0, 0, 0 });
|
||||
m_bgSprite->setOpacity(75);
|
||||
m_bgSprite->setContentSize(size * 2 + CCSize { 25.f, 25.f });
|
||||
m_bgSprite->setPosition(size / 2);
|
||||
m_bgSprite->setContentSize(size * 2);
|
||||
m_bgSprite->setPosition(m_size / 2);
|
||||
this->addChild(m_bgSprite);
|
||||
|
||||
m_scrollLayer = ScrollLayer::create({ 0, 0, m_size.width, m_size.height }, true);
|
||||
|
@ -695,7 +695,16 @@ void MDTextArea::updateLabel() {
|
|||
|
||||
m_renderer->end();
|
||||
|
||||
m_scrollLayer->m_contentLayer->setContentSize(m_content->getContentSize());
|
||||
if (m_content->getContentSize().height > m_size.height) {
|
||||
// Generate bottom padding
|
||||
m_scrollLayer->m_contentLayer->setContentSize(m_content->getContentSize() + CCSize { 0.f, 12.5 });
|
||||
m_content->setPositionY(10.f);
|
||||
} else {
|
||||
m_scrollLayer->m_contentLayer->setContentSize(m_content->getContentSize());
|
||||
m_content->setPositionY(-2.5f);
|
||||
}
|
||||
|
||||
|
||||
m_scrollLayer->moveToTop();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
300
loader/src/ui/nodes/TextArea.cpp
Normal file
300
loader/src/ui/nodes/TextArea.cpp
Normal file
|
@ -0,0 +1,300 @@
|
|||
#include <Geode/ui/TextArea.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
SimpleTextArea* SimpleTextArea::create(const std::string& text, const std::string& font, const float scale) {
|
||||
return SimpleTextArea::create(font, text, scale, CCDirector::sharedDirector()->getWinSize().width / 2, false);
|
||||
}
|
||||
|
||||
SimpleTextArea* SimpleTextArea::create(const std::string& text, const std::string& font, const float scale, const float width) {
|
||||
return SimpleTextArea::create(font, text, scale, width, true);
|
||||
}
|
||||
|
||||
SimpleTextArea* SimpleTextArea::create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth) {
|
||||
SimpleTextArea* instance = new SimpleTextArea(font, text, scale, width, artificialWidth);
|
||||
|
||||
if (instance && instance->init()) {
|
||||
instance->autorelease();
|
||||
|
||||
return instance;
|
||||
} else {
|
||||
CC_SAFE_DELETE(instance);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTextArea::SimpleTextArea(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth) {
|
||||
m_font = font;
|
||||
m_text = text;
|
||||
m_maxLines = 0;
|
||||
m_scale = scale;
|
||||
m_linePadding = 0;
|
||||
m_color = { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
m_alignment = kCCTextAlignmentLeft;
|
||||
m_wrappingMode = WORD_WRAP;
|
||||
m_artificialWidth = artificialWidth;
|
||||
m_container = CCMenu::create();
|
||||
m_shouldUpdate = true;
|
||||
|
||||
this->setAnchorPoint({ 0.5f, 0.5f });
|
||||
m_container->setPosition({ 0, 0 });
|
||||
m_container->setAnchorPoint({ 0, 1 });
|
||||
m_container->setContentSize({ width, 0 });
|
||||
|
||||
this->addChild(m_container);
|
||||
}
|
||||
|
||||
void SimpleTextArea::setFont(const std::string& font) {
|
||||
m_font = font;
|
||||
m_shouldUpdate = true;
|
||||
}
|
||||
|
||||
std::string SimpleTextArea::getFont() {
|
||||
return m_font;
|
||||
}
|
||||
|
||||
void SimpleTextArea::setColor(const ccColor4B& color) {
|
||||
m_color = color;
|
||||
m_shouldUpdate = true;
|
||||
}
|
||||
|
||||
ccColor4B SimpleTextArea::getColor() {
|
||||
return m_color;
|
||||
}
|
||||
|
||||
void SimpleTextArea::setAlignment(const CCTextAlignment alignment) {
|
||||
m_alignment = alignment;
|
||||
m_shouldUpdate = true;
|
||||
}
|
||||
|
||||
CCTextAlignment SimpleTextArea::getAlignment() {
|
||||
return m_alignment;
|
||||
}
|
||||
|
||||
void SimpleTextArea::setWrappingMode(const WrappingMode mode) {
|
||||
m_wrappingMode = mode;
|
||||
m_shouldUpdate = true;
|
||||
}
|
||||
|
||||
WrappingMode SimpleTextArea::getWrappingMode() {
|
||||
return m_wrappingMode;
|
||||
}
|
||||
|
||||
void SimpleTextArea::setText(const std::string& text) {
|
||||
m_text = text;
|
||||
m_shouldUpdate = true;
|
||||
}
|
||||
|
||||
std::string SimpleTextArea::getText() {
|
||||
return m_text;
|
||||
}
|
||||
|
||||
void SimpleTextArea::setMaxLines(const size_t maxLines) {
|
||||
m_maxLines = maxLines;
|
||||
m_shouldUpdate = true;
|
||||
}
|
||||
|
||||
size_t SimpleTextArea::getMaxLines() {
|
||||
return m_maxLines;
|
||||
}
|
||||
|
||||
void SimpleTextArea::setWidth(const float width) {
|
||||
m_artificialWidth = true;
|
||||
m_shouldUpdate = true;
|
||||
|
||||
this->setContentSize({ width, this->getContentSize().height });
|
||||
m_container->setContentSize(this->getContentSize());
|
||||
}
|
||||
|
||||
float SimpleTextArea::getWidth() {
|
||||
return m_container->getContentSize().width;
|
||||
}
|
||||
|
||||
void SimpleTextArea::setScale(const float scale) {
|
||||
m_scale = scale;
|
||||
m_shouldUpdate = true;
|
||||
}
|
||||
|
||||
float SimpleTextArea::getScale() {
|
||||
return m_scale;
|
||||
}
|
||||
|
||||
void SimpleTextArea::setLinePadding(const float padding) {
|
||||
m_linePadding = padding;
|
||||
m_shouldUpdate = true;
|
||||
}
|
||||
|
||||
float SimpleTextArea::getLinePadding() {
|
||||
return m_linePadding;
|
||||
}
|
||||
|
||||
std::vector<CCLabelBMFont*> SimpleTextArea::getLines() {
|
||||
return m_lines;
|
||||
}
|
||||
|
||||
float SimpleTextArea::getHeight() {
|
||||
return m_container->getContentSize().height;
|
||||
}
|
||||
|
||||
float SimpleTextArea::getLineHeight() {
|
||||
return m_lineHeight;
|
||||
}
|
||||
|
||||
CCLabelBMFont* SimpleTextArea::createLabel(const std::string& text, const float top) {
|
||||
CCLabelBMFont* label = CCLabelBMFont::create(text.c_str(), m_font.c_str());
|
||||
|
||||
label->setScale(m_scale);
|
||||
label->setPosition({ 0, top });
|
||||
label->setColor({ m_color.r, m_color.g, m_color.b });
|
||||
label->setOpacity(m_color.a);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
float SimpleTextArea::calculateOffset(CCLabelBMFont* label) {
|
||||
return m_linePadding + label->getContentSize().height * m_scale;
|
||||
}
|
||||
|
||||
void SimpleTextArea::charIteration(const std::function<CCLabelBMFont*(CCLabelBMFont* line, const char c, const float top)>& overflowHandling) {
|
||||
float top = 0;
|
||||
CCLabelBMFont* line = this->createLabel("", top);
|
||||
m_lines = { line };
|
||||
|
||||
for (const char c : m_text) {
|
||||
if (m_maxLines && m_lines.size() > m_maxLines) {
|
||||
CCLabelBMFont* last = m_lines.at(m_maxLines - 1);
|
||||
const std::string text = last->getString();
|
||||
|
||||
m_lines.pop_back();
|
||||
last->setString(text.substr(0, text.size() - 3).append("...").c_str());
|
||||
|
||||
break;
|
||||
} else if (c == '\n') {
|
||||
m_lines.push_back(line = this->createLabel("", top -= this->calculateOffset(line)));
|
||||
} else if (m_artificialWidth && line->getContentSize().width * m_scale >= this->getWidth()) {
|
||||
m_lines.push_back(line = overflowHandling(line, c, top -= this->calculateOffset(line)));
|
||||
} else {
|
||||
line->setString((std::string(line->getString()) + c).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleTextArea::updateLinesNoWrap() {
|
||||
std::stringstream stream(m_text);
|
||||
std::string part;
|
||||
float top = 0;
|
||||
|
||||
while (std::getline(stream, part)) {
|
||||
if (m_maxLines && m_lines.size() >= m_maxLines) {
|
||||
CCLabelBMFont* last = m_lines.at(m_maxLines - 1);
|
||||
const std::string text = last->getString();
|
||||
|
||||
last->setString(text.substr(0, text.size() - 3).append("...").c_str());
|
||||
|
||||
break;
|
||||
} else {
|
||||
CCLabelBMFont* line = this->createLabel(part, 0);
|
||||
|
||||
top -= this->calculateOffset(line);
|
||||
|
||||
m_lines.push_back(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleTextArea::updateLinesWordWrap() {
|
||||
this->charIteration([this](CCLabelBMFont* line, const char c, const float top) {
|
||||
static std::string delimiters(" `~!@#$%^&*()-_=+[{}];:'\",<.>/?\\|");
|
||||
|
||||
if (delimiters.find(c) == std::string_view::npos) {
|
||||
const std::string text = line->getString();
|
||||
const size_t position = text.find_last_of(delimiters) + 1;
|
||||
|
||||
line->setString(text.substr(0, position).c_str());
|
||||
|
||||
return this->createLabel(text.substr(position) + c, top);
|
||||
} else {
|
||||
return this->createLabel(std::string(c, c != ' '), top);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SimpleTextArea::updateLinesCutoffWrap() {
|
||||
this->charIteration([this](CCLabelBMFont* line, const char c, const float top) {
|
||||
const std::string text = line->getString();
|
||||
const char back = text.back();
|
||||
const bool lastIsSpace = back == ' ';
|
||||
CCLabelBMFont* newLine = this->createLabel(std::string(!lastIsSpace, back).append(std::string(c != ' ', c)), top);
|
||||
|
||||
if (!lastIsSpace) {
|
||||
if (text[text.size() - 2] == ' ') {
|
||||
line->setString(text.substr(0, text.size() - 1).c_str());
|
||||
} else {
|
||||
line->setString((text.substr(0, text.size() - 1) + '-').c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return newLine;
|
||||
});
|
||||
}
|
||||
|
||||
void SimpleTextArea::updateContainer() {
|
||||
switch (m_wrappingMode) {
|
||||
case NO_WRAP: {
|
||||
this->updateLinesNoWrap();
|
||||
} break;
|
||||
case WORD_WRAP: {
|
||||
this->updateLinesWordWrap();
|
||||
} break;
|
||||
case CUTOFF_WRAP: {
|
||||
this->updateLinesCutoffWrap();
|
||||
} break;
|
||||
}
|
||||
|
||||
const size_t lineCount = m_lines.size();
|
||||
const float width = this->getWidth();
|
||||
|
||||
if (lineCount > 0) {
|
||||
m_lineHeight = m_lines.back()->getContentSize().height * m_scale;
|
||||
} else {
|
||||
m_lineHeight = 0;
|
||||
}
|
||||
|
||||
float height = m_lineHeight * lineCount + m_linePadding * (lineCount - 1);
|
||||
|
||||
this->setContentSize({ width, height });
|
||||
m_container->setContentSize(this->getContentSize());
|
||||
m_container->removeAllChildren();
|
||||
|
||||
for (CCLabelBMFont* line : m_lines) {
|
||||
const float y = height + line->getPositionY();
|
||||
|
||||
switch (m_alignment) {
|
||||
case kCCTextAlignmentLeft: {
|
||||
line->setAnchorPoint({ 0, 1 });
|
||||
line->setPosition({ 0, y });
|
||||
} break;
|
||||
case kCCTextAlignmentCenter: {
|
||||
line->setAnchorPoint({ 0, 1 });
|
||||
line->setPosition({ width / 2, y });
|
||||
} break;
|
||||
case kCCTextAlignmentRight: {
|
||||
line->setAnchorPoint({ 1, 1 });
|
||||
line->setPosition({ width, y });
|
||||
} break;
|
||||
}
|
||||
|
||||
m_container->addChild(line);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleTextArea::draw() {
|
||||
CCNode::draw();
|
||||
|
||||
if (m_shouldUpdate) {
|
||||
this->updateContainer();
|
||||
|
||||
m_shouldUpdate = false;
|
||||
}
|
||||
}
|
|
@ -68,6 +68,19 @@ Addresser::MultipleInheritance* Addresser::instance() {
|
|||
|
||||
intptr_t Addresser::followThunkFunction(intptr_t address) {
|
||||
#ifdef GEODE_IS_WINDOWS
|
||||
// if theres a jmp at the start
|
||||
if (*reinterpret_cast<uint8_t*>(address) == 0xE9) {
|
||||
auto relative = *reinterpret_cast<uint32_t*>(address + 1);
|
||||
auto newAddress = address + relative + 5;
|
||||
// and if that jmp leads to a jmp dword ptr, only then follow it,
|
||||
// because otherwise its just a hook.
|
||||
// For some reason this [jmp -> jmp dword ptr] chain happens with a few cocos functions,
|
||||
// but not all. For example: cocos2d::ZipUtils::decompressString2
|
||||
if (*reinterpret_cast<uint8_t*>(newAddress) == 0xFF && *reinterpret_cast<uint8_t*>(newAddress + 1) == 0x25) {
|
||||
address = newAddress;
|
||||
}
|
||||
}
|
||||
|
||||
// check if first instruction is a jmp dword ptr [....], i.e. if the func is a thunk
|
||||
if (*reinterpret_cast<uint8_t*>(address) == 0xFF && *reinterpret_cast<uint8_t*>(address + 1) == 0x25) {
|
||||
// read where the jmp reads from
|
||||
|
|
|
@ -21,48 +21,51 @@ using namespace geode::prelude;
|
|||
using namespace geode::utils::file;
|
||||
|
||||
Result<std::string> utils::file::readString(ghc::filesystem::path const& path) {
|
||||
if (!ghc::filesystem::exists(path))
|
||||
return Err("File does not exist");
|
||||
|
||||
#if _WIN32
|
||||
std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
|
||||
#else
|
||||
std::ifstream in(path.string(), std::ios::in | std::ios::binary);
|
||||
#endif
|
||||
if (in) {
|
||||
std::string contents;
|
||||
in.seekg(0, std::ios::end);
|
||||
contents.resize((const size_t)in.tellg());
|
||||
in.seekg(0, std::ios::beg);
|
||||
in.read(&contents[0], contents.size());
|
||||
in.close();
|
||||
return Ok(contents);
|
||||
}
|
||||
return Err("Unable to open file");
|
||||
if (!in)
|
||||
return Err("Unable to open file");
|
||||
|
||||
std::string contents;
|
||||
in.seekg(0, std::ios::end);
|
||||
contents.resize((const size_t)in.tellg());
|
||||
in.seekg(0, std::ios::beg);
|
||||
in.read(&contents[0], contents.size());
|
||||
in.close();
|
||||
return Ok(contents);
|
||||
}
|
||||
|
||||
Result<json::Value> utils::file::readJson(ghc::filesystem::path const& path) {
|
||||
|
||||
auto str = utils::file::readString(path);
|
||||
|
||||
if (str) {
|
||||
try {
|
||||
return Ok(json::parse(str.value()));
|
||||
} catch(std::exception const& e) {
|
||||
return Err("Unable to parse JSON: " + std::string(e.what()));
|
||||
}
|
||||
} else {
|
||||
return Err("Unable to open file");
|
||||
if (!str)
|
||||
return Err(str.unwrapErr());
|
||||
try {
|
||||
return Ok(json::parse(str.value()));
|
||||
}
|
||||
catch(std::exception const& e) {
|
||||
return Err("Unable to parse JSON: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
Result<ByteVector> utils::file::readBinary(ghc::filesystem::path const& path) {
|
||||
if (!ghc::filesystem::exists(path))
|
||||
return Err("File does not exist");
|
||||
|
||||
#if _WIN32
|
||||
std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
|
||||
#else
|
||||
std::ifstream in(path.string(), std::ios::in | std::ios::binary);
|
||||
#endif
|
||||
if (in) {
|
||||
return Ok(ByteVector(std::istreambuf_iterator<char>(in), {}));
|
||||
}
|
||||
return Err("Unable to open file");
|
||||
if (!in)
|
||||
return Err("Unable to open file");
|
||||
|
||||
return Ok(ByteVector(std::istreambuf_iterator<char>(in), {}));
|
||||
}
|
||||
|
||||
Result<> utils::file::writeString(ghc::filesystem::path const& path, std::string const& data) {
|
||||
|
@ -72,14 +75,14 @@ Result<> utils::file::writeString(ghc::filesystem::path const& path, std::string
|
|||
#else
|
||||
file.open(path.string());
|
||||
#endif
|
||||
if (file.is_open()) {
|
||||
file << data;
|
||||
if (!file.is_open()) {
|
||||
file.close();
|
||||
|
||||
return Ok();
|
||||
return Err("Unable to open file");
|
||||
}
|
||||
|
||||
file << data;
|
||||
file.close();
|
||||
return Err("Unable to open file");
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> utils::file::writeBinary(ghc::filesystem::path const& path, ByteVector const& data) {
|
||||
|
@ -89,14 +92,14 @@ Result<> utils::file::writeBinary(ghc::filesystem::path const& path, ByteVector
|
|||
#else
|
||||
file.open(path.string(), std::ios::out | std::ios::binary);
|
||||
#endif
|
||||
if (file.is_open()) {
|
||||
file.write(reinterpret_cast<char const*>(data.data()), data.size());
|
||||
if (!file.is_open()) {
|
||||
file.close();
|
||||
|
||||
return Ok();
|
||||
return Err("Unable to open file");
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<char const*>(data.data()), data.size());
|
||||
file.close();
|
||||
return Err("Unable to open file");
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> utils::file::createDirectory(ghc::filesystem::path const& path) {
|
||||
|
|
|
@ -79,6 +79,7 @@ Result<ByteVector> web::fetchBytes(std::string const& url) {
|
|||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBytes);
|
||||
auto res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
|
@ -118,6 +119,7 @@ Result<std::string> web::fetch(std::string const& url) {
|
|||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeString);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
auto res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
curl_easy_cleanup(curl);
|
||||
|
@ -185,7 +187,7 @@ static std::unordered_map<std::string, SentAsyncWebRequestHandle> RUNNING_REQUES
|
|||
static std::mutex RUNNING_REQUESTS_MUTEX;
|
||||
|
||||
SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const& req, std::string const& id) :
|
||||
m_id(id), m_url(req.m_url), m_target(req.m_target), m_extra(req.extra()), m_httpHeaders(req.m_httpHeaders) {
|
||||
m_self(self), m_id(id), m_url(req.m_url), m_target(req.m_target), m_extra(req.extra()), m_httpHeaders(req.m_httpHeaders) {
|
||||
|
||||
#define AWAIT_RESUME() \
|
||||
{\
|
||||
|
@ -269,7 +271,7 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
|||
// Follow redirects
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
// Fail if response code is 4XX or 5XX
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0L); // we will handle http errors manually
|
||||
|
||||
// Headers end
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
|
@ -294,10 +296,13 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
|||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
Loader::get()->queueInMainThread([self = data->self, now, total]() {
|
||||
std::lock_guard _(self->m_mutex);
|
||||
std::unique_lock<std::mutex> l(self->m_mutex);
|
||||
for (auto& prog : self->m_progresses) {
|
||||
l.unlock();
|
||||
prog(*self->m_self, now, total);
|
||||
l.lock();
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
|
@ -305,12 +310,17 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
|||
);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &data);
|
||||
auto res = curl_easy_perform(curl);
|
||||
long code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
if (res != CURLE_OK) {
|
||||
long code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
curl_easy_cleanup(curl);
|
||||
return this->error("Fetch failed: " + std::string(curl_easy_strerror(res)), code);
|
||||
}
|
||||
if (code >= 400 && code < 600) {
|
||||
std::string response_str(ret.begin(), ret.end());
|
||||
curl_easy_cleanup(curl);
|
||||
return this->error(response_str, code);
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
AWAIT_RESUME();
|
||||
|
@ -320,9 +330,11 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
|||
m_finished = true;
|
||||
|
||||
Loader::get()->queueInMainThread([this, ret]() {
|
||||
std::lock_guard _(m_mutex);
|
||||
std::unique_lock<std::mutex> l(m_mutex);
|
||||
for (auto& then : m_thens) {
|
||||
l.unlock();
|
||||
then(*m_self, ret);
|
||||
l.lock();
|
||||
}
|
||||
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
||||
RUNNING_REQUESTS.erase(m_id);
|
||||
|
@ -347,9 +359,11 @@ void SentAsyncWebRequest::Impl::doCancel() {
|
|||
}
|
||||
|
||||
Loader::get()->queueInMainThread([this]() {
|
||||
std::lock_guard _(m_mutex);
|
||||
std::unique_lock<std::mutex> l(m_mutex);
|
||||
for (auto& canc : m_cancelleds) {
|
||||
l.unlock();
|
||||
canc(*m_self);
|
||||
l.lock();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -385,9 +399,11 @@ void SentAsyncWebRequest::Impl::error(std::string const& error, int code) {
|
|||
});
|
||||
Loader::get()->queueInMainThread([this, error, code]() {
|
||||
{
|
||||
std::lock_guard _(m_mutex);
|
||||
std::unique_lock<std::mutex> l(m_mutex);
|
||||
for (auto& expect : m_expects) {
|
||||
l.unlock();
|
||||
expect(error, code);
|
||||
l.lock();
|
||||
}
|
||||
}
|
||||
std::lock_guard _(RUNNING_REQUESTS_MUTEX);
|
||||
|
|
39
loader/test/members/Android.cpp
Normal file
39
loader/test/members/Android.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#include "Common.hpp"
|
||||
|
||||
#ifdef GEODE_IS_ANDROID
|
||||
|
||||
// Add known android struct members here
|
||||
|
||||
// needed classes are ones in the ids folder and some generic ones (i think they are already done though so only ids)
|
||||
|
||||
GEODE_MEMBER_CHECK(GameManager, m_playLayer, 0x138);
|
||||
GEODE_MEMBER_CHECK(GameManager, m_levelEditorLayer, 0x13c);
|
||||
GEODE_MEMBER_CHECK(GameManager, m_canGetLevelSaveData, 0x28c);
|
||||
|
||||
static_assert(sizeof(GJBaseGameLayer) == 0x2cc);
|
||||
|
||||
GEODE_MEMBER_CHECK(PlayLayer, unknown4e8, 0x2e8);
|
||||
GEODE_MEMBER_CHECK(PlayLayer, m_endPortal, 0x324);
|
||||
GEODE_MEMBER_CHECK(PlayLayer, m_bottomGround, 0x37c);
|
||||
GEODE_MEMBER_CHECK(PlayLayer, m_topGround, 0x380);
|
||||
GEODE_MEMBER_CHECK(PlayLayer, m_level, 0x470);
|
||||
GEODE_MEMBER_CHECK(PlayLayer, m_shouldTryToKick, 0x4e0);
|
||||
|
||||
static_assert(sizeof(GameObject) == 0x42c);
|
||||
|
||||
GEODE_MEMBER_CHECK(GameStatsManager, m_dailyChests, 0x110);
|
||||
GEODE_MEMBER_CHECK(GameStatsManager, m_completedLevels, 0x164);
|
||||
|
||||
GEODE_MEMBER_CHECK(DailyLevelPage, m_weekly, 0x1ed);
|
||||
|
||||
GEODE_MEMBER_CHECK(TeleportPortalObject, m_orangePortal, 0x430);
|
||||
|
||||
GEODE_MEMBER_CHECK(EditorUI, m_rotationControl, 0x16c);
|
||||
GEODE_MEMBER_CHECK(EditorUI, m_updateTimeMarkers, 0x1a4);
|
||||
GEODE_MEMBER_CHECK(EditorUI, m_selectedObjects, 0x1bc);
|
||||
GEODE_MEMBER_CHECK(EditorUI, m_selectedObject, 0x2c4);
|
||||
|
||||
GEODE_MEMBER_CHECK(MoreSearchLayer, m_enterSongID, 0x1E4);
|
||||
GEODE_MEMBER_CHECK(MoreSearchLayer, m_songLeftBtn, 0x1D4);
|
||||
|
||||
#endif
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue