├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .nojekyll ├── CNAME ├── LICENSE ├── README.md ├── assets ├── BetterEdit_GDShare_bug.png ├── BrowseLocalFilesForGD.png ├── DevTools_layoutAttributes.png ├── DevTools_layouts.png ├── GeodeFilesExample.png ├── MenuLayer_init.png ├── MenuLayer_vtable.png ├── Misc_iOS_webserver-steps.png ├── Misc_iOS_webserver-ui1.png ├── Misc_iOS_webserver-ui2.png ├── Set_Env_Var.png ├── clion_addconfiguration.png ├── clion_buildmod.png ├── clion_openprojectwizard.png ├── clion_openprojectwizardsetup.png ├── clion_reloadcmake.png ├── clion_toolchainlinux.png ├── clion_toolchainllvm.png ├── clion_toolchainmsvc.png ├── geode-circle.png ├── geode_log_levels.png ├── geode_platform_console.png ├── handbook │ ├── vol1 │ │ ├── CommentCell_dissected.png │ │ ├── DevTools_MenuLayer.png │ │ ├── DevTools_ProfilePage.png │ │ ├── gd_in_notepad.png │ │ └── hello_world.png │ └── vol2 │ │ ├── Android_analyz.png │ │ ├── CCMenuItemSpriteExtra_create.png │ │ ├── CCMenuItemSpriteExtra_create_args.png │ │ ├── CCMenuItemSpriteExtra_create_correct.png │ │ ├── FLAlertLayer_ctor.png │ │ ├── FLAlertLayer_init.png │ │ ├── LevelSettingsLayer_androir.png │ │ ├── LevelSettingsLayer_base_init_call.png │ │ ├── LevelSettingsLayer_create.png │ │ ├── LevelSettingsLayer_create_androir.png │ │ ├── LevelSettingsLayer_create_call_to_init_androir.png │ │ ├── LevelSettingsLayer_ctor.png │ │ ├── LevelSettingsLayer_ctor_renamed.png │ │ ├── LevelSettingsLayer_ctor_xrefs.png │ │ ├── LevelSettingsLayer_in_tree.png │ │ ├── LevelSettingsLayer_init_rename.png │ │ ├── LevelSettingsLayer_init_retyped.png │ │ ├── LevelSettingsLayer_init_sig.png │ │ ├── LevelSettingsLayer_vtable_ctor_xref.png │ │ ├── LevelSettingsObject_init.png │ │ ├── LevelSettingsObject_invalid.png │ │ ├── LevelStats.png │ │ ├── MenuLayer_in_ghidra.png │ │ ├── MenuLayer_init.png │ │ ├── MenuLayer_init_code.png │ │ ├── MenuLayer_init_in_list.png │ │ ├── SEL_MenuHandler_def.png │ │ ├── add_android_to_ghidra.png │ │ ├── androir_fixed_init_ret_type.png │ │ ├── callback.png │ │ ├── classes_in_symbol_tree.png │ │ ├── compare_LSL_inits.png │ │ ├── correct_MenuLayer_init_sig.png │ │ ├── fixed_button_params.png │ │ ├── ghidra_start.png │ │ ├── ghidra_string_search.png │ │ ├── ghidra_typedef.png │ │ ├── ghidra_window.png │ │ ├── infoiconuse.png │ │ ├── infospritename.png │ │ └── rename_MenuLayer_init.png ├── hook_priority.drawio.svg ├── hook_priority.png ├── include_path_error.png ├── positioning_contentsize1.png ├── settings │ ├── bool.png │ ├── custom-button.png │ ├── custom-enum.png │ ├── enable-if.png │ ├── file.png │ ├── float.png │ ├── folder.png │ ├── int.png │ ├── popup.png │ ├── rgb.png │ ├── rgba.png │ ├── string-one-of.png │ ├── string.png │ └── title.png ├── vs_example_config.png ├── vs_manage_configurations.png ├── win_compilers.png ├── win_editconfig.png ├── win_relwithdebinfo.png └── win_usecmake.png ├── cpp ├── declarations.md ├── index.md └── tips.md ├── flash-template ├── content.css ├── default.css ├── nav.css └── themes.css ├── getting-started ├── cpp-stuff.md ├── create-mod.md ├── geode-cli.md ├── ide-setup.md ├── index.md ├── prerequisites.md ├── sdk.md └── what-next.md ├── handbook ├── chap0.md ├── index.md ├── vol1 │ ├── chap1_1.md │ ├── chap1_2.md │ ├── chap1_3.md │ ├── chap1_4.md │ ├── chap1_5.md │ ├── chap1_6.md │ ├── chap1_7.md │ └── index.md ├── vol2 │ ├── chap2_1.md │ ├── chap2_2.md │ ├── chap2_3.md │ ├── chap2_4.md │ ├── chap2_5.md │ ├── chap2_6.md │ └── index.md └── vol4 │ ├── chap4_1.md │ └── index.md ├── index.md ├── misc ├── index.md └── ios.md ├── mods ├── configuring.md ├── dependencies.md ├── guidelines.md ├── index.md ├── md-files.md ├── publishing.md ├── resources.md ├── savedata.md ├── settings-old.md └── settings.md ├── source ├── index.md └── styling.md └── tutorials ├── buttons.md ├── casting.md ├── dictionary.md ├── events.md ├── fetch.md ├── fields.md ├── hookpriority.md ├── image-loading.md ├── index.md ├── layouts.md ├── logging.md ├── manualhooks.md ├── memory.md ├── migrate-v4.md ├── migrating.md ├── modify-geode.md ├── modify.md ├── nodetree.md ├── popup.md ├── positioning.md ├── tasks.md ├── touchpriority.md └── utils.md /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Create docs 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | 7 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | # Allow one concurrent deployment 14 | concurrency: 15 | group: "pages" 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | deploy: 20 | name: Docs 21 | runs-on: windows-2019 22 | steps: 23 | 24 | # Clone Geode repo 25 | - name: Checkout Geode repo 26 | uses: actions/checkout@v4 27 | with: 28 | repository: 'geode-sdk/geode' 29 | path: 'geode' 30 | 31 | - name: Checkout docs repo 32 | uses: actions/checkout@v4 33 | with: 34 | path: 'geode/docs' 35 | 36 | - name: Setup Ninja 37 | uses: seanmiddleditch/gha-setup-ninja@master 38 | 39 | - name: Download Flash 40 | uses: robinraju/release-downloader@v1.5 41 | with: 42 | repository: geode-sdk/flash 43 | latest: true 44 | fileName: "flash.exe" 45 | tarBall: false 46 | zipBall: false 47 | out-file-path: "" 48 | 49 | - name: Create dist directory 50 | run: | 51 | mkdir dist 52 | 53 | - name: Build docs with Flash 54 | run: | 55 | cd dist 56 | ./../flash.exe -i ../geode -o . 57 | 58 | - name: Upload artifact 59 | uses: actions/upload-pages-artifact@v3 60 | with: 61 | path: './dist' 62 | 63 | - name: Deploy to GitHub Pages 64 | id: deployment 65 | uses: actions/deploy-pages@v4 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | dist/ -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/.nojekyll -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | docs.geode-sdk.org -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Geode SDK developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Geode Docs 2 | 3 | ### [Visit the Docs site](https://docs.geode-sdk.org) 4 | 5 | This is the source code for Geode's docs, containing all the hand-written tutorials. 6 | 7 | Class & function documentation is built automatically from [the Geode source code](https://github.com/geode-sdk/geode). 8 | 9 | ## Building 10 | 11 | The docs are built using [Flash](https://github.com/hjfod/flash). To build the docs, you need Flash, along with [CMake](https://cmake.org/install/) and [Clang](https://clang.llvm.org/). 12 | 13 | To build the docs, you first need to clone Geode, and then clone the docs inside the Geode root, for a folder structure like this: 14 | 15 | ``` 16 | geode/ 17 | docs/ 18 | 19 | 20 | ``` 21 | 22 | For example, you can do this with the following commands: 23 | 24 | ``` 25 | git clone https://github.com/geode-sdk/geode 26 | cd geode 27 | git clone https://github.com/geode-sdk/docs 28 | ``` 29 | 30 | Alternatively, you can symlink your local copy of the docs folder to your local copy of the Geode folder. 31 | 32 | After building Flash from source using Cargo or installing the latest release, you can build the docs with the following command: 33 | 34 | ``` 35 | flash -i -o --overwrite 36 | ``` 37 | 38 | Afterwards, start up a local HTTP server in the folder where you ran Flash. 39 | 40 | You should run Flash in the directory you want to build the docs in and use `-o .`, or run it in the parent directory and do `-o `. 41 | -------------------------------------------------------------------------------- /assets/BetterEdit_GDShare_bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/BetterEdit_GDShare_bug.png -------------------------------------------------------------------------------- /assets/BrowseLocalFilesForGD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/BrowseLocalFilesForGD.png -------------------------------------------------------------------------------- /assets/DevTools_layoutAttributes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/DevTools_layoutAttributes.png -------------------------------------------------------------------------------- /assets/DevTools_layouts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/DevTools_layouts.png -------------------------------------------------------------------------------- /assets/GeodeFilesExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/GeodeFilesExample.png -------------------------------------------------------------------------------- /assets/MenuLayer_init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/MenuLayer_init.png -------------------------------------------------------------------------------- /assets/MenuLayer_vtable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/MenuLayer_vtable.png -------------------------------------------------------------------------------- /assets/Misc_iOS_webserver-steps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/Misc_iOS_webserver-steps.png -------------------------------------------------------------------------------- /assets/Misc_iOS_webserver-ui1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/Misc_iOS_webserver-ui1.png -------------------------------------------------------------------------------- /assets/Misc_iOS_webserver-ui2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/Misc_iOS_webserver-ui2.png -------------------------------------------------------------------------------- /assets/Set_Env_Var.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/Set_Env_Var.png -------------------------------------------------------------------------------- /assets/clion_addconfiguration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/clion_addconfiguration.png -------------------------------------------------------------------------------- /assets/clion_buildmod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/clion_buildmod.png -------------------------------------------------------------------------------- /assets/clion_openprojectwizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/clion_openprojectwizard.png -------------------------------------------------------------------------------- /assets/clion_openprojectwizardsetup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/clion_openprojectwizardsetup.png -------------------------------------------------------------------------------- /assets/clion_reloadcmake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/clion_reloadcmake.png -------------------------------------------------------------------------------- /assets/clion_toolchainlinux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/clion_toolchainlinux.png -------------------------------------------------------------------------------- /assets/clion_toolchainllvm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/clion_toolchainllvm.png -------------------------------------------------------------------------------- /assets/clion_toolchainmsvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/clion_toolchainmsvc.png -------------------------------------------------------------------------------- /assets/geode-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/geode-circle.png -------------------------------------------------------------------------------- /assets/geode_log_levels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/geode_log_levels.png -------------------------------------------------------------------------------- /assets/geode_platform_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/geode_platform_console.png -------------------------------------------------------------------------------- /assets/handbook/vol1/CommentCell_dissected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol1/CommentCell_dissected.png -------------------------------------------------------------------------------- /assets/handbook/vol1/DevTools_MenuLayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol1/DevTools_MenuLayer.png -------------------------------------------------------------------------------- /assets/handbook/vol1/DevTools_ProfilePage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol1/DevTools_ProfilePage.png -------------------------------------------------------------------------------- /assets/handbook/vol1/gd_in_notepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol1/gd_in_notepad.png -------------------------------------------------------------------------------- /assets/handbook/vol1/hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol1/hello_world.png -------------------------------------------------------------------------------- /assets/handbook/vol2/Android_analyz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/Android_analyz.png -------------------------------------------------------------------------------- /assets/handbook/vol2/CCMenuItemSpriteExtra_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/CCMenuItemSpriteExtra_create.png -------------------------------------------------------------------------------- /assets/handbook/vol2/CCMenuItemSpriteExtra_create_args.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/CCMenuItemSpriteExtra_create_args.png -------------------------------------------------------------------------------- /assets/handbook/vol2/CCMenuItemSpriteExtra_create_correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/CCMenuItemSpriteExtra_create_correct.png -------------------------------------------------------------------------------- /assets/handbook/vol2/FLAlertLayer_ctor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/FLAlertLayer_ctor.png -------------------------------------------------------------------------------- /assets/handbook/vol2/FLAlertLayer_init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/FLAlertLayer_init.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_androir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_androir.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_base_init_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_base_init_call.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_create.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_create_androir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_create_androir.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_create_call_to_init_androir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_create_call_to_init_androir.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_ctor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_ctor.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_ctor_renamed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_ctor_renamed.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_ctor_xrefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_ctor_xrefs.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_in_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_in_tree.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_init_rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_init_rename.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_init_retyped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_init_retyped.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_init_sig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_init_sig.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsLayer_vtable_ctor_xref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsLayer_vtable_ctor_xref.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsObject_init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsObject_init.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelSettingsObject_invalid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelSettingsObject_invalid.png -------------------------------------------------------------------------------- /assets/handbook/vol2/LevelStats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/LevelStats.png -------------------------------------------------------------------------------- /assets/handbook/vol2/MenuLayer_in_ghidra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/MenuLayer_in_ghidra.png -------------------------------------------------------------------------------- /assets/handbook/vol2/MenuLayer_init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/MenuLayer_init.png -------------------------------------------------------------------------------- /assets/handbook/vol2/MenuLayer_init_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/MenuLayer_init_code.png -------------------------------------------------------------------------------- /assets/handbook/vol2/MenuLayer_init_in_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/MenuLayer_init_in_list.png -------------------------------------------------------------------------------- /assets/handbook/vol2/SEL_MenuHandler_def.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/SEL_MenuHandler_def.png -------------------------------------------------------------------------------- /assets/handbook/vol2/add_android_to_ghidra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/add_android_to_ghidra.png -------------------------------------------------------------------------------- /assets/handbook/vol2/androir_fixed_init_ret_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/androir_fixed_init_ret_type.png -------------------------------------------------------------------------------- /assets/handbook/vol2/callback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/callback.png -------------------------------------------------------------------------------- /assets/handbook/vol2/classes_in_symbol_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/classes_in_symbol_tree.png -------------------------------------------------------------------------------- /assets/handbook/vol2/compare_LSL_inits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/compare_LSL_inits.png -------------------------------------------------------------------------------- /assets/handbook/vol2/correct_MenuLayer_init_sig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/correct_MenuLayer_init_sig.png -------------------------------------------------------------------------------- /assets/handbook/vol2/fixed_button_params.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/fixed_button_params.png -------------------------------------------------------------------------------- /assets/handbook/vol2/ghidra_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/ghidra_start.png -------------------------------------------------------------------------------- /assets/handbook/vol2/ghidra_string_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/ghidra_string_search.png -------------------------------------------------------------------------------- /assets/handbook/vol2/ghidra_typedef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/ghidra_typedef.png -------------------------------------------------------------------------------- /assets/handbook/vol2/ghidra_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/ghidra_window.png -------------------------------------------------------------------------------- /assets/handbook/vol2/infoiconuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/infoiconuse.png -------------------------------------------------------------------------------- /assets/handbook/vol2/infospritename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/infospritename.png -------------------------------------------------------------------------------- /assets/handbook/vol2/rename_MenuLayer_init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/handbook/vol2/rename_MenuLayer_init.png -------------------------------------------------------------------------------- /assets/hook_priority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/hook_priority.png -------------------------------------------------------------------------------- /assets/include_path_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/include_path_error.png -------------------------------------------------------------------------------- /assets/positioning_contentsize1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/positioning_contentsize1.png -------------------------------------------------------------------------------- /assets/settings/bool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/bool.png -------------------------------------------------------------------------------- /assets/settings/custom-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/custom-button.png -------------------------------------------------------------------------------- /assets/settings/custom-enum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/custom-enum.png -------------------------------------------------------------------------------- /assets/settings/enable-if.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/enable-if.png -------------------------------------------------------------------------------- /assets/settings/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/file.png -------------------------------------------------------------------------------- /assets/settings/float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/float.png -------------------------------------------------------------------------------- /assets/settings/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/folder.png -------------------------------------------------------------------------------- /assets/settings/int.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/int.png -------------------------------------------------------------------------------- /assets/settings/popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/popup.png -------------------------------------------------------------------------------- /assets/settings/rgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/rgb.png -------------------------------------------------------------------------------- /assets/settings/rgba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/rgba.png -------------------------------------------------------------------------------- /assets/settings/string-one-of.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/string-one-of.png -------------------------------------------------------------------------------- /assets/settings/string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/string.png -------------------------------------------------------------------------------- /assets/settings/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/settings/title.png -------------------------------------------------------------------------------- /assets/vs_example_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/vs_example_config.png -------------------------------------------------------------------------------- /assets/vs_manage_configurations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/vs_manage_configurations.png -------------------------------------------------------------------------------- /assets/win_compilers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/win_compilers.png -------------------------------------------------------------------------------- /assets/win_editconfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/win_editconfig.png -------------------------------------------------------------------------------- /assets/win_relwithdebinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/win_relwithdebinfo.png -------------------------------------------------------------------------------- /assets/win_usecmake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geode-sdk/docs/1b21a6e8e278ee0b21d7514bf945c9365c8aad71/assets/win_usecmake.png -------------------------------------------------------------------------------- /cpp/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 999 3 | --- 4 | 5 | # C++ Tutorials 6 | 7 | This is a collection of tutorials for C++, ranging from basic language features to compiler-specific UB. This is intended as **supplemental** material for GD modders who are new to modding and C++ - **it should not be regarded as a full tutorial**. To learn C++, you should seek more information from sites such as [W3Schools](https://www.w3schools.com/cpp/default.asp) and [learncpp](https://www.learncpp.com/). 8 | -------------------------------------------------------------------------------- /cpp/tips.md: -------------------------------------------------------------------------------- 1 | # General C++ Tips 2 | 3 | This tutorial features some tips for GD modding and C++ programming in general. 4 | 5 | ## Use `auto` 6 | 7 | Despite what some GD modders might tell you, using `auto` is perfectly fine and actually usually desirable. `auto` is the C++-equivalent of the `let` keyword in languages like JavaScript or `var` in C#; it makes C++ **infer the type** of a variable. For example: 8 | ```cpp 9 | // inferred as int 10 | auto x = 5; 11 | // inferred as std::string 12 | auto str = functionThatReturnsString(); 13 | // inferred as float 14 | auto z = 5.2f; 15 | ``` 16 | However, do note that `auto` can lead to unexpected behaviour due to **implicit type conversions**. For example: 17 | ```cpp 18 | // str is an std::string 19 | std::string str = "Awesome string!"; 20 | // Whoops! str is a const char* 21 | auto str = "Awesome string!"; 22 | ``` 23 | This code can not be converted to `auto` by just replacing the type, as this actually calls `std::string`'s `const char*` constructor. The type of string literals is `const char*`, so if you want to create an `std::string` from a string literal, it's usually best to avoid using `auto`. Of course, you could do this: 24 | ```cpp 25 | auto str = std::string("Awesome string!"); 26 | ``` 27 | But that's more verbose than the original code you started with, and it's not really more readable. 28 | 29 | ## Avoid C-style casts 30 | 31 | C++ has inherited a lot of technical debt from C, and one of these is C-style casts: 32 | ```cpp 33 | float x = 5.3f; 34 | int y = (int)x; 35 | ``` 36 | You should **avoid using them** for nearly all situations. This is because C-style casts are **wholly unpredictable**. For example, in the above code, what do you thinks happens? Does the float get rounded down to 5? Does the float's binary representation gets reinterpreted as an integer? 37 | 38 | C++ provides more explicit alternatives that make it clear what you want: 39 | ```cpp 40 | float x = 5.3f; 41 | int y = static_cast(x); 42 | ``` 43 | The main ones are `static_cast`, `reinterpret_cast`, and `dynamic_cast`. There are also some others like `const_cast`, some casting functions in the standard library like `std::static_pointer_cast` and `std::duration_cast`, and some very magical and terrifying aliases in Geode like `reference_cast` and `as`, but in general these will only be used in specific situations. For most cases, you should just use `static_cast`. 44 | 45 | > :warning: A lot of older mods use `reinterpret_cast` for a lot of things, but this is **not recommended at all**. You should always use `static_cast` over `reinterpret_cast`, unless you are **very sure** about what you are doing. 46 | 47 | `static_cast` is what you would expect type conversion to do: it converts the value to match the requested type. This works for casting between built-in datatypes, downcasting pointers, casting between your own classes etc.. 48 | 49 | `dynamic_cast` is a safe version of `static_cast` for polymorphic classes. For example: 50 | ```cpp 51 | class A {}; 52 | class B : A {}; 53 | 54 | if (auto b = dynamic_cast(a)) { 55 | // b is certainly a valid instance of B 56 | } 57 | ``` 58 | In this code, `b` will either be certainly a valid `B` or `nullptr`. `dynamic_cast` comes with a **runtime cost** though, so if you know that `b` will definitely be a valid `B`, you can use `static_cast` instead. 59 | 60 | > :warning: For GD mods, you should **not use `dynamic_cast` on Cocos2d nodes**. This is because, due to problems, `dynamic_cast(spr)` will **always return null** regardless of what you're expecting. Instead, Geode provides an alternative: `typeinfo_cast`. 61 | 62 | ## Avoid memory leaks 63 | 64 | C++ does not have a garbage collector or any sort of memory management, so you have to be careful with handling your own memory. In general, the following tips will help: 65 | 66 | ## Always allocate on the stack when possible 67 | 68 | ```cpp 69 | struct Thing { 70 | float x; 71 | float y; 72 | }; 73 | 74 | class OtherThing { 75 | Thing m_thing; // value 76 | }; 77 | 78 | void someFun(Thing const& thing) {} // pass by const reference 79 | ``` 80 | 81 | If you're passing small structs or classes, always allocate it on the stack by just writing its name. This is the easiest way to keep track of memory; the one built-in memory management C++ does have is RAII, which means that once your struct is no longer used, its memory will be freed. Allocating on the stack is also generally much faster than allocating on the heap. 82 | 83 | Allocating objects on the stack also makes dealing with exceptions and early returns easier; no need to add `delete` everywhere, C++ will automatically free the object for you. 84 | 85 | ```cpp 86 | { 87 | Thing x; 88 | // no matter what happens, be it exceptions or 89 | // something else, x will be freed at the end 90 | } 91 | ``` 92 | 93 | ## Use references over pointers 94 | 95 | If you're passing data to a function or a shared parent class to a child, **use references** instead of pointers. References are safer for a lot of reasons, and also don't increment memory. However, when passing by reference, **you have to make sure your class outlives the reference** - for example, **the following code will crash**: 96 | 97 | ```cpp 98 | struct SomeThing { 99 | int& m_num; 100 | }; 101 | 102 | SomeThing* thing; 103 | { 104 | int x = 5; 105 | thing = new SomeThing { x }; 106 | } 107 | thing->m_num; // Whoops! x is out of scope and so what 108 | // m_num is referencing has been deallocated 109 | ``` 110 | 111 | ## Be aware of lifetimes 112 | 113 | In C++, objects created on the stack live until they go out of scope, and objects created on the heap using `new` live until you call `delete` on them. References and pointers to the object can not extend this lifetime, so make sure that if you're referencing an object, it has not been freed. 114 | 115 | For example, some libraries may require you to pass pointers to objects, but you probably want to just pass pointers to stack-allocated objects (since they are automatically freed, so you don't have to worry about calling `delete`) 116 | ```cpp 117 | int x; 118 | someLegacyFun(&x); // make sure from someLegacyFun's docs that it only uses the 119 | // pointer to x inside its body, and doesn't store it anywhere 120 | ``` 121 | 122 | ## Use `CCObject`-based class over normal ones 123 | 124 | Cocos2d comes with its own handy garbage collector, and if you create something like a `CCArray`, its memory will be automatically freed when it's no longer used. Be wary though that this means **you have to make sure any `CCArray` you do actually need doesn't get garbage-collected**. Cocos2d can't know if you've assigned the `CCArray` to a variable or class member, so either use the `Ref` class in Geode to ensure the object stays alive, or manually keep track of it using `retain` if you have to. (**`Ref` should be used in nearly all situations though**) 125 | 126 | If you're making your own class that inherits from `CCObject` (for example, a node), **always make sure to call `autorelease`** on it. This will ensure the garbage collector will clean its memory. (Unless you are working with `TableViewCell`s. In that case, never call `autorelease` unless you want to experience the worst bug of all time.) 127 | 128 | ## Use smart pointers over pointers 129 | 130 | If you really do have to use pointers and the class you're working with isn't `CCObject`-based, prefer using **smart pointers** like `shared_ptr` and `unique_ptr` over raw pointers. Smart pointers automatically manage the memory being pointed to, and free it when it's no longer needed. 131 | 132 | ## For every call to `new` you have, there must exist a matching call to `delete` 133 | 134 | If none of the other strategies are applicable and you have to use raw pointers, the rule of thumb for memory management there is **for every call to `new` you have, there must be a matching call to `delete`**. The only exception to this are **global manager classes**, which are never freed in lieu of being static. Also, in Cocos2d code, you will call `new` for all of your own node classes, but never `delete`; this is because Cocos2d calls `delete` for you when the garbage collector frees the object. 135 | -------------------------------------------------------------------------------- /flash-template/default.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | tab-size: 4; 4 | } 5 | 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | height: 100%; 10 | background: var(--flash-body-bg); 11 | color: var(--flash-white); 12 | display: grid; 13 | grid-template-columns: minmax(1rem, 26.5rem) 1fr; 14 | grid-template-rows: 100% 100%; 15 | } 16 | 17 | @media only screen and (max-device-width: 250px) { 18 | body > .overlay.theme { 19 | display: none; 20 | } 21 | } 22 | 23 | @media only screen and (max-device-width: 800px) { 24 | body { 25 | grid-template-columns: 1fr; 26 | } 27 | 28 | nav { 29 | position: absolute; 30 | width: 100%; 31 | transition: opacity .25s; 32 | } 33 | 34 | nav.collapsed { 35 | opacity: 0%; 36 | pointer-events: none; 37 | } 38 | 39 | nav:not(.collapsed) { 40 | opacity: 100%; 41 | } 42 | 43 | nav:not(.collapsed) ~ .overlay.theme { 44 | display: none; 45 | } 46 | 47 | body > .overlay.menu { 48 | display: block; 49 | } 50 | } 51 | 52 | @media only screen and (min-device-width: 800px) and (max-device-width: 1100px) { 53 | body { 54 | grid-template-columns: 1fr; 55 | } 56 | 57 | nav { 58 | position: absolute; 59 | width: 30rem; 60 | transition: left .25s; 61 | } 62 | 63 | nav:not(.collapsed) { 64 | left: 0rem; 65 | box-shadow: .25rem 0rem .5rem var(--flash-shadow); 66 | } 67 | 68 | nav.collapsed { 69 | left: -30rem; 70 | } 71 | 72 | body > .overlay.menu { 73 | display: block; 74 | } 75 | } 76 | 77 | @media only screen and (min-device-width: 1100px) { 78 | body > .overlay.menu { 79 | display: none; 80 | } 81 | } 82 | 83 | body > .overlay.menu { 84 | left: 1rem; 85 | } 86 | 87 | body > .overlay.theme { 88 | right: 1rem; 89 | } 90 | 91 | body > .overlay { 92 | top: 1rem; 93 | position: absolute; 94 | display: flex; 95 | flex-direction: row; 96 | border-radius: 9999px; 97 | box-shadow: 0 .25rem .5rem var(--flash-shadow); 98 | z-index: 4; 99 | } 100 | 101 | body > .overlay > button { 102 | background-color: var(--flash-dark); 103 | color: var(--flash-light); 104 | border: none; 105 | padding: .35rem; 106 | padding-left: 1rem; 107 | padding-right: 1rem; 108 | } 109 | 110 | body > .overlay > button:first-child { 111 | border-top-left-radius: 9999px; 112 | border-bottom-left-radius: 9999px; 113 | } 114 | 115 | body > .overlay > button:last-child { 116 | border-top-right-radius: 9999px; 117 | border-bottom-right-radius: 9999px; 118 | } 119 | 120 | body > .overlay > button:hover:not(.selected) { 121 | background-color: var(--flash-less-dark); 122 | cursor: pointer; 123 | } 124 | 125 | body > .overlay > button.selected { 126 | background-color: var(--flash-tab-selected-bg); 127 | color: var(--flash-tab-selected-color); 128 | } 129 | 130 | body > .overlay > button > .feather { 131 | height: 1.25rem; 132 | } 133 | 134 | .version { 135 | font-weight: normal; 136 | color: var(--flash-light); 137 | margin-left: .25rem; 138 | } 139 | 140 | .stretch-apart { 141 | display: flex; 142 | flex-direction: row; 143 | justify-content: space-between; 144 | align-items: baseline; 145 | } 146 | -------------------------------------------------------------------------------- /flash-template/nav.css: -------------------------------------------------------------------------------- 1 | nav > header { 2 | display: grid; 3 | font-family: 'Open Sans', sans-serif; 4 | font-weight: bold; 5 | color: var(--flash-white); 6 | grid-template-columns: 1fr min-content; 7 | align-items: center; 8 | } 9 | 10 | @media only screen and (max-device-width: 1100px) { 11 | nav > header { 12 | padding-left: 3.5rem; 13 | } 14 | 15 | nav > header > a { 16 | justify-content: center; 17 | } 18 | } 19 | 20 | nav > header > a { 21 | color: var(--flash-white); 22 | text-decoration: none; 23 | display: flex; 24 | flex-direction: row; 25 | align-items: center; 26 | } 27 | 28 | nav > header > a:hover { 29 | background: none; 30 | } 31 | 32 | nav > header > a > img { 33 | height: 2.25rem; 34 | margin-right: .5rem; 35 | } 36 | 37 | nav > header > .button { 38 | color: var(--flash-light); 39 | border: .15rem solid var(--flash-dark); 40 | border-radius: 9999px; 41 | height: 2.25rem; 42 | width: 2.25rem; 43 | display: flex; 44 | align-items: center; 45 | justify-content: center; 46 | } 47 | 48 | nav > header > .button > * { 49 | padding: 0; 50 | margin: 0 !important; 51 | } 52 | 53 | nav { 54 | background-color: var(--flash-gray-dark); 55 | height: 100%; 56 | display: grid; 57 | grid-template-rows: min-content min-content 1fr min-content; 58 | z-index: 3; 59 | } 60 | 61 | nav > * { 62 | padding: 1rem; 63 | } 64 | 65 | nav > .mode { 66 | display: flex; 67 | flex-direction: row; 68 | justify-content: stretch; 69 | gap: .25rem; 70 | padding-top: 0rem; 71 | padding-bottom: 0rem; 72 | } 73 | 74 | nav > .mode > button { 75 | display: grid; 76 | grid-template-columns: 1rem 1fr 1rem; 77 | align-items: center; 78 | font-family: 'Open Sans', sans-serif; 79 | color: var(--flash-white); 80 | padding: .5rem; 81 | background-color: rgba(0, 0, 0, 0); 82 | border: none; 83 | transition: background; 84 | flex: 1; 85 | } 86 | 87 | nav > .mode > button:hover { 88 | cursor: pointer; 89 | } 90 | 91 | nav > .mode > button .feather { 92 | width: 1rem; 93 | height: 1rem; 94 | opacity: 50%; 95 | } 96 | 97 | nav > .mode > button.selected { 98 | border-bottom: .2rem solid var(--flash-tab-selected-bg); 99 | border-top: .2rem solid rgba(0, 0, 0, 0); 100 | } 101 | 102 | nav > .mode > button:not(.selected) { 103 | border-bottom: .2rem solid var(--flash-border); 104 | border-top: .2rem solid rgba(0, 0, 0, 0); 105 | } 106 | 107 | nav > .mode > button:hover { 108 | background-color: var(--flash-hover); 109 | } 110 | 111 | nav > .content { 112 | overflow-x: hidden; 113 | overflow-y: auto; 114 | display: flex; 115 | flex-direction: column; 116 | padding: .25rem; 117 | } 118 | 119 | nav > .content details:not(.root) > div { 120 | padding-left: 1.25rem; 121 | } 122 | 123 | nav > .content.monospace { 124 | font-family: 'Source Code Pro', monospace; 125 | } 126 | 127 | nav > .content:not(.monospace) { 128 | font-family: 'Open Sans', sans-serif; 129 | } 130 | 131 | nav > .content:not(.monospace) summary { 132 | color: var(--flash-white); 133 | } 134 | 135 | nav > .content summary { 136 | color: var(--flash-light); 137 | display: flex; 138 | flex-direction: row; 139 | align-items: center; 140 | padding: .25rem; 141 | user-select: none; 142 | overflow: hidden; 143 | text-overflow: ellipsis; 144 | min-width: 5rem; 145 | } 146 | 147 | nav > .content details.root > summary { 148 | color: var(--flash-light); 149 | } 150 | 151 | nav > .content details.root:not(:last-child) { 152 | padding-bottom: 1rem; 153 | margin-bottom: 1rem; 154 | border-bottom: .1rem solid var(--flash-hover); 155 | } 156 | 157 | nav > .content:not(.monospace) > details:not(:first-child) { 158 | margin-top: 1.25rem; 159 | } 160 | 161 | nav > .content:not(.monospace) summary { 162 | padding: .5rem; 163 | } 164 | 165 | nav > .content summary:hover { 166 | background-color: var(--flash-hover); 167 | color: var(--flash-white); 168 | cursor: pointer; 169 | } 170 | 171 | nav .feather { 172 | height: 1.2rem; 173 | } 174 | 175 | nav .feather-chevron-right { 176 | opacity: 50%; 177 | color: var(--flash-nav-arrow); 178 | } 179 | 180 | nav .feather:not(.feather-chevron-right) { 181 | margin-right: .25rem; 182 | } 183 | 184 | nav > .content details[open] > summary > .feather-chevron-right { 185 | transform: rotate(90deg); 186 | } 187 | 188 | nav > .content a { 189 | color: var(--flash-light); 190 | text-decoration: none; 191 | display: flex; 192 | flex-direction: row; 193 | padding: .25rem; 194 | user-select: none; 195 | white-space: nowrap; 196 | text-overflow: clip; 197 | max-width: 100%; 198 | } 199 | 200 | @media only screen and (max-device-width: 800px) { 201 | nav > .content summary { 202 | padding: .75rem; 203 | } 204 | 205 | nav > .content a { 206 | padding: .75rem; 207 | } 208 | } 209 | 210 | @media only screen and (min-device-width: 800px) and (max-device-width: 1100px) { 211 | nav > .content.monospace summary { 212 | padding: .5rem; 213 | } 214 | 215 | nav > .content.monospace a { 216 | padding: .5rem; 217 | } 218 | 219 | nav > .content:not(.monospace) a { 220 | padding: .5rem; 221 | } 222 | } 223 | 224 | @media only screen and (min-device-width: 1100px) { 225 | nav > .content:not(.monospace) a { 226 | padding: .5rem; 227 | } 228 | } 229 | 230 | nav a.selected { 231 | background-color: var(--flash-hover); 232 | color: var(--flash-white); 233 | } 234 | 235 | nav a .feather { 236 | min-width: max-content; 237 | } 238 | 239 | nav a:hover { 240 | background-color: var(--flash-hover); 241 | color: var(--flash-white); 242 | cursor: pointer; 243 | } 244 | 245 | nav a .matched { 246 | color: var(--flash-search-match); 247 | font-weight: bold; 248 | } 249 | 250 | nav a .namespace { 251 | opacity: 50%; 252 | } 253 | 254 | nav a .scope { 255 | opacity: 50%; 256 | color: var(--flash-less-light); 257 | } 258 | 259 | nav .nothing-found { 260 | font-family: 'Open Sans', sans-serif; 261 | color: var(--flash-light); 262 | margin-left: 1rem; 263 | } 264 | 265 | .search { 266 | background-color: var(--flash-gray-darker); 267 | } 268 | 269 | .input { 270 | background: var(--flash-gray-darkest); 271 | border-radius: .25rem; 272 | display: flex; 273 | flex-direction: row; 274 | padding-left: .5rem; 275 | justify-content: stretch; 276 | align-items: stretch; 277 | } 278 | 279 | .input > input { 280 | padding: .5rem; 281 | display: block; 282 | background: none; 283 | border: none; 284 | width: 100%; 285 | color: var(--flash-white); 286 | outline: none; 287 | flex-grow: 1; 288 | } 289 | 290 | .input > button { 291 | display: flex; 292 | align-items: center; 293 | justify-content: center; 294 | padding: .5rem; 295 | align-self: center; 296 | background-color: rgba(0, 0, 0, 0); 297 | border-radius: .25rem; 298 | border: none; 299 | color: var(--flash-light); 300 | } 301 | 302 | .input > button:hover { 303 | background-color: var(--flash-hover); 304 | cursor: pointer; 305 | } 306 | 307 | .input > button .feather { 308 | margin: 0; 309 | } 310 | -------------------------------------------------------------------------------- /flash-template/themes.css: -------------------------------------------------------------------------------- 1 | .flash-theme-dark { 2 | --flash-gray: #272727; 3 | --flash-gray-dark: #222; 4 | --flash-gray-darker: #1a1a1a; 5 | --flash-gray-darkest: #111; 6 | --flash-white: #eee; 7 | --flash-light: #aaa; 8 | --flash-less-light: #888; 9 | --flash-less-dark: #6a6a6a; 10 | --flash-dark: #4a4a4a; 11 | --flash-darker: #3a3a3a; 12 | --flash-blue: #6a80d8; 13 | --flash-cyan-light: #7adff8; 14 | --flash-cyan: #6ac2d8; 15 | --flash-cyan-dark: #427b8a; 16 | --flash-cyan-darker: #294e57; 17 | --flash-green: #9ff4b7; 18 | --flash-purple: #9e6ad4; 19 | --flash-pink: #f49fe9; 20 | --flash-skin: #EC897C; 21 | --flash-dark-skin: #58342f; 22 | --flash-red: #e23485; 23 | --flash-yellow: #f5dd9f; 24 | --flash-orange: #f5c99f; 25 | --flash-dark-orange: #5c4c3e; 26 | --flash-border: rgba(255, 255, 255, .2); 27 | --flash-hover: rgba(255, 255, 255, .1); 28 | --flash-hover-light: rgba(255, 255, 255, .3); 29 | --flash-shade: rgba(0, 0, 0, .25); 30 | --flash-shadow: rgba(0, 0, 0, .2); 31 | 32 | --flash-body-bg: var(--flash-gray); 33 | --flash-h1-color: var(--flash-white); 34 | --flash-tab-selected-bg: var(--flash-light); 35 | --flash-tab-selected-color: var(--flash-dark); 36 | --flash-search-match: var(--flash-blue); 37 | --flash-nav-arrow: var(--flash-light); 38 | --flash-highlight: var(--flash-cyan); 39 | } 40 | 41 | .flash-theme-ocean { 42 | --flash-gray: #272737; 43 | --flash-gray-dark: #223; 44 | --flash-gray-darker: #1a1a2a; 45 | --flash-gray-darkest: #112; 46 | --flash-white: #eee; 47 | --flash-light: #aac; 48 | --flash-less-light: #88a; 49 | --flash-less-dark: #6a6a8a; 50 | --flash-dark: #4a4a6a; 51 | --flash-darker: #3a3a5a; 52 | --flash-blue: #6a80d8; 53 | --flash-cyan-light: #7adff8; 54 | --flash-cyan: #6ac2d8; 55 | --flash-cyan-dark: #427b8a; 56 | --flash-cyan-darker: #294e57; 57 | --flash-green: #9ff4b7; 58 | --flash-purple: #9e6ad4; 59 | --flash-pink: #f49fe9; 60 | --flash-skin: #EC897C; 61 | --flash-dark-skin: #58342f; 62 | --flash-red: #e23485; 63 | --flash-yellow: #f5dd9f; 64 | --flash-orange: #f5c99f; 65 | --flash-dark-orange: #5c4c3e; 66 | --flash-border: rgba(155, 155, 255, .2); 67 | --flash-hover: rgba(255, 255, 255, .1); 68 | --flash-hover-light: rgba(255, 255, 255, .3); 69 | --flash-shade: rgba(0, 0, 0, .25); 70 | --flash-shadow: rgba(0, 0, 0, .2); 71 | 72 | --flash-body-bg: var(--flash-gray); 73 | --flash-h1-color: var(--flash-white); 74 | --flash-tab-selected-bg: var(--flash-blue); 75 | --flash-tab-selected-color: var(--flash-white); 76 | --flash-search-match: var(--flash-cyan); 77 | --flash-nav-arrow: var(--flash-light); 78 | --flash-highlight: var(--flash-cyan); 79 | } 80 | 81 | .flash-theme-peach { 82 | --flash-gray: #372733; 83 | --flash-gray-dark: #342030; 84 | --flash-gray-darker: #2a1a28; 85 | --flash-gray-darkest: #221020; 86 | --flash-white: #eee; 87 | --flash-light: #bab; 88 | --flash-less-light: #a89; 89 | --flash-less-dark: #9a6a8a; 90 | --flash-dark: #6a4a5a; 91 | --flash-darker: #5a3a4a; 92 | --flash-blue: #6a80d8; 93 | --flash-cyan-light: #7adff8; 94 | --flash-cyan: #6ac2d8; 95 | --flash-cyan-dark: #427b8a; 96 | --flash-cyan-darker: #294e57; 97 | --flash-green: #9ff4b7; 98 | --flash-purple: #9e6ad4; 99 | --flash-pink: #f49fe9; 100 | --flash-skin: #EC897C; 101 | --flash-dark-skin: #58342f; 102 | --flash-red: #e23485; 103 | --flash-yellow: #f5dd9f; 104 | --flash-orange: #f5c99f; 105 | --flash-dark-orange: #5c4c3e; 106 | --flash-border: rgba(255, 225, 155, .2); 107 | --flash-hover: rgba(255, 255, 255, .1); 108 | --flash-hover-light: rgba(255, 255, 255, .3); 109 | --flash-shade: rgba(0, 0, 0, .25); 110 | --flash-shadow: rgba(0, 0, 0, .2); 111 | 112 | --flash-body-bg: var(--flash-gray); 113 | --flash-h1-color: var(--flash-yellow); 114 | --flash-tab-selected-bg: var(--flash-yellow); 115 | --flash-tab-selected-color: var(--flash-dark); 116 | --flash-search-match: var(--flash-yellow); 117 | --flash-nav-arrow: var(--flash-white); 118 | --flash-highlight: var(--flash-yellow); 119 | } 120 | -------------------------------------------------------------------------------- /getting-started/cpp-stuff.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1.1. Required C++ Tools 3 | order: 2 4 | --- 5 | 6 | # Required C++ Tools 7 | 8 | To be able to use the Geode SDK, you **will** need at least the following: 9 | * [A C++ compiler](#compiler) 10 | * [CMake](https://cmake.org/download/) - Version 3.25+ is required - make sure to add to PATH when installing on Windows. 11 | * [Git](https://git-scm.com/downloads) - Hey you. Yes, you! I know a lot of people skip this step **but you will need it**. Don't come at us asking for why you "could not find git for clone of json-populate". 12 | 13 | ## Compiler 14 | 15 | To use the Geode SDK, and in turn make Geometry Dash mods, you will need either: 16 | * [Visual Studio 2022+](#windows) on Windows 17 | * [Clang](#macos) on MacOS 18 | * [A secret third thing](#linux) on Linux 19 | 20 | ### Windows 21 | 22 | Download Visual Studio from [its website](https://visualstudio.microsoft.com/downloads/). If you want **just** the compiler and not the code editor, look for *Build Tools for Visual Studio* further down in the page. 23 | 24 | After launching the installer, select **Desktop development with C++**. You may choose other features, but you **will** need at least ***MSVC*** and ***Windows SDK*** installed. 25 | 26 | Once Visual Studio is installed, you should now have a working C++ compiler that is suited for GD mod development. 27 | 28 | Please note that Visual Studio **2022** or higher is required. 2019 or lower will not work, as they don't support C++20 properly. 29 | 30 | ### MacOS 31 | 32 | Install [brew](https://brew.sh/) if you don't already have it, and then run: 33 | ```bash 34 | brew install llvm 35 | ``` 36 | 37 | ### Linux 38 | 39 | Linux is a bit more complicated, as there's no official Linux release of GD (yet). Of course, you can run the Windows version of GD through software like [wine](https://www.winehq.org/) quite well, which is probably what you're already doing. 40 | 41 | Because of that, this guide will set you up to [cross-compile](https://en.wikipedia.org/wiki/Cross_compiler) Windows Geode mods from Linux. 42 | 43 | First, besides Git and Cmake, make sure you have `clang` and `lld` installed. 44 | 45 | On Ubuntu: 46 | 47 | ```bash 48 | apt install clang-17 clang-tools-17 lld-17 49 | ``` 50 | 51 | On Arch-based systems: 52 | 53 | ```bash 54 | pacman -S clang lld 55 | ``` 56 | 57 | The next step will install the Windows SDK and a CMake toolchain. For ease of installation, first install [the Geode CLI](/getting-started/geode-cli.md) and then come back here. If you want to do it manually, you can follow [this guide](https://gist.github.com/matcool/abb65ee59ded3766717c673014c3a2a7). 58 | 59 | After installing the CLI, run this command to install all the needed tools: 60 | 61 | ```bash 62 | geode sdk install-linux 63 | ``` 64 | 65 | Now you can proceed to [setting up Geode CLI](/getting-started/geode-cli.md). 66 | -------------------------------------------------------------------------------- /getting-started/create-mod.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3. Creating and building a new mod 3 | order: 5 4 | --- 5 | 6 | # Creating a new mod 7 | 8 | After all that setup, you can now create your first mod! 9 | 10 | To do this, open up a terminal where you want to create your project and run: 11 | ```bash 12 | geode new 13 | ``` 14 | Follow the given prompts and afterwards you should have a new folder containing the code for your mod. 15 | 16 | ## Files 17 | 18 | You may notice the project already comes with a few files. Lets go over them: 19 | * `CMakeLists.txt` - This is the main file for your CMake project. 20 | * `about.md` - Here you can write a very long description page for your mod, in markdown. Think of it as a README for your mod! This file is technically optional, but highly recommended. 21 | * `logo.png` - This is the icon for your mod, which shows up in-game. This file is technically optional, but highly recommended. 22 | * `mod.json` - This json file contains all the metadata about your mod, such as name, version, custom resources, settings, etc. [See this page for detailed info](/mods/configuring) 23 | 24 | If you plan on releasing your mod, remember to edit the about.md and logo.png files! 25 | 26 | The source code for your mod can be found inside the `src` folder. 27 | 28 | ## Additional Files 29 | 30 | Geode will also look for these special files within your mod folder: 31 | * `changelog.md` - Lists all of the changes between versions to the mod; [see detailed info](/mods/md-files) 32 | * `support.md` - Free-form info about how to show support to the developer of the mod; [see detailed info](/mods/md-files) 33 | 34 | # Build 35 | 36 | Now, to build your mod you have a few options: 37 | 38 | If youre using an IDE such as Clion, VScode or Visual Studio, head over to the [IDE Setup](/getting-started/ide-setup) page. 39 | 40 | If you're building for Android, check out the [Android section](#build-for-android). 41 | 42 | Otherwise if you want to build your mods manually from the command line you can do that by simply running these commands in your mod's folder: 43 | ```bash 44 | # Configures & builds for the current platform 45 | geode build 46 | ``` 47 | 48 | > Check out `geode build --help` for other options! 49 | 50 | If you have an issue running that command for whatever reason ([do let us know!](https://github.com/geode-sdk/cli/issues)), you can build your mod the same way using these commands: 51 | ```bash 52 | # Configure CMake 53 | cmake -B build 54 | 55 | # Build the project 56 | cmake --build build --config RelWithDebInfo 57 | ``` 58 | 59 | If you [created a profile in CLI](/getting-started/geode-cli), then the mod should be automatically installed to GD. If not, then the built `your.mod.geode` package should be in your build folder, from where you can manually install it in-game. 60 | 61 | ## Build for Android 62 | 63 | To build mods for Android you must install the [Android NDK](https://developer.android.com/ndk/downloads). Extract it somewhere and set the `ANDROID_NDK_ROOT` enviroment variable to its path. 64 | 65 | > On **Windows** you must also install [Ninja](https://github.com/ninja-build/ninja/releases). If you have Scoop, you can do this via `scoop install ninja`. 66 | 67 | You must also get the Geode binaries for Android, you can do this using the CLI: 68 | ```bash 69 | geode sdk install-binaries -p android64 70 | # Or if you're using a 32 bit phone 71 | geode sdk install-binaries -p android32 72 | ``` 73 | 74 | Now you can build your mod for Android via: 75 | ```bash 76 | geode build -p android64 77 | # Or if you're using a 32 bit phone 78 | geode build -p android32 79 | ``` 80 | 81 | You can then copy the built .geode file from the `build-android64` folder to your phone at this location: 82 | ``` 83 | /storage/emulated/0/Android/media/com.geode.launcher/game/geode/mods/ 84 | ``` 85 | 86 | ## Building Windows mods on Linux 87 | 88 | If you have followed the steps earlier and installed all the required tools with `geode sdk install-linux`, building should be as simple as on Windows: 89 | 90 | ```bash 91 | geode build 92 | ``` 93 | 94 | If you have installed the Windows SDK and the toolchain in a different way, you will have to provide paths to them manually: 95 | 96 | ```bash 97 | geode build -- -DCMAKE_TOOLCHAIN_FILE=/path/to/clang-msvc-sdk/clang-msvc.cmake -DSPLAT_DIR=/path/to/splat 98 | ``` 99 | -------------------------------------------------------------------------------- /getting-started/geode-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1.2. Geode CLI 3 | order: 3 4 | --- 5 | 6 | # Geode CLI 7 | 8 | Geode has its own CLI tool to aid in many tasks involved in making mods, such as packing assets, generating fonts, managing installed SDK versions, etc.. While it is technically possible to use Geode without the CLI, there is little reason not to install it as **it's required for nearly everything in practice**. 9 | 10 | # Installation 11 | 12 | * [Windows](#windows) 13 | * [MacOS](#macos) 14 | * [Linux](#linux) 15 | 16 | ## Windows 17 | 18 | ### Scoop 19 | 20 | You can use [Scoop](https://scoop.sh/) to easily install the CLI by doing: 21 | ```bash 22 | scoop bucket add extras 23 | scoop install geode-sdk-cli 24 | ``` 25 | Later on, you can easily update the Geode CLI by doing: 26 | ```bash 27 | scoop update geode-sdk-cli 28 | ``` 29 | 30 | ### winget 31 | 32 | If you prefer, you can also use [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/winget/): 33 | ```bash 34 | winget install GeodeSDK.GeodeCLI 35 | ``` 36 | To update the CLI, run: 37 | ```bash 38 | winget upgrade GeodeSDK.GeodeCLI 39 | ``` 40 | 41 | --- 42 | 43 | **(Not Recommended!)** Otherwise, you can manually install the Geode CLI by: 44 | 1. Download the latest Windows release over on [GitHub](https://github.com/geode-sdk/cli/releases/latest) 45 | 1. Extract `geode.exe` into some folder on your computer 46 | 1. Select the executable in File Explorer, Shift + Right-Click it and select `Copy as Path` 47 | 1. Search `Edit the system environment variables` on Windows search. Alternatively, you can open up Control Panel and search for it, then select `Edit the system environment variables` or **to skip straight to step 6 select `Edit environment variables for your account`**. 48 | 1. Click `Environment Variables...` 49 | 1. In the top `User variables` section, select the `Path` variable and click `Edit` 50 | 1. Now click `New` and paste the path of the CLI executable you copied at Step 1. **Remove the `\geode.exe` from the end;** the path has to point to the _directory_ with Geode CLI, not the CLI itself. 51 | 1. Click OK to close the environment variable windows. 52 | 53 | --- 54 | 55 | After installing the CLI, you should now be able to run `geode --version` in your command line and see a version number! (If this doesn't work, try restarting your terminal and/or computer.) 56 | 57 | It is recommended that you [set up a profile afterwards](#profile-setup). 58 | 59 | ## MacOS 60 | 61 | You can easily install the CLI via [Brew](https://brew.sh) 62 | ```bash 63 | brew install geode-sdk/geode/geode-cli 64 | ``` 65 | 66 | It is recommended that you [set up a profile afterwards](#profile-setup). 67 | 68 | ## Linux 69 | 70 | We provide prebuilt Linux binaries in [the CLI releases page](https://github.com/geode-sdk/cli/releases/latest). Since Linux distros differ between each other, you need to figure out yourself how to add this binary to your global path so CMake can find it. As long as `geode --version` works anywhere, everything should be fine. 71 | 72 | Once you figure that out, it is recommended that you [set up a profile afterwards](#profile-setup). 73 | 74 | # Profile Setup 75 | 76 | A profile is just an instance of Geometry Dash. The CLI allows keeping multiple separate installations of Geometry Dash at once, though most users will just have a single installation of GD with Geode on it. If you do have GDPSes with Geode on them installed, you can run `geode profile add` to add them to the list of known profiles. You need to have at least one profile set up so your mods can be automatically installed post build. 77 | 78 | To setup a new profile, simply run the `geode config setup` command on your terminal. 79 | -------------------------------------------------------------------------------- /getting-started/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | order: 1 4 | --- 5 | 6 | # Getting Started 7 | 8 | Please read through this chapter in order, starting in [Prerequisites](/getting-started/prerequisites.md). -------------------------------------------------------------------------------- /getting-started/prerequisites.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1. Prerequisites 3 | order: 1 4 | --- 5 | 6 | # Prerequisites 7 | 8 | Before getting started developing with the Geode SDK, there are a few prerequisites you should be aware of: 9 | 10 | * Prior C++ knowledge is **HIGHLY** recommended. Learning C++ alongside modding is *very* hard. 11 | * Installing [Geode](https://geode-sdk.org/install/) itself! You want to test your own mods, don't you? 12 | 13 | It is recommended that you follow the tutorials on this chapter in order. 14 | -------------------------------------------------------------------------------- /getting-started/sdk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2. Setting up the SDK 3 | order: 4 4 | --- 5 | 6 | # Setting up the SDK 7 | 8 | To install the SDK we will be using the Geode CLI installed on [the previous step](/getting-started/geode-cli). 9 | 10 | To download the SDK, run the following command: 11 | ```bash 12 | geode sdk install 13 | ``` 14 | This *should* set the `GEODE_SDK` enviroment variable, which can you test after restarting your terminal: 15 | ```bash 16 | # On Windows CMD 17 | echo %GEODE_SDK% 18 | 19 | # On Windows PowerShell 20 | echo $env:GEODE_SDK 21 | 22 | # Elsewhere 23 | echo $GEODE_SDK 24 | ``` 25 | 26 | If that command prints out the path you installed the SDK to, then it has worked correctly. 27 | 28 | To develop mods, you should will need to either build Geode from source, or just download the prebuilt binaries using this command: 29 | ```bash 30 | geode sdk install-binaries 31 | ``` 32 | 33 | ## Cache 34 | 35 | It is **highly recommended** to set the [CPM_SOURCE_CACHE](https://github.com/cpm-cmake/CPM.cmake?tab=readme-ov-file#cpm_source_cache) environment variable. This will prevent CMake from flooding your filesystem with duplicates of the same repositories, and allow you to build mods offline (given you have built them online at least once). 36 | 37 | To do this, create a directory somewhere permanent, and set the environment variable `CPM_SOURCE_CACHE` to the full path to that folder. 38 | 39 | ## Updating 40 | 41 | You will need to manually update your local SDK every once in a while, which you can do by running this command: 42 | ```bash 43 | geode sdk update 44 | ``` 45 | 46 | Every time you update the SDK, you should update its prebuilt binaries too. 47 | ```bash 48 | geode sdk install-binaries 49 | ``` 50 | 51 | --- 52 | 53 | You can also switch to the **nightly** version, which uses the latest commit. 54 | ```bash 55 | geode sdk update nightly 56 | ``` 57 | 58 | Or to go back to stable: 59 | ```bash 60 | geode sdk update stable 61 | ``` 62 | -------------------------------------------------------------------------------- /getting-started/what-next.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 4. What next? 3 | order: 7 4 | --- 5 | 6 | # What next? 7 | 8 | You should now be set on your journey to develope GD mods! If you are completely new to GD modding, a good place to start is [the Handbook](/handbook/chap0.md), which covers all of the basics of creating a Hello World. 9 | 10 | ## Geode Features 11 | 12 | Here are a list of Geode-specific concepts you might like to familiarize yourself with: 13 | 14 | * [Hooking with `$modify`](/tutorials/modify.md) and [adding fields](/tutorials/fields.md) 15 | * [Using sprites and other resources](/mods/resources.md) 16 | * [Settings](/mods/settings.md) and [saving data](/mods/savedata.md) 17 | * [String IDs](/tutorials/nodetree.md) and [Layouts](/tutorials/layouts.md) 18 | * [Events](/tutorials/events.md) 19 | * [Using dependencies](/mods/dependencies.md) 20 | * [Publishing mods](/mods/publishing.md) 21 | 22 | The [Tutorials](/tutorials) category in general is a great source for information about working with Geode mods. 23 | -------------------------------------------------------------------------------- /handbook/chap0.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: A complete tutorial for GD modding, directed at beginners 3 | --- 4 | 5 | # An Introduction to GD Modding (using Geode) 6 | 7 | Hello everyone! This is **an introduction to GD Modding (using Geode)**, a handbook that covers all of the essentials of GD Modding. To follow along with this handbook, you will need to have [**Geode** installed](/installation). This handbook is written as a **tutorial for beginners**, though even if you are an experienced GD modder already, getting a fresh-up on the basics can never do harm. Even if for some reason you don't use Geode, this handbook still contains **general information about modding and GD modding specifically**, however all practical code examples and the code project we will build later will use Geode. 8 | 9 | ## Prerequisites 10 | 11 | Do note that while this handbook is written for people with little to no previous experience modding GD, **previous programming and computing knowledge is assumed**. It is highly recommended that before you start GD modding, you should at the very least know how to use **C++**. (Knowledge of other programming languages is also good, but the closer to C++ the better) 12 | 13 | If you're **unsure** that you have sufficient programming skills, here's a **list of concepts you should at the very least know**: 14 | 15 | * Control flow 16 | * Variables 17 | * Functions 18 | * Data types 19 | * Classes 20 | * Inheritance (including multiple inheritance) 21 | * Namespaces 22 | * The C++ Standard Library 23 | * Pointers & references (if you don't know how pointers work, you can get through this tutorial, but you will also be in big trouble later on) 24 | * Macros and the C++ Preprocessor 25 | * Difference between compile-time and runtime 26 | 27 | **Other concepts that are very good to know beforehand but not strictly required**: 28 | 29 | * X86 Assembly (doesn't mean you should speak or read it fluently; just know what it is) 30 | * Reverse enginering (the basics of using Ghidra will be explained in Volume 2) 31 | 32 | If some of the items in the list aren't familiar to you, **you should read up on some online resources first**. A good place to start is to just write "C++ tutorial" in Google and see what comes up. If only some specific parts are unfamiliar, for example you don't know how namespaces work, search for "C++ namespaces". 33 | 34 | ## Volumes 35 | 36 | This handbook is collected into ?? Volumes, going over the following material: 37 | 38 | * **Volume 1**: The basics of modding (Hello, World!) 39 | 40 | * **Volume 2**: Reverse engineering 41 | 42 | * **Volume 3**: Geode, working with other mods, and publishing your mod 43 | 44 | * **Volume 4**: Making a mod (Project) 45 | 46 | ## How to read this handbook 47 | 48 | It is recommended to read through the book chapter-by-chapter. Important concepts and keywords are highlighted in **bold**. Variables, functions, registers and all other code entities are written like `this`. Code blocks look like this: 49 | 50 | ```cpp 51 | #include 52 | 53 | int main() { 54 | std::cout << "Hi mom!\n"; 55 | } 56 | ``` 57 | 58 | The language presented in the code blocks should be stated right before or after the block in the text, but the majority of code blocks use C++. 59 | 60 | > :warning: This is what a warning looks like. If you see one of these boxes, **pay close attention**; it contains tips about how to **avoid common mistakes** in modding. 61 | 62 | > :information_source: This is what an info box looks like. It contains **general information** about the current topic that you might find useful. 63 | 64 | > :green_book: These boxes may also contain links to **further reading** materials for the interested. 65 | 66 | Sometimes the text has notes like this [Note 1]. They are explained at the end of the paragraph, or at the end of the text at the **Notes** section. 67 | 68 | > [Note 1] This is what an **explanation of a note** looks like. 69 | 70 | ## "Traditional" Mods 71 | 72 | Many things in this handbook are referred to as "traditional modding". This means mods made and modding methods used in 2.1; Geode is currently the only major mod loader available for 2.2 and beyond, and differs in some major ways from traditional modding practices. Traditional practices are still included in this tutorial however as a historical curiosity, and to help you spot where old tutorials differ from modern practices. 73 | 74 | ## Ready, set, go! 75 | 76 | **And with that, let us start with [Chapter 1.1](/handbook/vol1/chap1_1)!** 77 | -------------------------------------------------------------------------------- /handbook/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 4 3 | --- 4 | 5 | # Handbook 6 | 7 | This handbook is a tutorial on how to mod GD, starting from the very basics and covering everything from hooking to reverse engineering. It has been written for newcomers with the expectation of now previous game modding experience, however C++ and general programming experience are assumed. 8 | -------------------------------------------------------------------------------- /handbook/vol1/chap1_1.md: -------------------------------------------------------------------------------- 1 | # Chapter 1.1. What is a Mod? 2 | 3 | A **mod** (also known as an **extension**, **hack**, or **plugin**) is a piece of software that modifies another piece of software. Usually in games, this is done through the use of a **mod loader**; something that, well, loads mods. Some games, like Friday Night Funkin and Skyrim have built-in support for loading mods, as well as built-in interfaces for working with mods, which make modding very easy and simple. 4 | 5 | **Geometry Dash**, unfortunately, does not have any built-in mod support. However, using very clever methods such as **DLL injection** and **hooking**, we can create an **external mod loader**. This does come with some disadvantages like the lack of a **stable API** (meaning it's not guaranteed to work between versions), but it also comes with some huge advantages, such as **fully unrestricted access** (which might also be seen as a huge disadvantage, depending on who and when you ask). **A mod can do basically anything**, within the limits of computer hardware. 6 | 7 | But how exactly does a mod work? The actual executable file for Geometry Dash that you have installed on your computer is what is known as an **executable binary program**. If you open the game's executable with a text editor, you will see a bunch of random-looking characters. This is, in fact, the game's code; however it is _not_ its **source code**. This is **binary code**, the raw low-level instructions your CPU executes. If we had the game's source code, we could create a fork of it and add built-in mod loader support. However, as Geometry Dash is closed-source, we have to deal with modifying the binary code. 8 | 9 | ![Image showcasing GeometryDash.exe open in Notepad, showing garbled characters that are the game's binary code interpreted as ASCII](/assets/handbook/vol1/gd_in_notepad.png) 10 | 11 | Modifying binary code is a bit difficult, however, as it is not meant to be for humans; binary code is strictly for computers, and as such it is unreadable and unmodifiable for a human. Binary code is also **platform-specific**: a mod written purely in binary would only work on one platform, and could not be ported over to others without fully rewriting the entire mod from the ground up. 12 | 13 | As a historical curiosity, it should be noted that mods used to be written fully in binary. Or, more accurately, they were written in **assembly**, which is a (somewhat) human-readable form of binary. However, to make it very clear, **no sane person does this anymore**. Working with binary code directly is nowadays only done in rare cases, which will be explained in [Chapter 1.2](/handbook/vol1/chap1_2.md). 14 | 15 | But what's the alternative? We can make modding much easier by knowing two facts: 16 | 17 | - Binary code is produced through **compilation**. 18 | - Geometry Dash is written in **C++**. 19 | 20 | **Compilation is the process of turning source code into binary code**. While we can't modify GD's binary code easily, we can write our own source code in C++ and compile it into compatible binary code. Knowing what language GD was made in is vitally important, as not all binary code is equal. Some programming languages such as [Rust](https://www.rust-lang.org/) produce wildly different binary code from C++, and while it is technically compatible, it is much easier to work with a C++ game using C++. (Some modders, however, are working on making Rust usable for modding!) Writing our mods in a higher-level language like C++ also makes porting much easier; **C++ can be compiled to compatible machine code on all the platforms GD is available on**. This does come with multiple caveats though, however those are left for a later chapter. 21 | 22 | What all of this means is if we write our mod in C++ and then find some way to make GD run the compiled binary code of it, then we have unlocked a path to modding! And luckily, we know how to make GD load extra code: **binary injection**. 23 | 24 | Almost every platform GD is on has some sort of **dynamic library support**. On Windows, these are known as **.DLL files**; on Mac, they're **.DYLIBs**; on Android, they're **.SO files**. A dynamic library is like a binary executable, but they have one special property: other executables can load them. This is usually done through address tables and the such; these are much too complicated for the purposes of this tutorial, and the specifics are also platform-dependant and modern modloaders use proxy DLLs instead. As such, how binary injection works in detail will not be explained here. All you have to take away is that we have methods to make GD load a dynamic library, and that acts as our entry point to modding the game. 25 | 26 | > :information_source: There is one platform that doesn't have dynamic library support: iOS (unless jailbroken). Modding on iOS without jailbreaking requires basically baking all the mods you want into a premodified game, like [iCreate](https://icreate.pro/) has done, but a general mod loader like Geode will likely never see iOS support unless some major changes to the OS happen first. 27 | 28 | > :green_book: If you are interested in **learning more about binary injection**, [the Wikipedia article on DLL injection](https://en.wikipedia.org/wiki/DLL_injection) is a pretty good place to start. 29 | 30 | Usually, the first custom dynamic library we make GD load is a mod loader; that is, a mod whose purpose is to load other mods. This is because the methods we use for injection are not easily scalable, but once we have our one library running, we can invent much simpler ways of loading more of them. For example, on Windows, loading a library from another library that you control is as simple as calling the `LoadLibrary` function. This can also be done an arbitary number of times within our initial library, so we can for example automatically find all the .DLL files in some directory and load them. This is how old 2.1 mod loaders such as **Mega Hack v7** used to work: they would search for all of the `.dll` files in a predefined folder like `extensions` and call `LoadLibrary` on them. 31 | 32 | At this point, we've gotten our library up and running, and are ready to start writing some code. However, now we enter a new problem: **how do we actually do things?** 33 | 34 | [Chapter 1.2: Hooking & Patching](/handbook/vol1/chap1_2.md) 35 | -------------------------------------------------------------------------------- /handbook/vol1/chap1_3.md: -------------------------------------------------------------------------------- 1 | # Chapter 1.3: Functions & Addresses 2 | 3 | In [the last chapter](/handbook/vol1/chap1_2.md), we looked at hooking and how it works. However, the last chapter only touched hooking in theory. The code shown was not what actuals hooks in your code look like. For instance, we do not have access to GD's source code, so we can't exactly just write `return ourDetour()` at the start of the function we want to hook. Instead, we need to figure out some way to **insert hooks into GD's binary code**. 4 | 5 | ## Manual hooking 6 | 7 | The main way to create hooks in Geode is using an abstraction called `$modify` - however, it is a very powerful tool and hard to explain without some preface. It is also just an abstraction; you can create hooks manually in Geode as well through the [`Mod::addHook`](/classes/geode/Mod#addHook) interface - although in practice **you should never be creating manual hooks unless necessary**. 8 | 9 | > :information_source: Manual hooks are sometimes necessary, for example to hook some obscure low-level functions that only exists on one platform, like GLFW on Windows. However, for 99% of mods, you should just be using `$modify`, since it makes your code much more portable and easier to work with. 10 | 11 | > :information_source: The traditional way in 2.1 of placing hooks used a library called [**MinHook**](https://github.com/TsudaKageyu/minhook). If you've been in the GD modding scene before 2.2, you've almost certainly heard of MinHook before, or at least seen its 32-bit dynamic library `minhook.x32.dll`. However, using MinHook directly is **no longer considered good practice**. While it can work perfectly fine for a single mod, MinHook has a few issues: it's Windows-only. hard to use, and if you don't link to it as a dynamic library, it will cause **hook conflicts** [[Note 1]](#notes). 12 | 13 | Let's see a [real-world example](/tutorials/manualhooks) of creating manual hooks in Geode: 14 | ```cpp 15 | auto wrapFunction(uintptr_t address, tulip::hook::WrapperMetadata const& metadata) { 16 | auto wrapped = geode::hook::createWrapper(reinterpret_cast(address), metadata); 17 | if (wrapped.isErr()) {{ 18 | throw std::runtime_error(wrapped.unwrapErr()); 19 | }} 20 | return wrapped.unwrap(); 21 | } 22 | 23 | void MenuLayer_onNewgrounds(MenuLayer* self, CCObject* sender) { 24 | log::info("Hook reached!"); 25 | static auto original = wrapFunction( 26 | geode::base::get() + 0x27b480, 27 | tulip::hook::WrapperMetadata{ 28 | .m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::Thiscall), 29 | .m_abstract = tulip::hook::AbstractFunction::from(void(*)(MenuLayer*, CCObject*)), 30 | } 31 | ); 32 | reinterpret_cast(original)(self, sender); 33 | log::info("After original!"); 34 | } 35 | 36 | $execute { 37 | Mod::get()->addHook( 38 | reinterpret_cast(geode::base::get() + 0x27b480), 39 | &MenuLayer_onNewgrounds, 40 | "MenuLayer::onNewgrounds", 41 | tulip::hook::TulipConvention::Thiscall 42 | ); 43 | } 44 | ``` 45 | 46 | Now, this code sure is quite a jump from the hooking code in the previous chapter. If you haven't done much low-level C++, you might be confused at a lot of the syntax here. There's a bit too much to take in from the code above, so for now we will just be concentrating on a few key details. 47 | 48 | The most important part of this code is `geode::base::get() + 0x27b480`. This is the **address** of the function. When C++ is compiled down to machine code, **all variable and function names are erased** and functions are instead given **memory addresses**. A memory address is just **the location in a binary that the function resides in**. For example, `geode::base::get() + 0x27b480` means that the function is located at offset `0x27b480` (or 2602112 in decimal) bytes from the **base address** - GD's base address, given by the function `geode::base::get()`. 49 | 50 | > :information_source: The reason we need to add the base address to the function's address is because **the base address of GD is dynamic** - it changes between startups! 51 | 52 | What this means is that in order to hook a function in GD, **we need to know its address**. On top of that, **we need to know its signature**, as your detour must always have the same signature as the function you're hooking - otherwise the game will crash! 53 | 54 | > :warning: The above code snippet also references **calling conventions** - if you use `$modify`, Geode handles them for you, however if you're doing manual hooks or reverse engineering, these are very important to get right. 55 | 56 | ## Addresses 57 | 58 | So how do we find out these things? Usually, this is done through **reverse engineering**; however, RE is quite a complex skill, and would take far too much time to explain here, so it has its [own dedicated volume instead](https://docs.geode-sdk.org/handbook/vol2/chap2_1). And on top of that, **most common functions have already been found**. This means that instead of REing the function yourself, you can use the GD bindings that come packaged with Geode. 59 | 60 | > :information_source: Traditionally, the most common GD header library was [**gd.h**](https://github.com/HJfod/gd.h). However, nowadays **gd.h is completely obsolete**, as it is only for 2.1 and fully unmaintained. 61 | 62 | However, it is also important to note that **you still need to know how to reverse engineer** in order to make GD mods. Even if the addresses and signatures are all available, they still don't tell you what the function actually does, how it works, or where its called. 63 | 64 | As noted previously, you shouldn't usually be creating hooks manually. Instead, **Geode comes with a special hooking syntax called `$modify`**. How it works will be explained in a later chapter, but first we must talk a bit about GD's game engine: **Cocos2d**. 65 | 66 | [Chapter 1.4: Cocos2d](/handbook/vol1/chap1_4.md) 67 | 68 | ## Notes 69 | 70 | > [Note 1] Hook conflicts are a type of [**race condition**](https://en.m.wikipedia.org/wiki/Race_condition) and it happens when two mods try to hook the same function at the same time. If the mods do this sufficiently close to one another, there is a high chance that **one mod's hook will replace the other's**. The end result of this is that one of the mods functions incorrectly, when it fails to hook the function it expected to. In the best case, this just results in the mod losing functionality, but in the extreme case this **could cause crashes**. 71 | 72 | -------------------------------------------------------------------------------- /handbook/vol1/chap1_5.md: -------------------------------------------------------------------------------- 1 | # Chapter 1.5: Layers 2 | 3 | In [the previous chapter](/handbook/vol1/chap1_3.md), it was mentioned that at all times there exists **one scene at the top of the node tree**. Usually, in practice, this scene has only one child: some **layer**. Scenes are usually only used as containers for layers and for **transitions**; you will likely never have to inherit from `CCScene` in your code. Instead, the class your layers and GD layers usually inherit from is `CCLayer`. 4 | 5 | Nearly all layers have their own class; for example, the icon kit is `GJGarageLayer`, the editor is `LevelEditorLayer`, and when you go to play a level you enter `PlayLayer`. The first layer most GD modders start modding and also the first layer in the game (after the loading screen) is **the main menu**; it is an instance of the `MenuLayer` class. 6 | 7 | ## Finding Layer Names 8 | 9 | You can find the **class name** of a given layer using the [DevTools](https://github.com/geode-sdk/devtools) mod in Geode. To see how it works, install the **DevTools** mod in-game through the **Download** tab in Geode and then press **F11** to activate it: 10 | 11 | ![Image of the DevTools mod open in GD, focused on MenuLayer](/assets/handbook/vol1/DevTools_MenuLayer.png) 12 | 13 | If you look at the **Tree** view on the left, you will see that the current scene contains one layer named **MenuLayer**, which then further contains a bunch of `CCMenu`s and other nodes. The menus contain all of the buttons in the scene; for example, the bottom center row of buttons is contained in one menu. 14 | 15 | One thing you will notice in DevTools is that many of the nodes have **string IDs**; this is a **Geode-specific addition** meant to make modding simpler. You can read more about string IDs [in its dedicated article](/tutorials/nodetree.md). 16 | 17 | > :warning: In addition to string IDs, some of the menus in Geode are different from the ones in vanilla GD. This is because of the [layout system](/tutorials/layouts.md) in Geode, which handles positioning - in traditional mods, all position has to be done manually! 18 | 19 | There may also be multiple layers in a scene at once. For example, if you click the profile button in `MenuLayer`, you will find it adds a layer named `ProfilePage` in the scene: 20 | 21 | ![Image of the DevTools mod open in GD, showing MenuLayer with ProfilePage on top](/assets/handbook/vol1/DevTools_ProfilePage.png) 22 | 23 | Using DevTools, you can find the name of any layer. Just navigate to the layer whose name you want to figure out in-game, open up DevTools, and look at the node tree! 24 | 25 | > :information_source: DevTools also comes with many other utilities, such as moving nodes in the scene around. However, this tutorial is not about DevTools, so you will have to look at its (currently non-existent) documentation for that ;) 26 | 27 | So now we know that the main menu is called `MenuLayer`, but so what? How can we actually modify it? For that, see [Chapter 1.6: Modifying Layers](/handbook/vol1/chap1_6.md) 28 | -------------------------------------------------------------------------------- /handbook/vol1/chap1_7.md: -------------------------------------------------------------------------------- 1 | # Chapter 1.7: Hello, World! 2 | 3 | If you just skipped to this chapter directly after seeing it in the navigation bar, you may be wondering why it has taken us 7 chapters to reach a `Hello, World!` program. Rest assured, the previous chapters are not just a waste; modding is a complex topic and Geode is an advanced framework. As such, we need to first lay a solid foundation for the basics of modding to build upon before we can get to actual code. 4 | 5 | From here on out however, we're starting to leave the lame theory part and beginning to enter the part where you actually get to make stuff. 6 | 7 | And with that, let's start building our **Hello, World!** mod! 8 | 9 | ## Constructing the Mod 10 | 11 | To begin with, we need to outline what our mod will actually do. The goal is simple: add some text to the main menu in GD that says "Hello, World!". 12 | 13 | ## Modifying the Layer 14 | 15 | Our first step is to use what we learned in the previous chapter and modify `MenuLayer`: 16 | 17 | ```cpp 18 | #include 19 | 20 | class $modify(MenuLayer) { 21 | }; 22 | ``` 23 | 24 | Since we want to add things to the layer, let's hook its `init` function, making sure to call the original: 25 | 26 | ```cpp 27 | #include 28 | 29 | class $modify(MenuLayer) { 30 | bool init() { 31 | if (!MenuLayer::init()) 32 | return false; 33 | 34 | return true; 35 | } 36 | }; 37 | ``` 38 | 39 | ## Adding the label 40 | 41 | Now it's time to actually show the text. As outlined in [Chapter 1.4](/handbook/vol1/chap1_4.md), Cocos2d is node-based; we don't do our own rendering, we leverage other nodes to do it for us. In this case, since we want to display text, we go for the standard `CCLabelBMFont` class: 42 | 43 | ```cpp 44 | #include 45 | 46 | class $modify(MenuLayer) { 47 | bool init() { 48 | if (!MenuLayer::init()) 49 | return false; 50 | 51 | auto label = cocos2d::CCLabelBMFont::create("Hello, World!", "bigFont.fnt"); 52 | 53 | return true; 54 | } 55 | }; 56 | ``` 57 | 58 | Everything Cocos2d-related lies in the `cocos2d` namespace, so we must prefix our usage of `CCLabelBMFont::create` with it. However, as you keep modding, having to stick `cocos2d::` everywhere gets annoying really fast. In addition, Geode comes with a bunch of namespaces you probably also don't want to rewrite every time. For this reason, Geode provides a `geode::prelude` namespace that automatically brings all Cocos2d and Geode-related namespaces to scope: 59 | 60 | ```cpp 61 | #include 62 | 63 | using namespace geode::prelude; 64 | 65 | class $modify(MenuLayer) { 66 | bool init() { 67 | if (!MenuLayer::init()) 68 | return false; 69 | 70 | auto label = CCLabelBMFont::create("Hello, World!", "bigFont.fnt"); 71 | 72 | return true; 73 | } 74 | }; 75 | ``` 76 | 77 | > :information_source: If you don't want to bring Geode namespaces into scope, you can just use `using namespace cocos2d` instead. 78 | 79 | Now, the label isn't currently a child of any layer, so it won't show up anywhere. Let's add it as a child to `MenuLayer`: 80 | 81 | ```cpp 82 | #include 83 | 84 | using namespace geode::prelude; 85 | 86 | class $modify(MenuLayer) { 87 | bool init() { 88 | if (!MenuLayer::init()) 89 | return false; 90 | 91 | auto label = CCLabelBMFont::create("Hello, World!", "bigFont.fnt"); 92 | this->addChild(label); 93 | 94 | return true; 95 | } 96 | }; 97 | ``` 98 | 99 | ## Positioning the label 100 | 101 | The default position for any node is (0, 0) which is at bottom left of the screen; however, we want to center our label in the middle of the screen. To do this, we need to first figure out what the size of the screen is, and to do this we use the `getWinSize` method on `CCDirector`: 102 | 103 | ```cpp 104 | #include 105 | 106 | using namespace geode::prelude; 107 | 108 | class $modify(MenuLayer) { 109 | bool init() { 110 | if (!MenuLayer::init()) 111 | return false; 112 | 113 | auto winSize = CCDirector::get()->getWinSize(); 114 | 115 | auto label = CCLabelBMFont::create("Hello, World!", "bigFont.fnt"); 116 | this->addChild(label); 117 | 118 | return true; 119 | } 120 | }; 121 | ``` 122 | 123 | Next, to actually position our label, we call the `setPosition` method on it, placing it halfway across the screen horizontally and vertically: 124 | 125 | ```cpp 126 | #include 127 | 128 | using namespace geode::prelude; 129 | 130 | class $modify(MenuLayer) { 131 | bool init() { 132 | if (!MenuLayer::init()) 133 | return false; 134 | 135 | auto winSize = CCDirector::get()->getWinSize(); 136 | 137 | auto label = CCLabelBMFont::create("Hello, World!", "bigFont.fnt"); 138 | label->setPosition(winSize.width / 2, winSize.height / 2); 139 | this->addChild(label); 140 | 141 | return true; 142 | } 143 | }; 144 | ``` 145 | 146 | All nodes are by default centered relative to their position, so doing this is enough to center our label in the middle of the screen. However, we can make this code more concise by knowing that `winSize`'s type is `CCSize`, and that dividing a `CCSize` by an integer is the same as dividing that size's width and height by the integer. Since we are dividing both `winSize.width` and `winSize.height` by the same number, we can shorten it to just `winSize / 2`. In addition, all nodes have both a `setPosition(float, float)` and `setPosition(CCPoint)` setter by default, and `CCSize` is implicitly convertible to `CCPoint`. 147 | 148 | All of this means that we can make our `setPosition` call shorter as just: 149 | ```cpp 150 | label->setPosition(winSize / 2); 151 | ``` 152 | 153 | ## Finished Mod 154 | 155 | And with that, **we have completed our Hello, World! mod**. Here's what the final code looks like: 156 | 157 | ```cpp 158 | #include 159 | 160 | using namespace geode::prelude; 161 | 162 | class $modify(MenuLayer) { 163 | bool init() { 164 | if (!MenuLayer::init()) 165 | return false; 166 | 167 | auto winSize = CCDirector::get()->getWinSize(); 168 | 169 | auto label = CCLabelBMFont::create("Hello, World!", "bigFont.fnt"); 170 | label->setPosition(winSize / 2); 171 | this->addChild(label); 172 | 173 | return true; 174 | } 175 | }; 176 | ``` 177 | 178 | To try the mod out, [create a new mod using `geode new`](/geode/creating), and then replace the code in `src/main.cpp` with the above. After building the mod, open up GD and you should see this: 179 | 180 | ![Image showing the main menu in GD with a 'Hello, world' text on top](/assets/handbook/vol1/hello_world.png) 181 | 182 | If it works for you, **congratulations!** You have now officially built your first Geometry Dash mod! Go grab a lil orange juice from the kitchen and give yourself a little treat :) 183 | 184 | After drinking your juice however, it's time to get back into business. So we've got a Hello, World! going, that's great. Now it's time to start crafting something actually useful. 185 | 186 | In Volume 2 of the tutorial, we will start looking at **reverse engineering** and how making new mods actually works in practice. To start, let's once again ask the following question: [so how exactly does one make a mod?](/handbook/vol2/chap2_1.md). 187 | -------------------------------------------------------------------------------- /handbook/vol1/index.md: -------------------------------------------------------------------------------- 1 | # Volume 1: The Basics 2 | 3 | This volume of the handbook covers the very basics of modding - how modding works, what a hook is, and why we would want a framework to begin with. 4 | -------------------------------------------------------------------------------- /handbook/vol2/chap2_1.md: -------------------------------------------------------------------------------- 1 | # Chapter 2.1: Reverse Engineering 2 | 3 | At this point in this handbook, **reverse engineering** (RE for short) has been alluded to and referred to numerous times. At its core, it is quite simply **the process of figuring out how something works**. However, in practice with GD, this often involves having to read assembly code, understanding how your computer works, and a lot of other deeply low-level stuff. And even so, it is fundamental to making mods, as **you can't modify something you don't understand**. This is why this handbook has an entire dedicated Volume just for reverse engineering; **it is difficult, it is complicated, and yet it's super important**. Every modder has to learn reverse engineering at some point or another, and it's best to start early. 4 | 5 | It should be noted that reverse engineering is also an ever-evolving skill; you're **never going to be done learning it**. This Volume covers many important aspects of it, but it should by no means be treated as a comprehensive list. 6 | 7 | But that's enough preface. Let's start actually **REing**! 8 | 9 | ## Tools 10 | 11 | There are a lot of tools GD modders use for reverse engineering, but some of the most common ones include: 12 | 13 | * [Ghidra](https://ghidra-sre.org/) 14 | * [IDA Pro](https://hex-rays.com/IDA-pro/) (which every modder most definitely has legally bought :wink:) 15 | * [x64dbg](https://x64dbg.com/) (Windows) 16 | * [Cheat Engine](https://cheatengine.org/) (Windows) 17 | * [ReClass](https://github.com/ReClassNET/ReClass.NET) (Windows) 18 | * [DevTools](https://github.com/geode-sdk/DevTools) (Traditionally [CocosExplorer](https://github.com/matcool/CocosExplorer)) 19 | * [Slicer](https://github.com/zorgiepoo/Bit-Slicer) (Mac) 20 | * [LLDB](https://lldb.llvm.org/) 21 | 22 | For this tutorial, we will be using **Ghidra** and **x64dbg**. 23 | 24 | ## Setting Up Ghidra 25 | 26 | First, [download Ghidra](https://ghidra-sre.org/) and install it on your machine. Open it, **create a new project**, and you should see something like this: 27 | 28 | ![Image showing the project page of Ghidra](/assets/handbook/vol2/ghidra_start.png) 29 | 30 | > :information_source: You can switch themes by going in `Edit > Theme > Switch...` 31 | 32 | Now, drag Geometry Dash's binary (e.g. `GeometryDash.exe`) into the window, add it with the default settings, open it, and **Analyze it** with the default settings (if you're reverse engineering Android, disable the `Non returning functions: discovered` option). Depending on your computer, analyzing might take a while - go grab another cup of orange juice while waiting for it to finish. 33 | 34 | You should now see a window like this: 35 | 36 | ![Image showing the opened project in Ghidra](/assets/handbook/vol2/ghidra_window.png) 37 | 38 | The three most important windows for our purposes are **Symbol Tree**, **Listing**, and **Decompiler**. You can close the others. 39 | 40 | Now that we have Ghidra setup, it's time to learn how we can find a layer's `init` function. 41 | 42 | [Chapter 2.2: Finding `MenuLayer::init`](/handbook/vol2/chap2_2.md) 43 | -------------------------------------------------------------------------------- /handbook/vol2/chap2_2.md: -------------------------------------------------------------------------------- 1 | # Chapter 2.2: Finding `MenuLayer::init` 2 | 3 | The first thing we have to figure out when finding functions is exactly what function we want to find. Often times, this is some layer's `init` function - however, it can also be any function responsible for some behaviour we want to alter, like the function that is responsible for scaling objects, the function that plays a level's song, or anything else. 4 | 5 | Luckily for us, **pretty much all functions in GD are part of some class**. This means that to find a function, we first have to make an educated guess about what class it might be in, and use that as a starting point for figuring out where it actually is. 6 | 7 | If you look at the Symbol Tree window in Ghidra, you will see it has a folder named Classes. Opening this folder reveals, to great surprise, every class in GD: 8 | 9 | ![Picture showing the list of classes in GD open in Ghidra](/assets/handbook/vol2/classes_in_symbol_tree.png) 10 | 11 | Now, there is one problem with this list: **there are a lot of classes in GD**. Just browsing this list is most likely not going to help you figure out where the function you're looking for is. Instead, first you need to make some guess about where to start. 12 | 13 | For finding layers' `init` functions, this guess is very easy to make: just figure out the layer's name using DevTools, and it's probably in that class. 14 | 15 | So, to find the `init` function for `MenuLayer`, lets first navigate to `MenuLayer`: 16 | 17 | ![Picture showing MenuLayer's vtable open in Ghidra](/assets/handbook/vol2/MenuLayer_in_ghidra.png) 18 | 19 | Unfortunately, there are no functions listed, as Ghidra can't really know which functions belong to which class. However, what there is listed are the class's **virtual function tables**; in other words, a list of all the virtual functions a class has. While not every class's `init` function is virtual, in `MenuLayer`'s case it is, so we can actually find the `init` function through the vtable directly. 20 | 21 | Now, knowing exactly which index in the vtable is which virtual function is a bit difficult to figure out, but the jist is that virtual functions are in the order they were declared in the class itself. For `CCNode`, the first virtual function declared is `init` - however, as `CCNode` inherits from `CCObject` which has a bunch of a virtual functions of its own, `init` is not the first virtual function in the vtable, but instead the 10th. 22 | 23 | If we look at the 10th function in `MenuLayer`'s vtable, we notice that it has a different name from the others: 24 | 25 | ![Picture showing MenuLayer's vtable open in Ghidra, focused on the index #9 which shows a function named FUN_005907b0 surrounded by other functions with human-readable names](/assets/handbook/vol2/MenuLayer_init.png) 26 | 27 | The reason for this is that this function is the only one that's overridden - `MenuLayer` does not overwrite `setZOrder` for instance, so the function included in `MenuLayer`'s vtable is `CCNode::setZOrder`, whose name Ghidra knows as it is an exported symbol from `libcocos2d.dll`. However, because this function is overridden and defined in GD itself, Ghidra can't know its name. However, we know what its name is - it is `MenuLayer::init`! 28 | 29 | ![Picture showing MenuLayer::init's decompiled code open in Ghidra](/assets/handbook/vol2/MenuLayer_init_code.png) 30 | 31 | Double-clicking the function name to enter it, we can look at its decompiled code. Since it uses sprites like `logo.png` which we can know is only used in the main menu by playing the game, we can verify that this is most certainly the function we're looking for. We can give it the correct name by selecting the function name and pressing L: 32 | 33 | ![Picture showing renaming MenuLayer::init in Ghidra](/assets/handbook/vol2/rename_MenuLayer_init.png) 34 | 35 | Now the function also appears under the `MenuLayer` class in the class list, which is very convenient. 36 | 37 | We can also fix the function's signature by right-clicking on it and selecting `Edit Function Signature`, setting its calling convention as `thiscall` and making it return a `bool`: 38 | 39 | ![Picture showing fixing MenuLayer::init's signature in Ghidra using the Edit Function Signature option](/assets/handbook/vol2/correct_MenuLayer_init_sig.png) 40 | 41 | ![Picture showing MenuLayer::init under MenuLayer in Ghidra](/assets/handbook/vol2/MenuLayer_init_in_list.png) 42 | 43 | Congratulations, you have reverse-engineered your first function! However, let's not stop here - let's find some other, a little more obscure `init` functions: 44 | 45 | [Chapter 2.3: Finding `LevelSettingsLayer::init`](/handbook/vol2/chap2_3.md) 46 | -------------------------------------------------------------------------------- /handbook/vol2/chap2_3.md: -------------------------------------------------------------------------------- 1 | # Chapter 2.3: Finding `LevelSettingsLayer::init` 2 | 3 | Not all `init` functions are found on the vtable however. The only reason `MenuLayer::init` is on there is because it takes no extra parameters - so it overrides `CCNode::init`. However, if the init function has any parameters, it has a different name, or the class doesn't inherit from `CCNode`, then you most likely won't find it on the vtable (unless it is explicitly marked virtual). 4 | 5 | Luckily, finding non-virtual `init` functions is still quite easy! As an example, let's find the `init` function for `LevelSettingsLayer`! 6 | 7 | ![Picture showing LevelSettingsLayer open in the Ghidra class list](/assets/handbook/vol2/LevelSettingsLayer_in_tree.png) 8 | 9 | Let's start as before by finding `LevelSettingsLayer` in the Ghidra class list (tip: you can use the search function at the bottom!). Click any of the vtables to navigate to that vtable in the disassembly view. 10 | 11 | ![Picture showing a vtable for LevelSettingsLayer, with two XREFs](/assets/handbook/vol2/LevelSettingsLayer_vtable_ctor_xref.png) 12 | 13 | Now, check the XREFs - for most classes, there should be two [[Note 1]](#notes). One of these is the constructor, and the other is the destructor. Pick one, and see which one it is - we are looking for the constructor. 14 | 15 | ![Picture showing one of the XREFs for LevelSettingsLayer open in the decompiler, likely being the constructor](/assets/handbook/vol2/LevelSettingsLayer_ctor.png) 16 | 17 | Ah, this is likely the constructor! You can tell this by the fact that it initializes a bunch of fields in the class pointer to their default values, as well as that it contains a single call to the base constructor. Although, the base constructor is unknown - double-click it to find out what it is! 18 | 19 | ![Picture showing FLAlertLayer's constructor open in the decompiler](/assets/handbook/vol2/FLAlertLayer_ctor.png) 20 | 21 | Judging by the vtables, this seems to quite clearly be `FLAlertLayer`'s constructor. So let's rename the function to `FLAlertLayer::FLAlertLayer` and press `Alt + Left Arrow` to go back to the previous constructor (note: you may have to press it multiple times because Ghidra is quirky like that). 22 | 23 | ![Picture showing LevelSettingsLayer constructor renamed to the correct name](/assets/handbook/vol2/LevelSettingsLayer_ctor_renamed.png) 24 | 25 | Now check the XREFs for the `LevelSettingsLayer` constructor. There's likely only one [[Note 1]](#notes) - click it to follow the reference. 26 | 27 | ![Picture showing `LevelSettingsLayer::create`](/assets/handbook/vol2/LevelSettingsLayer_create.png) 28 | 29 | Hm, okay, let's analyze this code a little bit. First it calls `operator new`, then it checks if that was null, then calls a function on the returned value, and if that function returned a truthy value, it calls `autorelease` and returns the value. 30 | 31 | This looks very familiar - this is the structure of a `create` function: 32 | 33 | ```cpp 34 | LevelSettingsLayer* LevelSettingsLayer::create(...) { 35 | auto ret = new LevelSettingsLayer(); 36 | if (ret) { 37 | if (ret->init(...)) { 38 | ret->autorelease(); 39 | return ret; 40 | } 41 | delete ret; 42 | } 43 | return nullptr; 44 | } 45 | ``` 46 | 47 | We can be almost certain that this is the `create` function, which means that the one unknown function call we have before the call to `autorelease` must be `LevelSettingsLayer::init` - double click the function to make sure! 48 | 49 | ![Picture showing an unknown function call at the start of what we assume to be `LevelSettingsLayer::init`](/assets/handbook/vol2/LevelSettingsLayer_base_init_call.png) 50 | 51 | Hm, there's a call to some unknown function at the start, whose return value the code checks to be truthy before doing anything else, and if it's false then it does nothing but propagate that information up to the original caller. Sounds very similar to the structure of an `init` function: 52 | 53 | ```cpp 54 | bool LevelSettingsLayer::init(...) { 55 | if (!FLAlertLayer::init(...)) 56 | return false; 57 | 58 | ... 59 | 60 | return true; 61 | } 62 | ``` 63 | 64 | Let's make sure by checking out the unknown function's body: 65 | 66 | ![Picture showing what is clearly `FLAlertLayer::init`](/assets/handbook/vol2/FLAlertLayer_init.png) 67 | 68 | OK, this is clearly an `init` function. Since we know from the constructor that the base class of `LevelSettingsLayer` is `FLAlertLayer`, that means that this is most likely `FLAlertLayer::init` [[Note 2]](#notes). That means that the other function we found is very likely `LevelSettingsLayer::init` as well, so let's rename both approppriately: 69 | 70 | ![Picture showing fixing `LevelSettingsLayer::init`'s signature](/assets/handbook/vol2/LevelSettingsLayer_init_rename.png) 71 | 72 | Well. Hmm. We know that any `init` function returns a bool, but what about these parameters? `LevelSettingsLayer` probably doesn't actually take a bare `CCObject*` and `undefined4` is definitely not the correct type - but how do we find the real ones? 73 | 74 | And besides, what we have done know is still just educated guesses, though they are very good guesses. For example, we guessed that the name of the init function is `init` - however, it could also just as well be something like `initWithObject`. Could we gain a little more certainty? It would be really convenient if there was something we could compare this against - something we know to be correct. 75 | 76 | Well, luckily, there is: Android! 77 | 78 | [Chapter 2.4: Comparing against Android](/handbook/vol2/chap2_4.md) 79 | 80 | ## Notes 81 | 82 | > [Note 1] If there are multiple XREFs, it's likely that either the create function for the class has either been inlined, or there are multiple or them - RE to find out what is the case! 83 | 84 | > [Note 2] The base `init` call is not necessarily a call to the actual base class's `init` for two reasons: inlining and the code not doing that. You can figure out what is the case by comparing against Android (explained in Chapter 3) 85 | -------------------------------------------------------------------------------- /handbook/vol2/chap2_4.md: -------------------------------------------------------------------------------- 1 | # Chapter 2.4: Comparing Against Android 2 | 3 | Android GD is special for one important reason - **it has symbols!** What this means is that every function name is present in the code, just like Cocos2d on Windows - and not only their names, but their parameter types aswell! And because GD is a cross-compiled game from a single codebase, this means that we can compare against Android GD to aid reverse engineering other platforms. 4 | 5 | Android GD's binary is called `libcocos2dcpp.so` - it contains GD, Cocos2d, and all other dependencies in a single package. However, getting your hands into it is not as easy as Windows, since you need to extract it from the APK - ask some other modder for their copy of it :wink: 6 | 7 | > :warning: ARMv8 support in Ghidra broke in version 11, so you'd preferably get the ARMv7 version. 8 | 9 | Once you have acquired the Android binary, import it to Ghidra the same way you imported Windows, except make sure to disable the `Non returning functions: discovered` option for Analysis. 10 | 11 | ![Android GD imported to Ghidra](/assets/handbook/vol2/add_android_to_ghidra.png) 12 | 13 | ![Analyzing Android GD, with the `Non returning functions: discovered` option disabled](/assets/handbook/vol2/Android_analyz.png) 14 | 15 | So, let's verify the REing we did [in the last chapter](/handbook/vol2/chap2_3.md)! 16 | 17 | Open up the class list on Android and look up `LevelSettingsLayer`: 18 | 19 | ![`LevelSettingsLayer` from Android open in Ghidra, showing a lot more functions](/assets/handbook/vol2/LevelSettingsLayer_androir.png) 20 | 21 | Whoa, that's a lot of functions... thanks, symbols! The first function we found in the last chapter was the constructor, so let's navigate straight to it! 22 | 23 | Well, hm. There doesn't seem to be any function named `LevelSettingsLayer` here? The destructors are there but no constructor? Suspicious... let's open up `create` instead to see what's up: 24 | 25 | ![`LevelSettingsLayer::create` on Android, showing that the constructor has been inlined](/assets/handbook/vol2/LevelSettingsLayer_create_androir.png) 26 | 27 | Ah, so that's what happened - **the constructor got inlined**. This means that its code was inserted to its call sites and the function definition itself got removed. 28 | 29 | Well, if we look at the end of the `create` function, we can see the call to `init` is there: 30 | 31 | ![`LevelSettingsLayer::init` called inside `LevelSettingsLayer::create`](/assets/handbook/vol2/LevelSettingsLayer_create_call_to_init_androir.png) 32 | 33 | Let's follow the call and compare this to the `LevelSettingsLayer::init` we found on Windows: 34 | 35 | ![Comparison between `LevelSettingsLayer::init` on Android and what we think is the same on Windows](/assets/handbook/vol2/compare_LSL_inits.png) 36 | 37 | (Left is Android, right is Windows) 38 | 39 | Hmm, alright, both have the call to `FLAlertLayer::init` at the start. And both make calls to `CCObject::retain`, `CCDirector::sharedDirector`, and the `CCRect` constructor at similar times... Oh, and there's a string literal! Both have a call to `CCLabelBMFont::create` with the same parameters. String literals are a really good way to compare functions, so this confirms it - we definitely found the real `LevelSettingsLayer::init` on Windows. 40 | 41 | So, let's copy the signature from Android to Windows: 42 | 43 | ![The signature of `LevelSettingsLayer::init` on Android, acquired through a symbol](/assets/handbook/vol2/LevelSettingsLayer_init_sig.png) 44 | 45 | Note that the Android function signatures aren't perfect - they don't have parameter names, and more crucially they **don't have a return type**. You may notice that Ghidra has *inferred* the type of `LevelSettingsLayer::init` to be a void, however we know better than Ghidra that `init` functions don't return void! Manually fixing the return type makes Ghidra add the missing `return false`: 46 | 47 | ![`LevelSettingsLayer::init` on Android in Ghidra, now returning the correct type](/assets/handbook/vol2/androir_fixed_init_ret_type.png) 48 | 49 | So let's get back to adding the correct parameter types on Windows. The first one is a `LevelSettingsObject*`, so let's add it- 50 | 51 | ![The type `LevelSettingsObject` doesn't exist in Ghidra](/assets/handbook/vol2/LevelSettingsObject_invalid.png) 52 | 53 | Oh, Ghidra has no clue what a `LevelSettingsObject` is. Well, we could manually define the type using the Data Type Manager, but it's better to let Ghidra make the definition for us by finding some `LevelSettingsObject` member function and typing it as `__thiscall`: 54 | 55 | ![`LevelSettingsObject::init` renamed correctly, which causes Ghidra to register the `LevelSettingsObject` type](/assets/handbook/vol2/LevelSettingsObject_init.png) 56 | 57 | (`LevelSettingsObject::init` was found using the same method as the previous chapters, however replicating it is left as an exercise for the reader!) 58 | 59 | Repeat the same for `LevelEditorLayer`, and now Ghidra lets us use them as parameter types: 60 | 61 | ![`LevelSettingsLayer::init` with the correct parameter types on Windows](/assets/handbook/vol2/LevelSettingsLayer_init_retyped.png) 62 | 63 | > :warning: The reason the **calling convention** of `LevelSettingsLayer::init` is `__thiscall` will be explained in a later chapter - not all functions have the same calling convention! 64 | 65 | And there we go - we can now be pretty much certain that we have found the correct `LevelSettingsLayer::init`, and that our signature is right! 66 | 67 | [Chapter 2.5: Finding Callbacks](/handbook/vol2/chap2_5.md) 68 | -------------------------------------------------------------------------------- /handbook/vol2/chap2_5.md: -------------------------------------------------------------------------------- 1 | # Chapter 2.5: Finding Callbacks 2 | 3 | The reason we started reverse engineering by finding `init` functions is because they're the most common thing you'll be hooking. However, there is another type of function that is used very often in mods - callbacks! 4 | 5 | Callbacks are passed to buttons through their `create` function (usually `CCMenuItemSpriteExtra::create`), and are what the button does when it is clicked - vitally important for any mod which wants to change what a button does! 6 | 7 | Callbacks are usually named `onSomething` and take a single `CCObject*` parameter along with the current class. This is what passing a callback looks like in C++ code: 8 | 9 | ```cpp 10 | auto button = CCMenuItemSpriteExtra::create( 11 | ..., this, menu_selector(ThisClass::onButtonClicked) 12 | ); 13 | ``` 14 | 15 | The `menu_selector` macro is just syntactic sugar for `reinterpret_cast(&ThisClass::onButtonClicked)` - in other words, it just casts the callback to the correct type. 16 | 17 | What we can take away from this code is that to find a callback in Ghidra, all we need to do is find out where the button it's used on is created, and then look at that button's create call. The easiest way to find out where our desired callback is used is through Android - although, first we need to figure out which callback we're looking for. 18 | 19 | Let's say we're looking for whatever function it is that shows this popup: level stats. 20 | 21 | ![Level statistics popup for Zenith by HJfod](/assets/handbook/vol2/LevelStats.png) 22 | 23 | As a start, thanks to DevTools, we know this layer's name is `LevelInfoLayer` (todo: add picture), so we're looking for some callback in it. 24 | 25 | Now, we know that the button that shows this has an info button sprite, whose sprite name is `GJ_infoIcon_001.png` (thanks, Geode VS Code extension). 26 | 27 | ![The info sprite found through the Geode VS Code extension, showing that its name is `GJ_infoIcon_001.png`](/assets/handbook/vol2/infospritename.png) 28 | 29 | Let's find `LevelInfoLayer::init` and then find where it uses the info sprite: 30 | 31 | ![Code showing a `CCSprite` being created with `GJ_infoIcon_001.png`](/assets/handbook/vol2/infoiconuse.png) 32 | 33 | Hm, this doesn't seem right. Let's check the unknown function call after: 34 | 35 | ![Code resembling a `create` function](/assets/handbook/vol2/CCMenuItemSpriteExtra_create.png) 36 | 37 | Hm, this looks like a `create` function. Checking the constructor, it seems to be `CCMenuItemSpriteExtra::create` - let's rename it as such. Checking on Android, the function has 4 parameters: `CCNode*`, `CCNode*`, `CCObject*`, and `void(CCObject::*)(CCObject*)`. 38 | 39 | The first three are easy - however, the last one is a function pointer. Cocos2d has an alias for this pointer in particular - `SEL_MenuHandler`. However, as it's a typedef, it doesn't come with any symbols and as such we need to add it manually: 40 | 41 | ![Adding a new function definition in Ghidra](/assets/handbook/vol2/ghidra_typedef.png) 42 | 43 | ![Defining SEL_MenuHandler in Ghidra](/assets/handbook/vol2/SEL_MenuHandler_def.png) 44 | 45 | After this, we can correct the signature of `CCMenuItemSpriteExtra::create`. 46 | 47 | ![The correct signature for `CCMenuItemSpriteExtra::create` in Ghidra](/assets/handbook/vol2/CCMenuItemSpriteExtra_create_correct.png) 48 | 49 | > :warning: Note the calling convention being `__fastcall` rather than `__thiscall` - this will be explained later! 50 | 51 | Now if we look back at `LevelInfoLayer::init`, we can see that the function call looks more accurate - there's a function being passed as a parameter now! If we follow through to that function's definition, we will see that it in fact is the one we were looking for, judging by the string literals present. 52 | 53 | ![A call to `CCMenuItemSpriteExtra::create` in Ghidra with the correct args](/assets/handbook/vol2/fixed_button_params.png) 54 | 55 | ![`LevelInfoLayer::onLevelInfo`](/assets/handbook/vol2/callback.png) 56 | 57 | Now we just need to find the same function on Android to get the correct signature. Here we can take advantage of the fact that Ghidra can search for string literals defined in the code: 58 | 59 | ![Searching for the string `Total Attempts` in Ghidra](/assets/handbook/vol2/ghidra_string_search.png) 60 | 61 | Filtering through the potential XREFs, we find `LevelInfoLayer::onLevelInfo`, which checking through `LevelInfoLayer::init` is the correct one. So we can retype our Windows function and be confident we found the correct one! 62 | 63 | Right now, reverse engineering should seem a little less mystifying, however there is one more important aspect we need to consider: calling conventions. Where do they come from? Where do they go? How do you know what calling convention a function uses? 64 | 65 | Before we can delve into that, we first need to learn a bit about assembly: 66 | 67 | [Chapter 2.6: Introduction to Assembly](/handbook/vol2/chap2_6.md) 68 | -------------------------------------------------------------------------------- /handbook/vol2/chap2_6.md: -------------------------------------------------------------------------------- 1 | # Chapter 2.6: Introduction to Assembly 2 | 3 | In earlier chapters, we have avoided having to look at **assembly code** as it can be quite hard to grasp. However, an unfortunate reality is that in order to be an independent GD modder, **you need to understand assembly**, or at the very least the basics of it. So, without further ado, let's delve into some **x86**! 4 | 5 | ```cpp 6 | int addTwo(int a, int b) { 7 | return a + b; 8 | } 9 | 10 | int main() { 11 | int z = addTwo(5, 7); 12 | } 13 | ``` 14 | Here we have a very simple program in C++: adding two numbers together. Let's compile this to **x86 assembly** and see what we get! 15 | ```cpp 16 | 0x432b40: 17 | mov eax, ecx 18 | add eax, edx 19 | ret 0 20 | 21 | _main: 22 | mov ecx, 5 23 | mov edx, 7 24 | call 0x432b40 25 | mov ecx, eax 26 | xor eax, eax 27 | ret 0 28 | ``` 29 | 30 | If you have never seen assembly code before, this will probably look completely alien to you. It might leave you scratching your head and asking many questions, from what happened to our variables to where the function name went. Let's see if we can understand it better by turning it into **C++-like pseudo-code**: 31 | ```cpp 32 | void 0x432b40() { 33 | eax = ecx; 34 | eax += edx; 35 | return; 36 | } 37 | 38 | void _main() { 39 | ecx = 5; 40 | edx = 7; 41 | 0x432b40(); 42 | ecx = eax; 43 | } 44 | ``` 45 | 46 | The first thing to notice is that after compilation, each function is given a **memory address**; that is, an address within the binary code where the function can be found. The second thing is that **in binary code, there are no names**. There are only memory addresses, **instructions**, and **registers**. Functions technically do not even exist in binary code; in there, they are called **subroutines**, and are referenced by their memory address. 47 | 48 | The second thing to notice is that our variables have turned into **register assignments**. Registers are like **global variables** in C++; they can be assigned to from anywhere. The actual registers that may appear in the assembly code are **predefined by the assembly language being used**. In **x86**, some of them include `ecx`, `edx`, `epx`, and `xmm0`. 49 | 50 | The memory addresses of functions are **not constant** between different compilations; what this means in practice for GD mods is that **a mod that works for GD version 2.113 does not work for version 2.112 or 2.2**, as the memory addresses of the functions are completely different. However, they are constant within copies of the **same compiled binary**; if you find a function's address in the GD 2.113 binary, you can use that address in your mod and it will work for anyone with GD 2.113 installed. 51 | 52 | > :warning: TODO: Rest of the damn handbook 53 | -------------------------------------------------------------------------------- /handbook/vol2/index.md: -------------------------------------------------------------------------------- 1 | # Volume 2: Reverse Engineering 2 | 3 | This volume of the handbook covers reverse engineering. 4 | -------------------------------------------------------------------------------- /handbook/vol4/chap4_1.md: -------------------------------------------------------------------------------- 1 | # Chapter 4.1: How Do You The Mod 2 | 3 | Given that you are reading this handbook, you probably have some big ideas for mods you want to make. Maybe you want to be the next Absolute and make an awesome mod menu. Maybe you're not satisfied by how long BetterEdit is taking and want to create your own editor mod. Maybe you have something completely original planned. Whatever it is, it is important to understand how exactly you could approach turning your idea into reality. 4 | 5 | Now, of course, it is obligatory to note that **there is no right or wrong way to make a mod**. There's no absolute do-this-and-youll-succeed process that is the same and works for every mod. Some mods require more effort than others, depending on a practically infinite amount of factors. However, this Chapter attempts to divide modding in general into 5 steps. Some may look at these steps and conclude that this is so generic it's practically useless, but it's good to remind ourselves now and then what it is we are actually doing. 6 | 7 | ## Step 1: Concretize Your Idea 8 | 9 | **The first step is to really concretize your idea.** Often times, the ideas for the mods you want to make can be quite grandiose, like "I want to make GD more optimized" or "I want to make the editor better". Unfortunately, figuring out where to start actually working on the mod can prove to be a problem with large-scale ideas. In cases like these, you should try to **break the problem down into smaller parts**, like "I want to fix the scale controls in the editor being unable to rescale multiple objects at once" or "I want to add a 'Select by Group ID' button". Break down your idea into smaller parts, until you have something tangible you can actually start approaching. 10 | 11 | ## Step 2: Reverse Engineer 12 | 13 | After figuring out what it is that you are actually going for, it's time to look into if it is even possible. For that, you will need to reverse engineer the game. If you want to fix some bugs in GD, first try to figure out what causes the bug. Is there a consistent setup or method to causing it? What classes and functions are related to this? Does it seem like a rounding error or something more severe? 14 | 15 | ## Step 3: Write Some Code 16 | 17 | Once you have an idea on what might be a route to achieving what you want, write some code to try it out. If you don't know the exact magic words to write, refer to documentation and [ask around for help](https://discord.gg/AWWCUUfeA7). 18 | 19 | ## Step 4: Test 20 | 21 | Once you have some code that compiles, the next step is to simply try it out. Load your mod into the game and see if it works. Send it to your friends to verify it works for them too. Make sure that it works all of the time, and that it doesn't have any unexpected side effects, like the game crashing after a while due to a memory leak. 22 | 23 | ## Step 5: Repeat ad nauseam 24 | 25 | If your code doesn't work, reverse engineer a bit more to find out why, write some new code, and test that. Once you finish one feature, you may find yourself coming up with ideas for new ones, and so you can reverse engineer for those, write some code and test. Repeat basically forever until you have a finished product, or you get tired and start the next hobby project. 26 | 27 | --- 28 | 29 | These may seem like completely dull and uninteresting steps for some, and perhaps like a glorified [WikiHow](https://www.youtube.com/watch?v=PSKQ3ZNQ_O8) article, but the point of that is to illustrate that there really is no specific process for making a mod. For example, reverse engineering may involve using Ghidra and IDA to analyze functions, or it may involve just looking around with DevTools. The code you write may be some in-depth template-filled C++ behemoth, or it may just be a few quick lines of test code. Testing may involve a larger group or just be you realizing you forgot to call `retain` on an array. Modding isn't an exact science; it's an art. 30 | 31 | Even then, there are many important skills and tools every modder should get acquainted with, and in the next chapter we will look at one of the most important ones: [Reverse Engineering](/handbook/vol2/chap2_2.md) 32 | -------------------------------------------------------------------------------- /handbook/vol4/index.md: -------------------------------------------------------------------------------- 1 | # Volume 4: Project 2 | 3 | This volume of the handbook goes through an example mod project. 4 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | ![Geode Logo](https://github.com/geode-sdk.png?size=80) 2 | 3 | # Geode SDK 4 | 5 | **Geode** is a [Geometry Dash](https://store.steampowered.com/app/322170/Geometry_Dash/) mod loader and modding SDK with a modern approach towards mod development. Unlike previous mod loaders, which merely inject the DLLs and let devs handle the rest, Geode aims to be a more comprehensive project, which manages loaded mods & hooks itself. Geode has been built to ensure performance, compatibility, portability and ease of use. For devs, Geode means easy development and portability; for end users, Geode means an uniform and easy experience using mods. 6 | 7 | > :warning: These docs are intended for **developers** looking to use Geode. If you're someone who would just like to use mods, [see our homepage](https://geode-sdk.org/install). 8 | 9 | ## Why Geode? 10 | 11 | The main goal of Geode is to **end mod incompatibility**. Traditional modding leads very easily to compatability problems, many of which Geode attempts to address with better solutions. 12 | 13 | On top of this, and perhaps more interestingly, **Geode provides much better ergonomics for modding**. Instead of having to deal with calling conventions, trampolines, manually setting hooks (likely in another source file), you can have all the code relevant to hooks in a [nice, clean, readable syntax](/tutorials/modify.md) contained within a single source file. 14 | 15 | ## Help, Contributing, Etc. 16 | 17 | If you need help using Geode or would like to contribute, feel free to join our [Discord Server](https://discord.gg/9e43WMKzhp). 18 | 19 | Alternatively, if you hate joining Discord servers to work with a framework, you can also just **open up an issue or pull request** on any [Geode repository on Github](https://github.com/orgs/geode-sdk/repositories). 20 | 21 | ## Getting Started 22 | 23 | See [Getting Started](/getting-started) for a step by step tutorial on getting started with Geode SDK. 24 | 25 | See [Handbook](/handbook/chap0) for a beginner-friendly tutorial series on using Geode and GD Modding in general (WIP!!). 26 | 27 | ## Credits 28 | 29 | ### Contributors 30 | 31 | 32 | 33 | 34 | 35 | ### Special Thanks 36 | 37 | * [NachoBIT](https://github.com/TheNachoBIT) 38 | * [RobTop Games](https://twitter.com/RobTopGames/) for making this amazing game and providing us and so many others with hours of entertainment ❤ 39 | -------------------------------------------------------------------------------- /misc/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Miscellaneous 3 | order: 6 4 | --- 5 | 6 | These pages contain miscellaneous but useful information, that does not fit elsewhere. -------------------------------------------------------------------------------- /misc/ios.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: iOS Development Setup 3 | --- 4 | 5 | # iOS Development Setup 6 | 7 | You will have to edit your `CMakeLists.txt` on your exiting mods. Newer created mods will already have this change 8 | ```cmake 9 | # At the beginning of your CMakeLists.txt, change this: 10 | # set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") 11 | # into: 12 | if ("${CMAKE_SYSTEM_NAME}" STREQUAL "iOS" OR IOS) 13 | set(CMAKE_OSX_ARCHITECTURES "arm64") 14 | else() 15 | set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") 16 | endif() 17 | ``` 18 | 19 | ## Build 20 | 21 | To build mods for iOS, you must have Mac OS with the iPhone SDK installed from Xcode. 22 | 23 | You must also get the Geode binaries for iOS, you can do this using the CLI: 24 | ```bash 25 | geode sdk install-binaries --platform ios 26 | ``` 27 | 28 | SDK Version 4.4.0 or higher is required, so make sure to update your SDK first. 29 | 30 | Now you can build your mod for iOS via: 31 | ```bash 32 | geode build -p ios 33 | ``` 34 | Or if you want to build manually: 35 | ```bash 36 | cmake -B build -DCMAKE_SYSTEM_NAME=iOS -DGEODE_TARGET_PLATFORM=iOS -DCMAKE_BUILD_TYPE=RelWithDebInfo 37 | cmake --build build 38 | ``` 39 | 40 | ## Github Actions 41 | To build your mod for iOS using `geode-sdk/build-geode-mod`, you have to add iOS to the build matrix, like so: 42 | ```yml 43 | - name: iOS 44 | os: macos-latest 45 | target: iOS 46 | ``` 47 | 48 | ## Web Server 49 | 50 | If your iOS device is not jailbroken, it is generally recommended to follow this section to enable the web server. 51 | 52 | > :warning: The web server for the TrollStore version of the launcher will not work properly. It is recommended to use `scp` to upload your mods instead. 53 | 54 | The web server is a feature bundled into the **Geode Launcher** that allows you to easily upload mods without needing to use iTunes. This eliminates the need to manually drag your geode mod into the mods directory, making the mod testing process more simpler. 55 | 56 | To activate the web server: 57 | 1. Open the launcher and navigate to settings 58 | 2. Scroll down until you reach the **About** section 59 | 3. Hold on the **iOS Launcher** text for at least 3 seconds until a popup appears prompting you to enable **Developer Mode** 60 | 4. Tap **Yes**, then scroll all the way down to find **Web Server** 61 | 5. Enable the setting and restart the launcher 62 | ![Image showing the steps above but a visualization of it](/assets/Misc_iOS_webserver-steps.png) 63 | 64 | ### Using the Web Server 65 | 66 | After restarting the launcher, navigate to `http://[Your Device IP]:8080` in your browser on your computer, replacing `[Your Device IP]` with the local IP address of your iOS device. 67 | 68 | Now you should see a web interface for the launcher in your browser! 69 | ![Image showcasing the web interface on browser](/assets/Misc_iOS_webserver-ui1.png) 70 | 71 | Through this web interface, you can: 72 | - Upload your mods 73 | - Launch the game 74 | - View in-game logs if the game has been launched 75 | 76 | As an example, this is what the web interface would look like when the game is launched: 77 | ![Image showcasing the web interface on browser but with game logs](/assets/Misc_iOS_webserver-ui2.png) 78 | 79 | ### Other Upload Method 80 | 81 | If you prefer not to upload your mods through your browser, you can use a script to upload your mod to your device, and launch the game immediately after you compile and upload your mod: 82 | ```bash 83 | # Build commands here... 84 | 85 | # Replace the Example ID with your Mod ID, or the path to your compiled geode file. This is assuming the CWD is the project. 86 | GEODE_MOD="./build-ios/my.example-mod.geode" 87 | 88 | # Replace the device URL with your device's local IP address 89 | DEVICE_URL="http://192.168.0.25:8080" 90 | curl -X POST -F "file=@${GEODE_MOD}" "${DEVICE_URL}/upload" 91 | 92 | # Including this curl request is optional. 93 | curl -X POST "${DEVICE_URL}/launch" 94 | ``` 95 | -------------------------------------------------------------------------------- /mods/configuring.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: How to configure Geode mods using mod.json 3 | --- 4 | 5 | # Configuring mods 6 | 7 | Geode mods are configured through a file called `mod.json`, located at the root of your project. The file contains general information about the mod, such as the name, ID, version, and also things like settings, resources, and dependencies. 8 | 9 | If you're using VS Code, you should [install the Geode VS Code extension](https://marketplace.visualstudio.com/items?itemName=GeodeSDK.geode), as it provides automatic checking for `mod.json` and intellisense for what keys are available. 10 | 11 | ## Required settings 12 | 13 | There are 6 required properties for every mod: the target Geode version, the ID and name of the mod, its developer, and its version. 14 | 15 | ```json 16 | { 17 | "geode": "v1.0.0", 18 | "gd": { 19 | "win": "2.206", 20 | "android": "2.206" 21 | } 22 | "id": "my.awesome-mod", 23 | "name": "My Awesome Mod", 24 | "version": "v1.0.0", 25 | "developer": "Me" 26 | } 27 | ``` 28 | You can also use the `developers` property instead of `developer` 29 | 30 | ```json 31 | { 32 | "geode": "v1.0.0", 33 | "gd": { 34 | "win": "2.206", 35 | "android": "2.206" 36 | } 37 | "id": "my.awesome-mod", 38 | "name": "My Awesome Mod", 39 | "version": "v1.0.0", 40 | "developers": ["Me","Friendly Developer"] 41 | } 42 | ``` 43 | 44 | ## List of keys 45 | 46 | ### `geode` 47 | 48 | The target Geode version. Should be in the format of an exact version, such as `v1.0.0` or `v1.2.0`. The target version should always be the exact version of Geode you are developing with. 49 | 50 | ### `gd` 51 | 52 | The target Geometry Dash version exactly, or `*` for **any** GD version (whether your mod actually works on any GD version is your responsibility.) 53 | 54 | This key is an object for specifying per platform GD version: 55 | ```json 56 | { 57 | "gd": { 58 | "android": "2.200", 59 | "win": "2.204" 60 | } 61 | } 62 | ``` 63 | The valid platform keys are `win`, `mac`, `android` and `ios`. 64 | ```json 65 | { 66 | "gd": { 67 | "android": "*" // Mod works on any android gd version 68 | } 69 | } 70 | ``` 71 | 72 | 73 | ### `id` 74 | 75 | The ID of the mod. May only contain lowercase ASCII characters (`a-z`), periods (`.`), underscores (`_`) and dashes (`-`). Otherwise may be formatted in any way you like, but it is common to make the ID be in the form of `developer.mod-name`. The ID is used to uniquely identify the mod in things such as folder names, URLs, and save data. 76 | 77 | ### `name` 78 | 79 | The name of the mod that is displayed in-game, and in other situations where the mod is displayed. May be any UTF-8 valid string, however do note that GD does not normally support many characters beyond the ASCII character set, so unless the user has a mod or texture pack that extends the character set, not all characters in the name may be displayed. 80 | 81 | ### `version` 82 | 83 | The version of the mod; should follow [semver](https://semver.org), especially if the mod can be used as a dependency (see [the API key](#api)). 84 | 85 | ### `developer` 86 | 87 | The name of the mod's developer, displayed on the Installed tab. Should be a single name, like "HJfod" or "Alk". If the mod has multiple developers, this should be a team name like "Geode Team". 88 | 89 | ### `developers` 90 | 91 | The name of the mod's developers. Replaces `developer`. Can be a single name, like \["HJfod"\] or \["Alk"\]. If the mod has multiple developers, you should list the names of each developer because, the `developers` property is a list/array. First developer listed is the main developer. If you have 3 or more developers listed, it will show `main developer + 2 more`. If you have 2 developers listed, it will show `Developer 1 & Developer 2`. If you list multiple developers, when someone clicks on your name(s), it will show the list of the developers, then you can click on one to see other mods they have made or are apart of. 92 | 93 | ### `description` 94 | 95 | A short description of the mod. Should only be a single sentence; for longer descriptions, see [about.md](/mods/md-files.md). 96 | 97 | ### `repository` 98 | 99 | The Git repository of the mod. 100 | 101 | ### `issues` 102 | 103 | Describes where users can report problems with the mod. Value is an object with the following properties: 104 | 105 | * `info` - Free-form description of where / whom to report issues to 106 | 107 | * `url` - URL to a Discord server, GitHub repository etc. where the issues are reported 108 | 109 | ### `dependencies` 110 | 111 | The dependencies of a mod; see [Dependencies](/mods/dependencies.md) for details 112 | 113 | ### `incompatibilities` 114 | 115 | The incompatibilities of a mod. Very similar to [dependencies](/mods/dependencies.md) but the valid importances are `breaking`, `conflicting` and `superseded`. 116 | 117 | * `breaking` - prevents the incompatible mod from loading 118 | * `conflicting` - both mods load anyway but it shows a warning in mod list 119 | * `superseded` - only used in very special circumstances when your mod is meant to show up as an update to a different mod 120 | 121 | ### `settings` 122 | 123 | The settings of a mod; see [Settings](/mods/settings.md) for details 124 | 125 | ### `resources` 126 | 127 | The resources of a mod; see [Resources](/mods/resources.md) for details 128 | 129 | ### `early-load` 130 | 131 | If true, specifies that this mod must have finished loading before the loading screen appears in GD. For example, [TextureLdr](https://github.com/geode-sdk/textureldr) uses this to apply texture packs at startup. 132 | 133 | ### `api` 134 | 135 | Specifies that this mod can be [used as a dependency](/mods/dependencies.md). Value is an object with the following properties: 136 | 137 | #### `headers` 138 | 139 | An array specifying the list of headers that should bundled with this mod. Supports [globbing](/mods/resources.md). 140 | 141 | ### `tags` 142 | 143 | A list of tags to categorize the mod. A mod can have any amount of tags, but between 1-4 is the recommended amount. 144 | 145 | | Tag name | Description | 146 | |----------|-------------| 147 | | `universal` | The mod affects the entire game | 148 | | `gameplay` | The mod affects mainly gameplay | 149 | | `editor` | The mod affects mainly the editor | 150 | | `offline` | The mod does not require an internet connection to work | 151 | | `online` | The mod requires an internet connection to work | 152 | | `enhancement` | The mod enhances (adds more) to an existing GD feature | 153 | | `music` | The mod deals with music, such as adding more songs | 154 | | `interface` | The mod modifies the GD UI in notable ways (beyond just adding a new button) | 155 | | `bugfix` | The mod fixes existing bugs in the game | 156 | | `utility` | The mod provides tools that simplify working with the game and its levels | 157 | | `performance` | The mod optimizes existing GD features | 158 | | `customization` | The mod adds new customization options to existing GD features | 159 | | `content` | The mod adds new content (new levels, gamemodes, etc.) | 160 | | `developer` | The mod is intended for mod developers only | 161 | | `cheat` | The mod adds cheats like noclip | 162 | | `paid` | The mod contains paid content, like a Pro tier, or if the mod acts as an installer for a fully paywalled mod | 163 | | `joke` | The mod is a joke. See [the docs](/mods/guidelines#joke-mods) for what joke mods are considered to be "meaningful" | 164 | -------------------------------------------------------------------------------- /mods/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: A collection of tutorials for working with Geode mods 3 | order: 3 4 | --- 5 | 6 | # Geode Mods 7 | 8 | These are a collection of tutorials for working with Geode mods; how to setup your developer environment, how to add settings, dependencies, etc. 9 | -------------------------------------------------------------------------------- /mods/md-files.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Special Markdown Files 3 | description: How to use about.md and other Markdown files in a Geode mod 4 | --- 5 | 6 | # `about.md` 7 | 8 | Geode mods can specify a long, free-form description typeset using Markdown by including a file named `about.md` at the root of their project. See [the MDTextArea class](/classes/geode/MDTextArea) for information on what features of Markdown are supported. 9 | 10 | This file is similar to a README, however **it's intended for people who use the mod**; you should also keep a README for developers interested in building / contributing to your mod. 11 | 12 | > :information_source: As of [CLI v3.5.0](https://github.com/geode-sdk/cli/releases/tag/v3.5.0), if a `README.md` is present but not an `about.md`, the readme will be used as the mod description. \ 13 | > If you don't want this behavior, then make an `about.md` file like usual. 14 | 15 | 16 | ## Example 17 | 18 | ```md 19 | # My Awesome Mod 20 | 21 | This is my awesome mod, that does **all** the awesome stuff! 22 | 23 | If you like this mod, please check [my other mod](mod:my.other-mod)! 24 | 25 | ## Credits 26 | 27 | [Join my Discord](https://discord.gg/K9Kuh3hzTC)! Thanks to [Hu Tao](https://www.youtube.com/watch?v=8oap-n_OEgc) for helping with the mod! 28 | ``` 29 | 30 | # `changelog.md` 31 | 32 | A markdown file that contains a changelog for your mod. Geode uses this file to show release notes for the mod. 33 | 34 | ## Example 35 | 36 | ```md 37 | # v1.1.0 38 | 39 | * Removed Herobrine 40 | 41 | # v1.0.1 42 | 43 | * Fixed bugs 44 | 45 | # v1.0.0 46 | 47 | * Initial release 48 | ``` 49 | 50 | # `support.md` 51 | 52 | A markdown file that contains free-form information about how to support you, the mod's developer. This may include things like links to your Patreon, PayPal, Ko-Fi, etc. - anything you wish to include! 53 | 54 | ```md 55 | # Thank You for using Rattledasher 5000! 56 | 57 | If you like this mod and would like to support me, I have an [OnlyFans](https://globed.dev/soggy/)! You can see me rattle my dashes like a pro if you know what I mean ;) 58 | ``` 59 | -------------------------------------------------------------------------------- /mods/publishing.md: -------------------------------------------------------------------------------- 1 | # Publishing Geode Mods 2 | 3 | Once your awesome mod is finished, it's time to publish it for all the world to see! Geode comes with an in-game "Download" section where users can download mods from, which gets its content from [the Geode Index](https://api.geode-sdk.org/v1/mods). 4 | 5 | ## Getting Your Mod on the Repo 6 | 7 | > :warning: All mods submitted on the index **must provide the source code**! If your mod is open source, just include a link to a Github repository or equivalent. For closed source mods, see [the dedicated section](#what-about-closed-source-mods) 8 | 9 | Submitting a mod to the official mod index is as follows: 10 | 11 | 1. Make sure you have the [**latest** CLI](/getting-started/geode-cli) set up. 12 | 2. Build and release your mod somewhere - we highly recommend using GitHub releases, as this provides a straight-forward way to deal with versioning. 13 | - Do **NOT** replace existing uploaded versions! This will change the hash and thus users will be unable to download the old version. 14 | 4. Login to the index using the **CLI**: `geode index login`. This will prompt you to login using your GitHub account. 15 | 5. Run `geode index mods create` 16 | 6. Provide a **direct download link** to the .geode file (for example `https://github.com/HJfod/BetterEdit/releases/download/v6.3.3/hjfod.betteredit.geode`) 17 | 7. An **index admin** will have to validate that your mod meets the [index guidelines](/mods/guidelines) and approve your mod. 18 | 19 | ## Releasing updates 20 | 21 | To release an update, use `geode index mods update`. You will have to be already logged in with the index to use this command. 22 | 23 | If you are using GitHub releases (or any other system), **do not update an existing release** - create a new one instead. Updating an existing release **will break that version of the mod**, as the Geode package is checksummed. 24 | 25 | Make sure to **increase your mod version** when updating it! You should be following [Semantic Versioning](https://semver.org), especially if you're developing a mod with a **public API**. 26 | 27 | If you are a **verified** developer, then the update will automatically be accepted onto the index, without the need of approval from an **index admin**. 28 | 29 | ## What are **verified** developers? 30 | 31 | Being a verified developer has the sole benefit of not requiring your mod updates to be checked by an **index admin** (and a cool role on the Discord server). **Newly created mods** will still have to go through the verification process. Of course, this means that we do not give the verified status easily. You can become a verified developer by having good quality mods (or mod, you don't have to upload tens of them), proving that you are trustworthy, and being an overall helpful figure in the modding community. 32 | 33 | Once you have your verified status, try and uphold the same quality standards you were keeping before getting the role. Having no update checks comes with the temptation of releasing updates **faster** and "figuring it out later if it breaks", which is not a mindset you should be having. Consistently having incidents where you release a broken update **will** lead to your verified status being removed. It also goes without saying that if you release mods / updates that break the **ban rules**, there will be consequences. 34 | 35 | ## What about closed source mods? 36 | 37 | Even if your mod is **closed source**, you still need to submit the source code for verification. Do so by asking [someone who can approve new mods](#who-can-approve-mods) on the index repository and send them the source code privately, for example by adding them temporarily as a contributor to your private repository. 38 | 39 | **Geode developers will never leak or steal your source code!** If you find that the person who verified your mod has breached this vital level of trust, **do let the other Geode lead developers know immediately**. 40 | 41 | ## What about paid mods? 42 | 43 | Paid mods may be published on the index, as long as they follow the [guidelines for paid mods](/mods/guidelines#paid-mods). Do note that the rules for closed source mods still apply to paid mods; paid mods must submit their source code for verification, including any extra closed-source components that are downloaded at runtime. 44 | 45 | ## Who can approve mods? 46 | 47 | **New mods** and **updates** for mods can be verified by **lead developers** and **index admins**. Additionally, anyone with the `verified` priviledge can automatically submit new mods / update their own mods without needing to wait for a staff member to verify it. 48 | -------------------------------------------------------------------------------- /mods/resources.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: How to include resources in Geode mods 3 | --- 4 | 5 | # Resources 6 | 7 | Geode mods may include any number of resources/assets in the mod package. Geode also provides utilities for automatically **generating spritesheets**, **turning TTF fonts to Bitmap ones**, and **creating Medium/Low versions of sprites**. 8 | 9 | In order for Geode to create the resources, [you must have Geode CLI installed](/geode/installcli). 10 | 11 | ## Adding sprites 12 | 13 | Adding sprites is as simple as adding the PNG files somewhere in your mod's source directory (commonly under `resources`) and then adding them to your [mod.json](/mods/configuring.md). 14 | 15 | Geometry Dash needs all sprites to have a High, Medium, and Low quality version for performance. **Geode assumes that the source sprites you provide are UHD quality, and creates the medium / low versions automatically.** You should only include the high quality version of your sprite in resources. 16 | 17 | Example for adding sprites: 18 | 19 | ```json 20 | { 21 | "resources": { 22 | "sprites": [ 23 | "resources/MySprite.png", 24 | "resources/other-sprite.png" 25 | ] 26 | } 27 | } 28 | ``` 29 | 30 | You can now use the sprite in-game by using its name, just like any other sprite. Note that **Geode automatically prefixes all sprites to avoid name collisions**, so the actual name of the sprite in code is `my.mod-id/MySprite.png`. For convenience, Geode provides a `_spr` string literal extension that automatically adds this prefix: 31 | 32 | ```cpp 33 | auto spr = CCSprite::create("MySprite.png"_spr); 34 | ``` 35 | 36 | Additionally, Geode supports **globbing**, so you can include all of the PNGs from a folder at once: 37 | 38 | ```json 39 | { 40 | "resources": { 41 | "sprites": [ 42 | "resources/*.png" 43 | ] 44 | } 45 | } 46 | ``` 47 | 48 | ## Adding spritesheets 49 | 50 | Spritesheets are **better for performance** than ordinary sprites, so it's recommended to put as many of your sprites in a spritesheet as you can. Geode requires all spritesheets to have names; the name should be written in [PascalCase](https://techterms.com/definition/pascalcase). 51 | 52 | ```json 53 | { 54 | "resources": { 55 | "spritesheets": { 56 | "MySheet": [ 57 | "resources/sheet/*.png" 58 | ], 59 | "OtherSheet": [ 60 | "resources/other/*.png" 61 | ] 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | After adding the sheet and its sprites to your `mod.json`, you can now use the sprite in code as normal: 68 | 69 | ```cpp 70 | auto spr = CCSprite::createWithSpriteFrameName("mySpriteInASheet.png"_spr); 71 | ``` 72 | 73 | ## Adding fonts 74 | 75 | Geode can automatically convert TTF and OTF fonts into GD-compatible Bitmap fonts. You can add fonts by adding them under the `fonts` key in `resources`: 76 | 77 | ```json 78 | { 79 | "resources": { 80 | "fonts": { 81 | "MyFont": { 82 | "path": "resources/myFont.ttf", 83 | "size": 80 84 | }, 85 | "OtherFont": { 86 | "path": "resources/other.ttf", 87 | "size": 64, 88 | "charset": "32-500,8226" 89 | } 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | The same `_spr` suffix for sprites can be used for fonts aswell: 96 | 97 | ```cpp 98 | auto label = CCLabelBMFont::create("Hi mom!", "MyFont.fnt"_spr); 99 | ``` 100 | 101 | The `path` key in a font is a path to the font file. The `size` key is the size of the rendered font characters; this may be any positive number, and a reasonable value for it is whatever is large enough to look good in-game. If the size is too small, the font may look blurry. The larger the size, the larger the font though. 102 | 103 | The `charset` key specifies the Unicode codepoints of which characters should be included in the font. GD fonts include `32-126,8226`, which are ASCII letters with a few punctuation marks included. Ranges can be specified with a dash and codepoints should be separated with a comma. 104 | 105 | There is also an experimental key `outline` that adds a black outline around the font characters and a small shadow, to make it similar to other GD fonts. As this feature is experimental, be aware that the output quality may vary. 106 | 107 | ## Adding audio & other files 108 | 109 | Any other files can be included through the `files` key: 110 | 111 | ```json 112 | { 113 | "resources": { 114 | "files": [ 115 | "resources/audio/trollface.ogg", 116 | "resources/scripts/*.js" 117 | ] 118 | } 119 | } 120 | ``` 121 | -------------------------------------------------------------------------------- /mods/savedata.md: -------------------------------------------------------------------------------- 1 | # Saving data 2 | 3 | Pretty much every mod will eventually enter the point where it needs to save some data on the user's machine, such as configuration settings or other user-customizable values. Geode has built-in support for **two kinds of savedata**: **user-editable settings** and **non-editable saved values**. 4 | 5 | Settings are covered [in their own tutorial](/mods/settings.md) - **this tutorial is about non-user-editable save data**. 6 | 7 | Unlike settings, save data does not need to be declared in `mod.json`, or anywhere else for that matter. Save data is automatically brought to life when you set it for the first time. Save data is, as the name implies, saved when the game is closed, and its previous state loaded back up the next time the game is opened. 8 | 9 | You can save any type of value as long as it implements `matjson::Serialize` (see the [STL container implementations](https://github.com/geode-sdk/json/blob/main/include/matjson/stl_serialize.hpp) for an example). 10 | 11 | ## Setting & getting save data 12 | 13 | You can set save data using [setSavedValue](/classes/geode/Mod#setSavedValue) 14 | 15 | ```cpp 16 | Mod::get()->setSavedValue("my-saved-value", .5f); 17 | Mod::get()->setSavedValue("my-other-value", "Garlagan!!!"); 18 | 19 | struct MyCustomSaveData { 20 | int x; 21 | int y; 22 | }; 23 | 24 | template<> 25 | struct matjson::Serialize { 26 | static MyCustomSaveData from_json(matjson::Value const& value) { 27 | return MyCustomSaveData { 28 | .x = value["x"].as_int(), 29 | .y = value["y"].as_int() 30 | }; 31 | } 32 | 33 | static matjson::Value to_json(MyCustomSaveData const& value) { 34 | auto obj = matjson::Object(); 35 | obj["x"] = value.x; 36 | obj["y"] = value.y; 37 | return obj; 38 | } 39 | }; 40 | 41 | Mod::get()->setSavedValue("data", MyCustomSaveData { .x = 5, .y = 2 }); 42 | ``` 43 | 44 | You can similarly get the current saved value using [getSavedValue](/classes/geode/Mod#getSavedValue). If the value has not been saved, a default value can be explicitly provided, or otherwise a default value will be created by invoking the save value's default constructor: 45 | 46 | ```cpp 47 | auto value = Mod::get()->getSavedValue("my-saved-value"); 48 | auto data = Mod::get()->getSavedValue("data", MyCustomSaveData { .x = 0, .y = 0 }); 49 | ``` 50 | 51 | As a useful little tip, `setSavedValue` returns the previous saved value, and if there is no previous saved value, default constructs it and returns that. You can use this knowledge, and combined with the knowledge that the default constructor for `bool` is false, to write one-time info popups using a single self-contained if statement: 52 | 53 | ```cpp 54 | if (!Mod::get()->setSavedValue("shown-upload-guidelines", true)) { 55 | FLAlertLayer::create( 56 | "Upload guidelines", 57 | "You are only allowed to post pictures of cute catgirls", 58 | "OK" 59 | )->show(); 60 | } 61 | ``` 62 | 63 | This popup is only shown the first time the user enters it, and never again. 64 | 65 | ## Saving other data 66 | 67 | Mods should save other data (files, backups, etc.) to their specific save directory, which they can access with `Mod::get()->getSaveDir()`. Using Geode's provided directories ensures that when the user uninstalls the mod, Geode can properly uninstall all its data. Geode's provided directories are also guaranteed to be readable and writable on all platforms. 68 | 69 | It should be noted that a mod can have a good reason to save data elsewhere - for example a mod that saves created levels as individual files instead of CCLocalLevels would be justified in saving directly under the GD save folder instead of the mod save folder, since the data its saving is not related to the mod. 70 | 71 | If you have data that the user should be able to edit (for example config files), these should go in the directory provided by `Mod::get()->getConfigDir()`. **Do not flood the main GD folder with config files or save data** - this will almost certainly get your mod rejected from the index unless you have a _very_ good reason for doing so! 72 | -------------------------------------------------------------------------------- /source/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Source 3 | order: 5 4 | --- 5 | 6 | These pages contain info about the Geode codebase itself. -------------------------------------------------------------------------------- /tutorials/casting.md: -------------------------------------------------------------------------------- 1 | # Pointer Casting 2 | 3 | The usual C++, with its type casting and all. But how is this related to GD modding? Well, a lot of things. Delegates, object oriented nature of Cocos, and so on... Let's explore a few examples. 4 | 5 | ## Known original type 6 | 7 | Let's say you have a callback, and in that you want to downcast the sender. You already know the sender type. What should you use? 8 | 9 | ```cpp 10 | void MyClass::onClick(CCObject* sender) { 11 | auto node = static_cast(sender); 12 | node->setPosition({0, 0}); 13 | } 14 | ``` 15 | 16 | You are expected to use static cast when downcasting the objects you already know the type of. 17 | 18 | ## Unknown original type 19 | 20 | You need to downcast an object, but you don't know its original type. Using your existing C++ knowledge, you might say "Dynamic cast!", but no. Well, not on Windows or MacOS. Since we do not have symbols in those platforms, the compiler recreates typeinfos for the GD classes, and those classes can not match with the ones found in the GD binary. This is why we have this utility called `geode::casts::typeinfo_cast`, which is literally a recreation of `dynamic_cast` that uses typeinfo name to compare the types. The usage is the exact same, but here is an example anyway: 21 | 22 | ```cpp 23 | void MyClass::onClick(CCObject* sender) { 24 | if (auto button = typeinfo_cast(sender)) { 25 | button->setSizeMult(1.2f); 26 | } 27 | } 28 | ``` 29 | 30 | ## Callbacks 31 | 32 | If you tried to make a button, chances are you used `menu_selector`. But what does that do? It reinterpret casts your member function into a `void (CCObject::*)(CCObject*)`. Since it reinterpret casts it, you should never give it an invalid function, like a `void MyClass::onClick(int)`. It will compile, and it might *techincally* work, but this is very bad. Don't do it. -------------------------------------------------------------------------------- /tutorials/fetch.md: -------------------------------------------------------------------------------- 1 | # Making web requests 2 | 3 | Making web requests in C++ sucks, and GD doesn't really help with it. Cocos2d has the `CCHTTPRequest` class, which GD uses to make its web requests, but it's a complicated mess to try to get working and it's not really ergonomical. 4 | 5 | To fix this, Geode has the `WebRequest` API, which is much more versatile. All web requests are **asynchronous**, so they run on a separate thread. 6 | 7 | Under the hood, the `WebRequests` API uses [Tasks](/tutorials/tasks), so make sure you have read that guide before going into this one. 8 | 9 | ## Creating a web request 10 | 11 | Creating a web request is as simple as creating a new `WebRequest` object, and setting some params on it. 12 | 13 | ```cpp 14 | #include 15 | 16 | web::WebRequest req = web::WebRequest(); 17 | 18 | // Let's add some query params 19 | req.param("one", "two"); 20 | req.param("three", "four"); 21 | 22 | // Maybe set our UserAgent? 23 | req.userAgent("Geode Web Request!"); 24 | 25 | // For POST / PUT requests, we should probably send a body. Choose whichever suits your needs 26 | 27 | // Byte vector (std::vector) 28 | req.body({ 0x23, 0x24, 0x25 }); 29 | // Strings 30 | req.bodyString("Hello, server!"); 31 | // JSON (uses any matjson::Value) 32 | // Note that you should probably have some actual data in your json 33 | auto myjson = matjson::Value(); 34 | req.bodyJSON(myjson); 35 | 36 | // Let's set some headers, shall we? 37 | req.header("Content-Type", "application/json"); 38 | 39 | // Set a timeout for the request, in seconds 40 | req.timeout(std::chrono::seconds(30)); 41 | ``` 42 | 43 | There are more functions, regarding certificate verification, proxy settings. You can find more info about them by looking through the headers! 44 | 45 | Now, let's send out request. The API has specific methods for POST, GET, PUT, PATCH. If you want to send a different method, you can use `.send()`. Calling these functions will return a **WebTask**, which is just an alias for `Task`. 46 | 47 | ```cpp 48 | // We're still using the same 'req' object from the codeblock above... 49 | 50 | std::string url = "https://example.org"; 51 | 52 | // Let's do a POST request 53 | auto task = req.post(url); 54 | ``` 55 | 56 | ## Getting the response from our request 57 | 58 | If you remember the [Tasks](/tutorials/tasks) tutorial, you probably also remember that Tasks have to be listened to using the [Events](/tutorials/events) system. We can create an `EventListener` for our request and use it to get our response. 59 | 60 | ```cpp 61 | class MyCoolClass { 62 | EventListener m_listener; 63 | }; 64 | 65 | // Continuing our code from above... 66 | 67 | m_listener.bind([] (web::WebTask::Event* e) { 68 | if (web::WebResponse* value = e->getValue()) { 69 | // The request finished! 70 | } else if (web::WebProgress* progress = e->getProgress()) { 71 | // The request is still in progress... 72 | } else if (e->isCancelled()) { 73 | // Our request was cancelled 74 | } 75 | }); 76 | m_listener.setFilter(task); 77 | ``` 78 | 79 | > :warning: Note that if the task itself goes out of scope, then the request will cancel. If the listener goes out of scope, then the callback will never be called, hence if you want to replace your request, you should cancel the current one, and call setFilter() on the listener instead of creating a new one. 80 | 81 | Once we receive our response, we can check multiple things on it! You can see the list of everything you can do with intellisense, or by checking the headers. 82 | 83 | ```cpp 84 | // Check if the response status code is 2xx 85 | res->ok(); 86 | // Get the actual status code 87 | res->code(); 88 | // Get a specific header 89 | res->header("Content-Type"); 90 | // Get all headers 91 | res->headers(); 92 | // Get the response body as bytes 93 | res->data(); 94 | // Get the response body as a string 95 | res->string(); 96 | // Get the response body as matjson::Value (json) 97 | res->json(); 98 | ``` 99 | 100 | ## A full example 101 | 102 | Let's do a full example. This simple code will hook MenuLayer::init, run a request, and log the resulting response, as a string. 103 | 104 | ```cpp 105 | #include 106 | #include 107 | #include 108 | 109 | using namespace geode::prelude; 110 | 111 | class $modify(MenuLayer) { 112 | struct Fields { 113 | EventListener m_listener; 114 | }; 115 | 116 | bool init() { 117 | if (!MenuLayer::init()) { 118 | return false; 119 | } 120 | 121 | m_fields->m_listener.bind([] (web::WebTask::Event* e) { 122 | if (web::WebResponse* res = e->getValue()) { 123 | log::info("{}", res->string().unwrapOr("Uh oh!")); 124 | } else if (web::WebProgress* p = e->getProgress()) { 125 | log::info("progress: {}", p->downloadProgress().value_or(0.f)); 126 | } else if (e->isCancelled()) { 127 | log::info("The request was cancelled... So sad :("); 128 | } 129 | }); 130 | 131 | auto req = web::WebRequest(); 132 | // Let's fetch... uhh... 133 | m_fields->m_listener.setFilter(req.get("https://pastebin.com/raw/vNi1WHNF")); 134 | return true; 135 | } 136 | }; 137 | ``` 138 | 139 | Now open your game (with the developer console enabled) and check to see if your request works! 140 | -------------------------------------------------------------------------------- /tutorials/fields.md: -------------------------------------------------------------------------------- 1 | # Fields 2 | 3 | Say you want to add a property to an existing class. One way you could do this is by storing the property in a global variable like so: 4 | 5 | ```cpp 6 | int totalJumps = 0; 7 | 8 | class $modify(PlayerObject) { 9 | void pushButton(PlayerButton button) { 10 | log::info("the player has jumped {} times !", totalJumps); 11 | totalJumps++; 12 | PlayerObject::pushButton(button); 13 | } 14 | }; 15 | ``` 16 | 17 | This works; however, it isn't ideal. The counter never resets back down to 0, and the same value is shared across all instances of PlayerObject. 18 | 19 | One could solve this problem by using the `setUserData` function in `CCNode` or by utilizing more complicated systems, but both solutions are less than ideal. 20 | 21 | Geode provides a better alternative: **fields**. Fields let you add new member variables to modified classes. Take a look at an example: 22 | 23 | ```cpp 24 | class $modify(PlayerObject) { 25 | struct Fields { 26 | int m_totalJumps = 0; 27 | }; 28 | 29 | void pushButton(PlayerButton button) { 30 | log::info("The player has jumped {} times !", m_fields->m_totalJumps); 31 | m_fields->m_totalJumps++; 32 | PlayerObject::pushButton(button); 33 | } 34 | }; 35 | ``` 36 | 37 | This code works even if you have multiple `PlayerObject`s, and the counter is initialized as 0 for each one, providing an elegant yet simple solution to the problem. 38 | 39 | Fields are declared just like normal member variables, but inside the special `Fields` struct. Even constructors and destructors work\*. 40 | 41 | > :info: Fields are initialized only whenever they're first accessed, **not** when the modified class is originally created. 42 | 43 | ```cpp 44 | class $modify(PlayerObject) { 45 | struct Fields { 46 | Fields() : m_totalJumps(13) { 47 | log::debug("Constructed!"); 48 | } 49 | int m_totalJumps; 50 | ~Fields() { 51 | log::debug("Destructed!"); 52 | } 53 | }; 54 | 55 | void pushButton(PlayerButton button) { 56 | log::info("the player has jumped {} times !", m_fields->m_totalJumps); 57 | m_fields->m_totalJumps++; 58 | PlayerObject::pushButton(button); 59 | } 60 | }; 61 | ``` 62 | 63 | # External Access 64 | 65 | Fields can be accessed outside your modified class like usual, with the help of some casting. 66 | 67 | > :warning: Do not use `typeinfo_cast` here, as it isn't actually an instance of `MyGameObject`. 68 | 69 | ```cpp 70 | class $modify(MyGameObject, GameObject) { 71 | struct Fields { 72 | int m_myField = 2; 73 | }; 74 | }; 75 | 76 | GameObject* someObject = /*...*/; 77 | // `m_fields` is still required! 78 | static_cast(someObject)->m_fields->m_myField = 12; 79 | ``` -------------------------------------------------------------------------------- /tutorials/hookpriority.md: -------------------------------------------------------------------------------- 1 | # Hook Priority 2 | 3 | Hook priority allows for a developer to control when their hook will be called in relation to other hooks on the same function. When done well, hook priority can improve a mod's compatibility. However, poorly applied priorities can also completely break compatibility with other mods. 4 | 5 | > :warning: This is advanced functionality! Most developers will not need to touch this system. 6 | 7 | The syntax, as previously mentioned in the [Hooking tutorial](/tutorials/modify.md), looks like this: 8 | 9 | ```cpp 10 | class $modify(cocos2d::CCLabelBMFont) { 11 | static void onModify(auto& self) { 12 | if (!self.setHookPriorityPost("cocos2d::CCLabelBMFont::init", Priority::First)) { 13 | geode::log::warn("Failed to set hook priority."); 14 | } 15 | if (!self.setHookPriorityPre("cocos2d::CCLabelBMFont::setString", Priority::Late)) { 16 | geode::log::warn("Failed to set hook priority."); 17 | } 18 | if (!self.setHookPriorityAfterPost("cocos2d::CCLabelBMFont::limitLabelWidth", "geode.node-ids")) { 19 | geode::log::warn("Failed to set hook priority."); 20 | } 21 | } 22 | }; 23 | ``` 24 | 25 | A function with no explicit priority is given the priority of `Priority::Normal`. 26 | 27 | There are 7 pre-assigned priority values recommended for use: `First, VeryEarly, Early, Normal, Late, VeryLate, Last`. Arithmetic on these values are also possible, although not recommended: `Priority::Early + 2`, meaning 2 internal units later than `Early`. 28 | 29 | There are also 2 prefixes related with priorities: `Pre, Post`. Pre priorities sort based on code called **before** the original call, and Post priorities sort based on code called **after** the original call. 30 | 31 | Here's an example of the pre/post differentiation: 32 | 33 | ```cpp 34 | void functionEarlyPre() { 35 | log::info("statement 1"); 36 | function(); 37 | } 38 | void functionLatePre() { 39 | log::info("statement 3"); 40 | function(); 41 | } 42 | void functionNormal() { 43 | log::info("statement 2"); 44 | function(); 45 | log::info("statement 5"); 46 | } 47 | void functionEarlyPost() { 48 | function(); 49 | log::info("statement 4"); 50 | } 51 | void functionLatePost() { 52 | function(); 53 | log::info("statement 6"); 54 | } 55 | ``` 56 | 57 | ## Guidelines 58 | 59 | **Always use** the before/after functions instead of doing arithmetic on priorities! Unless circular priorities happen, it is guaranteed for them to work consistently even if the other mods change their priorities. 60 | 61 | - If you want to run your code after some other mod's code and before calling original, use `setHookPriorityAfterPre`. 62 | 63 | - If you want to run your code before some other mod's code and before calling original, use `setHookPriorityBeforePre`. 64 | 65 | - If you want to run your code before some other mod's code and after calling original, use `setHookPriorityBeforePost`. 66 | 67 | - If you want to run your code after some other mod's code and after calling original, use `setHookPriorityAfterPost`. 68 | 69 | Using raw numbers as priorities is **strongly discouraged** for mod compatibility. 70 | There are tools for mod compatibilities! Please take advantage of them. 71 | 72 | - If you want your function to run _before_ all other code, including the original function, use `Priority::First` with `setHookPriorityPre` and call the original function _after_ your code. 73 | 74 | - If you want your function to run _after_ all other code, use `Priority::Last` with `setHookPriorityPost` and call the original function at the start of your hook. 75 | 76 | - If you want your function to run _after_ the original function, but _before_ other hooks, use `Priority::First` with `setHookPriorityPost` and call the original function at the start. 77 | 78 | - If you're _reimplementing_ the original function and do not call the original, use `Priority::Last` with `setHookPriorityPre`. 79 | 80 | Obviously, these are not strict rules. Feel free to choose the hook priority that best fits your situation. 81 | 82 | ## Notes 83 | 84 | This section details some additional tricks to the hook priority system. 85 | 86 | - Be sure you have the function name correct in `setHookPriority`! The namespace must be included. 87 | 88 | - While the current priority is preserved across function calls, it is not preserved in other situations. For example: 89 | 90 | ```cpp 91 | void hook_fn() { 92 | Loader::get()->runInMainThread([]() { 93 | fn(); // <-- this would call hook_fn 94 | }); 95 | } 96 | ``` 97 | 98 | Instead of calling the original, this will call the hook again. This also applies for calling the original through a Task (see [geode#994](https://github.com/geode-sdk/geode/issues/994)). 99 | 100 | Calling the original through another function will work as long as it is in the same thread: 101 | 102 | ```cpp 103 | void x() { 104 | fn(); // <-- this calls the original, instead of hook_fn 105 | } 106 | 107 | void hook_fn() { 108 | x(); 109 | } 110 | ``` 111 | 112 | - Using a stub function with `Priority::First` with `Pre` or `Priority::Last` with `Post` is an effective way to disable a function. In this scenario, it becomes impossible for other mods to call the original (as their hooks wouldn't be called either), so this is not recommended. 113 | 114 | - Pre/Post values embedded into `Priority::` do exist, but they are not that recommended as arithmetic on them can be confusing. (Positive becomes negative for Post) 115 | 116 | - Manual hooks can have their priorities set through the [Hook::setPriority](/classes/geode/Hook/#setPriority) method. 117 | 118 | - It is not possible to set the priorities of multiple functions with overloaded parameters in the same modify class (as this would require specifying arguments). In this situation, either split the hooks into separate modify classes or manually hook. 119 | 120 | ## Internal Implementation 121 | 122 | The initial call to the function starts with the hook with lowest priority (internally `INT_MIN`). 123 | Each call to the original function increases the current priority, with the original function being at the highest priority (internally `INT_MAX`). 124 | As each function in the chain returns, priority is decreased until the function with lowest priority returns. 125 | At this point, the function call is finished and execution returns to the original caller. 126 | 127 | The 7 pre-assigned priorities have the values of `-3000, -2000, -1000, 0, 1000, 2000, 3000` respectively. 128 | 129 | An example is shown by this diagram and its accompanying code: 130 | 131 | ![Diagram detailing the flow of execution for a hooked function](/assets/hook_priority.png) 132 | 133 | ```cpp 134 | // priority of -1'000'000 135 | void fn_lowest() { 136 | log::info("statement 1"); 137 | fn(); 138 | log::info("statement 6"); 139 | } 140 | 141 | // priority of -100 142 | void fn_neg100() { 143 | // code here happens prior to statement 2 and after statement 1 144 | fn(); 145 | log::info("statement 5"); 146 | } 147 | 148 | // priority of 0 149 | void fn_default() { 150 | log::info("statement 2"); 151 | fn(); 152 | log::info("statement 4"); 153 | } 154 | 155 | // priority of 1'000'000 156 | void fn_highest() { 157 | log::info("statement 3"); 158 | fn(); 159 | // code here will happen prior to statement 4 and after the original function call 160 | } 161 | ``` 162 | 163 | The statements will be printed in numerical order, from "statement 1" to "statement 6". 164 | 165 | A developer may also choose to not call the original, which means hooks that are a higher priority than the developer's hooks (including the original function) will not be executed. 166 | 167 | ### Internal notes 168 | 169 | - A hook priority of `INT_MAX` will not function as expected, as the original function also has a priority of `INT_MAX`. 170 | -------------------------------------------------------------------------------- /tutorials/image-loading.md: -------------------------------------------------------------------------------- 1 | # Image (Down)loading 2 | 3 | If your have to display **images** in your mod that are **not included** in the mod file (for example for reducing the **size** of the mod or for showing **user-generated content**), you can use geode's `LazySprite` class. It can **also** be used for ordinary image initialization, either from a path or from raw data, which would have slight performance improvements over `CCSprite::create`, as the image is lazily read and decoded **in the background**, without freezing the game. 4 | 5 | It is **recommended** to use this API instead of implementing custom solutions that mess with `CCTextureCache`. It is not straightforward to do this correctly, and multiple mods (and Geode itself) have in the past failed to do this without memory leaks and other bugs. 6 | 7 | By default, the sprite will have a **loading circle** inside it when it's loading, which is removed once loading is finished. This can be disabled, making the sprite blank until it's loaded. 8 | 9 | ## Usage 10 | 11 | Basic examples of downloading an image from a URL, loading from a file, and initializing from data, and a showcase of various options. 12 | 13 | ```cpp 14 | #include 15 | using namespace geode::prelude; 16 | 17 | // this will be the initial size of the node (and the loading circle) 18 | CCSize nodeSize{64.f, 64.f}; 19 | 20 | auto spr1 = LazySprite::create(nodeSize); 21 | // or, if you want to disable the loading circle 22 | auto spr1 = LazySprite::create(nodeSize, false); 23 | 24 | // if you want to know when the image finished loading, you can assign a callback 25 | // do this **before** calling one of the next functions, because if the image is cached, 26 | // the callback will be called instantly, potentially before you assign it. 27 | spr1->setLoadCallback([spr1](Result<> res) { 28 | if (res) { 29 | log::info("Sprite loaded successfully!"); 30 | } else { 31 | log::error("Sprite failed to load, setting fallback: {}", res.unwrapErr()); 32 | spr1->initWithSpriteFrameName("globed-logo.png"_spr); 33 | } 34 | }); 35 | 36 | // load the image from a URL 37 | spr1->loadFromUrl("https://globed.dev/logo.png"); 38 | // or, load from a file 39 | spr1->loadFromFile(Mod::get()->getSaveDir() / "file.png"); 40 | // or, load from binary data 41 | std::vector pngBytes = {0x1, 0x2, 0x3}; 42 | spr1->loadFromData(std::move(pngBytes)); 43 | 44 | // using cocos methods also works, but does not have any performance benefits: 45 | spr1->initWithFile("my-file.png"); 46 | spr1->initWithSpriteFrameName("my-texture.png"); 47 | 48 | // note: you MUST either add the sprite as a child or store it in a Ref<>, 49 | // if the sprite node gets deleted then it will stop loading and will never call your callback 50 | someMenu->addChild(spr1); 51 | ``` 52 | 53 | ## Automatic resize 54 | 55 | Once the sprite has finished loading, the content size of the `LazySprite` will be set to match the loaded image (in the same manner a `CCSprite::initWithTexture` would). If you want the sprite to be automatically scaled to the size you passed in `LazySprite::create`, you can enable autoresize: 56 | 57 | ```cpp 58 | 59 | auto spr1 = LazySprite::create({64.f, 64.f}); 60 | spr1->setAutoResize(true); 61 | spr1->loadFromFile(Mod::get()->getSaveDir() / "file.png"); 62 | 63 | // now, once the sprite finished loading, it should be resized to 64x64 (in cocos units) 64 | ``` 65 | 66 | ## Caching 67 | 68 | By default, if using `loadFromFile` or `loadFromUrl`, this class will cache the textures in `CCTextureCache` (`loadFromData` is not supported due to it being hard to pick a uniquely identifying cache key). 69 | 70 | If this is not wanted, the load functions have an extra bool argument for ignoring the cache: 71 | 72 | ```cpp 73 | spr1->loadFromUrl("url", true); 74 | ``` 75 | -------------------------------------------------------------------------------- /tutorials/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | --- 4 | 5 | # Modding Tutorials 6 | 7 | These are a collection of **tutorials** and **information** for using making Geometry Dash mods using the Geode framework. These are focused on general modding tips & Geode-specific features. 8 | -------------------------------------------------------------------------------- /tutorials/layouts.md: -------------------------------------------------------------------------------- 1 | # Layouts 2 | 3 | One of the biggest pains in all UI design in general is positioning stuff. This is especially troublesome in GD mods, where you can't really be sure where stuff are located on the screen. If some mod changes the visual outlook of a layer, how are you supposed to figure out where you should position your buttons? 4 | 5 | The solution Geode introduces for this are **layouts** - small classes added to CCNodes that dictate how that node's children should be positioned. For example, a row layout would lay its children in a horizontal row, whereas a grid layout would lay its children out in a grid. Although, perhaps surprisingly, Geode only adds one type of layout: the [AxisLayout](/classes/geode/AxisLayout) class, which actually handles all common types of layouts, including row, column, grid, and flex. 6 | 7 | You can view what layouts are visible on screen using the [DevTools](https://github.com/geode-sdk/devtools) mod, which has an option to show all layouts in the current scene. 8 | 9 | ![Image of the DevTools mod in GD, showcasing the layouts in MenuLayer](/assets/DevTools_layouts.png) 10 | 11 | ## Using layouts to position nodes 12 | 13 | Using layouts is very simple - especially if someone else has already set the layout for you. For example, if you want to add a child to bottom menubar in the main menu and position it in a row along with the other buttons there, doing so is very simple: 14 | 15 | ```cpp 16 | struct $modify(MenuLayer) { 17 | bool init() { 18 | if (!MenuLayer::init()) 19 | return false; 20 | 21 | auto menu = this->getChildByID("bottom-menu"); 22 | menu->addChild(/* create button */); 23 | menu->updateLayout(); 24 | 25 | return true; 26 | } 27 | }; 28 | ``` 29 | Notice that we don't have to do any sort of manual positioning to our button - all we have to do is call [`CCNode::updateLayout`](/classes/cocos2d/CCNode#updateLayout) after adding our node to the menu, and it will automatically position the button for us. 30 | 31 | ## Adding layouts 32 | 33 | If you'd like to add layouts to your own nodes or overwrite the layouts of existing nodes, you can do so by calling [`setLayout`](/classes/cocos2d/CCNode#setLayout) on the node: 34 | ```cpp 35 | auto myMenu = CCMenu::create(); 36 | myMenu->setLayout(RowLayout::create()); 37 | ``` 38 | 39 | ## AxisLayout 40 | 41 | Geode only adds one type of layout, [AxisLayout](/classes/geode/AxisLayout), which is meant to be an all-purpose general layout. It can arrange nodes in a row, column, grid, or even a flex-type flow layout. Geode also adds the [RowLayout](/classes/geode/RowLayout) and [ColumnLayout](/classes/geode/ColumnLayout) for readability, however you will find that these don't actually add anything new on top of AxisLayout - they're just syntactic sugar! 42 | 43 | By default, AxisLayout arranges its children in a single straight line. This can be changed using the [setGrowCrossAxis](/classes/geode/AxisLayout#setGrowCrossAxis) method. 44 | 45 | You can play around with the different options AxisLayout offers using the [DevTools](https://github.com/geode-sdk/devtools) mod. 46 | 47 | ![Image of the DevTools mod, showing the different options for AxisLayout](/assets/DevTools_layoutAttributes.png) 48 | 49 | ## Custom layouts 50 | 51 | If AxisLayout doesn't fully match your needs, you can also define entirely custom layouts by inheriting from the [Layout](/classes/geode/Layout) class and implementing the `apply` method. 52 | -------------------------------------------------------------------------------- /tutorials/logging.md: -------------------------------------------------------------------------------- 1 | # Logging 2 | 3 | Geode provides a builtin logger with [fmtlib](https://fmt.dev/latest/index.html) formatting, which you can view in real time via the [platform console](#platform-console), or later on by checking the logs in the: 4 | * `/geode/logs` directory on `Windows` and `MacOS` 5 | * `Android/media/com.geode.launcher/game/geode/logs` directory on `Android` 6 | 7 | ## Log Levels 8 | There are 4 logging levels, each being more important than the previous. 9 | 10 | > :warning: If you want to omit `geode::` from the log call, you can use ```using namespace geode::prelude``` at the top of your file 11 | 12 | ### Error 13 | 14 | This is the highest priority level, used for logging errors that interrupt your mod's functionality, **without recovery**. As examples, the following cases apply: an API call failing, not finding a node in a specific layer that you need, etc. Use this level sparingly. 15 | 16 | ```cpp 17 | geode::log::error("I am a grave error!"); 18 | ``` 19 | 20 | ### Warn 21 | 22 | This level is used for warning about certain aspects of your mod that failed, but aren't critical to its functionality / can be recovered from. For example, an API request failing (**but the mod retries the connection**), some non-vital setting or saved value not being set that you have a default for, etc. 23 | 24 | ```cpp 25 | geode::log::warn("I failed to fetch this list of items, but I'll retry a few times"); 26 | ``` 27 | 28 | ### Info 29 | 30 | This level is used for logging information about what your mod is doing. You shouldn't use this level to spam info messages, just simple information. For example, messages like **"Loading assets for scene"** are suitable, but **"Coordinates: {120.3, 150.3}"** being spammed over and over should be set to the lowest logging level. 31 | 32 | ```cpp 33 | geode::log::info("Setting up the editor adjustments"); 34 | ``` 35 | 36 | ### Debug 37 | 38 | Use this level for anything you find useful to log! May that be values used in your mod, more specific text, and going into more detail than the info level. For example, while for `info` we logged something like **"Loading assets for scene"**, for `debug` we can log **"Loading spritesheet MyModSheet.png"** or the coordinates message from above. 39 | 40 | ```cpp 41 | geode::log::debug("Loading sheet {}", sheetName); 42 | geode::log::debug("Filesize: {}", filesize); 43 | geode::log::debug("Found node: {}", node->getID()); 44 | ``` 45 | 46 | > :warning: Debug logs are not shown by default. See the [Log filtering](#log-filtering) section to see how you can change this behavior. 47 | 48 | ## Syntax 49 | The logger uses [fmtlib](https://fmt.dev/latest/syntax.html) under the hood, which means you can use its syntax, which will get forwarded to fmtlib. Geode also provides formatters for a few common types, such as `CCPoint`, `CCNode`, etc. 50 | 51 | ![Cheatsheet showing fmtlib syntax](https://hackingcpp.com/cpp/libs/fmt.png) 52 | *Image from [https://hackingcpp.com](https://hackingcpp.com/cpp/cheat_sheets.html)* 53 | 54 | As such, you are able to do things like this: 55 | ```cpp 56 | geode::log::debug("Hello {:>10.3f} world, [{}]", 12.4f, fmt::join(someVec, ", ")); 57 | ``` 58 | 59 | ## Platform console 60 | The platform console is the quickest way to see the logs, running real time ingame. You can open the platform console by going to the Geode settings. 61 | 62 | ![Image showing the platform console option ingame](/assets/geode_platform_console.png) 63 | 64 | ## Log filtering 65 | 66 | Log filtering is a system that hide the less important logs from the **platform console** and the **log files generated by Geode**. The log level can be customized individually for both of these output locations, so you can have a log level of **info** for the log files, and a log level of **debug** for the platform console. 67 | 68 | The log level takes the value **debug**, **info**, **warn** or **error**. Setting the log level to one of those values will only allow logs with that level **or higher** to be written. For example, log level **warn** will only show **warns** and **errors**, while log level **info** will show **info**, **warn** and **error** logs. The default values of those log levels is **info** for both channels. 69 | 70 | You can modify your log levels ingame, through **Geode's settings**. 71 | 72 | ![Image showing the log levels option](/assets/geode_log_levels.png) 73 | -------------------------------------------------------------------------------- /tutorials/manualhooks.md: -------------------------------------------------------------------------------- 1 | # Manual Hooks 2 | 3 | Sometimes, you need to hook some platform specific function that would be a hassle to manually add to bindings - or you want to provide support for adding hooks through other means than `$modify`, such as an embedded scripting language. In this case, you can also manually add hooks in Geode using [`Mod::addHook`](/classes/geode/Mod#addHook). 4 | 5 | ## Example 6 | 7 | The provided example hooks `MenuLayer::onNewgrounds` on Windows. 8 | 9 | > :warning: This function can be hooked normally using `$modify` - this is just an example! **Do not do this!** 10 | 11 | ```cpp 12 | void MenuLayer_onNewgrounds(MenuLayer* self, CCObject* sender) { 13 | log::info("Hook reached!"); 14 | // You can call the original by calling it - in this case, since the 15 | // original is in bindings, you can just call it the same way as you 16 | // would with $modify 17 | // TODO: How to call original manually 18 | self->onNewgrounds(sender); 19 | log::info("After original!"); 20 | } 21 | 22 | $execute { 23 | Mod::get()->hook( 24 | reinterpret_cast(geode::base::get() + 0x191E90), // address 25 | &MenuLayer_onNewgrounds, // detour 26 | "MenuLayer::onNewgrounds", // display name, shows up on the console 27 | tulip::hook::TulipConvention::Thiscall // calling convention 28 | ); 29 | } 30 | ``` 31 | 32 | ## Hooking an imported function 33 | 34 | This example shows how to hook a function that is linked to, for example through DLL imports. Geode comes with the `addresser` namespace for utilities related to figuring out the addresses of linked functions, including class methods and virtual functions. 35 | 36 | ```cpp 37 | void myDrawCircle(const cocos2d::CCPoint& center, float radius, float angle, unsigned int segments, bool drawLineToCenter) { 38 | // Call the original 39 | cocos2d::ccDrawCircle(center, radius, angle, segments, drawLineToCenter); 40 | log::info("alright {}", radius); 41 | } 42 | 43 | $execute { 44 | Mod::get()->hook( 45 | reinterpret_cast( 46 | // All of this is to get the address of ccDrawCircle 47 | geode::addresser::getNonVirtual( 48 | // This is used because this function is overloaded, 49 | // otherwise just a regular function pointer would suffice (&foobar) 50 | geode::modifier::Resolve::func(&cocos2d::ccDrawCircle) 51 | ) 52 | ), 53 | &myDrawCircle, // Our detour 54 | "cocos2d::ccDrawCircle", // Display name, shows up on the console 55 | tulip::hook::TulipConvention::Cdecl // Static free-standing cocos2d functions are cdecl 56 | ); 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /tutorials/migrate-v4.md: -------------------------------------------------------------------------------- 1 | # Migrating from Geode v3.x to v4.0 2 | 3 | ## Changes to `Result` 4 | [Link to the full docs](https://github.com/geode-sdk/result?tab=readme-ov-file#result) 5 | * Result type has been rewritten from scratch, and it is now shared with many parts of the Geode codebase (Geode itself, TulipHook and matjson). 6 | * Many methods removed or renamed: 7 | * `value()` -> `unwrap()` 8 | * `expect(str, fmt args...)` -> Use `mapErr` along with `fmt::format` 9 | * Snake case methods renamed to camel case 10 | * Universal `GEODE_UNWRAP(res)` and `GEODE_UNWRAP_INTO(value, res)` macros 11 | * These macros were already previously available, but this is just a reminder that they can help a lot when dealing with results. 12 | ```cpp 13 | Result addValues(matjson::Value json) { 14 | // Value::asInt() returns a Result 15 | GEODE_UNWRAP_INTO(int a, json["a"].asInt()); 16 | GEODE_UNWRAP_INTO(int b, json["b"].asInt()); 17 | // must wrap return value with Ok 18 | return Ok(fmt::format("sum is {}", a + b)); 19 | } 20 | ``` 21 | * If you only ever expect to build your code with **clang**, you can use the `GEODE_UNWRAP` macro like so, for convenience: 22 | ```cpp 23 | Result getInt(); 24 | 25 | Result foo(int extra) { 26 | // This will not compile on MSVC 27 | int value = GEODE_UNWRAP(getInt()) + extra; 28 | return Ok(value); 29 | } 30 | ``` 31 | 32 | ## Changes to the Geode Settings API 33 | * Settings v2 has been removed, V3 suffix from classes are now aliased 34 | * Most SettingV3 code should work fine, with the exception of some Result usages: 35 | ```cpp 36 | static Result> parse(std::string const&, std::string const&, matjson::Value const& json) { 37 | auto res = std::make_shared(); 38 | auto root = checkJson(json, "MyCustomSettingV3"); 39 | // ... some code here ... 40 | return root.ok(res); 41 | } 42 | // should now be: 43 | // ↓ here 44 | static Result> parse(std::string const&, std::string const&, matjson::Value const& json) { 45 | auto res = std::make_shared(); 46 | auto root = checkJson(json, "MyCustomSettingV3"); 47 | // ... some code here ... 48 | // ↓ here 49 | return root.ok(std::static_pointer_cast(res)); 50 | } 51 | ``` 52 | 53 | ## Changes to `utils::MiniFunction` 54 | * Removed, use `std::function` now 55 | 56 | ## Changes to `geode::Layout` 57 | * No longer in cocos2d namespace 58 | * Can now be found in `Geode/ui/Layout.hpp` 59 | 60 | ## Changes to `getChildOfType` 61 | * Deprecated in 3.9.0, and now removed, use `CCNode::getChildByType(int index)` instead: 62 | * `getChildOfType(node, 1)` -> `node->getChildByType(1)` 63 | * You can use this regex pattern to quickly find and replace: 64 | * `getChildOfType<(.+?)>\((.+?),\s*(.+?)\)` and replace with `$2->getChildByType<$1>($3)` 65 | 66 | ## Changes to `matjson` 67 | [Link to the full docs](https://github.com/geode-sdk/json?tab=readme-ov-file#matjson) 68 | * Entire library rewritten to use `geode::Result` 69 | * See result section for tips on how to use Geode's Result class 70 | * Methods are now camel case to fit with rest of Geode's codebase 71 | * `is_string` -> `isString` 72 | * `as_string` -> `asString` 73 | * etc.. 74 | * `matjson::Object` has been removed, now you can just iterate directly off a Value (same for arrays!) 75 | ```cpp 76 | matjson::Value someObject = ...; 77 | for (auto& [key, value] : someObject) { 78 | log::debug("{}: {}", key, value); 79 | } 80 | 81 | matjson::Value someArray = ...; 82 | for (auto& value : someArray) { 83 | log::debug("{}", value); 84 | } 85 | ``` 86 | * Use `matjson::makeObject` for making objects inline now: 87 | ```cpp 88 | matjson::Value obj = matjson::makeObject({ 89 | { "key", 123 }, 90 | { "another-key": "another value!" } 91 | }); 92 | ``` 93 | * Accessing missing properties with `Value::operator[]` will now return a null value instead of throwing an exception 94 | * Serialization methods have been changed: 95 | * `T from_json(matjson::Value const&)` -> `Result fromJson(matjson::Value const&)` 96 | * **Make sure to return a Result!** much of the code relies on `fromJson` returning a Result. 97 | * `matjson::Value to_json(T const& value)` -> `matjson::Value toJson(T const& value)` 98 | * `is_json` is no longer used, instead just return an error in `fromJson` 99 | * `bool Value::is()` removed 100 | * `#include ` -> `#include ` 101 | * Added new experimental `` header, uses [qlibs/reflect](https://github.com/qlibs/reflect) to (de)serialize aggregate structs 102 | * Behavior might change in the future, so use with caution 103 | 104 | ## Changes to `JsonChecker` / `JsonExpectedValue` 105 | * The JsonChecker class was previously marked deprecated, and is now removed. Now you should move to using `JsonExpectedValue` instead 106 | ```cpp 107 | auto checker = JsonChecker(json); 108 | auto root = checker.root("[file.json]").obj(); 109 | 110 | // ... code 111 | 112 | if (checker.isError()) { 113 | return Err(checker.getError()); 114 | } 115 | ``` 116 | is now 117 | ```cpp 118 | auto root = checkJson(json, "[file.json]") 119 | 120 | // ... code 121 | 122 | GEODE_UNWRAP(root.ok()); 123 | ``` 124 | * `JsonChecker::has` has different behavior to `JsonExpectedValue::has`, use `JsonExpectedValue::hasNullable` to keep old behavior with null values 125 | -------------------------------------------------------------------------------- /tutorials/migrating.md: -------------------------------------------------------------------------------- 1 | # Migrating your mods from the traditional 2 | 3 | As Geode is currently incompatible with traditional mods, migration of old mods is a must in order to use them with Geode. This page shows how to do just that. 4 | 5 | ## Migrating from gd.h and cocos-headers 6 | 7 | This one may be one of the most tedious parts. First of all, all of the GD classes are removed from the `gd` namespace. And since older gd.h's use the `m_tVariable` convention with t being the type identifier, all of those variables will give errors when given Geode's `m_variable` convention. Let's see an example of this. 8 | 9 | Here is a code snippet from HJFod's [BetterEdit v4](https://github.com/HJfod/BetterEdit/blob/v4-min/hooks/EditorUI.cpp#L39-L53): 10 | 11 | ```cpp 12 | CCPoint getShowButtonPosition(EditorUI* self) { 13 | auto winSize = CCDirector::sharedDirector()->getWinSize(); 14 | auto ratio = winSize.width / winSize.height; 15 | 16 | if (ratio > 1.5f) 17 | return { 18 | self->m_pTrashBtn->getPositionX() + 50.0f, 19 | self->m_pTrashBtn->getPositionY() 20 | }; 21 | 22 | return { 23 | self->m_pPlaybackBtn->getPositionX() + 45.0f, 24 | self->m_pPlaybackBtn->getPositionY() 25 | }; 26 | } 27 | ``` 28 | This code uses the `m_pTrashBtn` and the `m_pPlaybackBtn` members from `EditorUI`, a class from gd.h. These members correspond to `m_trashBtn` and `m_playbackBtn` in Geode respectively. It is not the best way to do it, but one can use regex `m_(?:ob|[a-z])([A-Z])` to find most of the variables that use the old convention. 29 | 30 | Cocos-headers don't need any extra work, since the cocos headers in Geode are based on that repository with changes needed for the Geode codebase. 31 | 32 | 33 | ## Migrating the hooks 34 | 35 | ### Bare Minhook 36 | 37 | This is the most common used method. Minhook hooks have a static function pointer in which the trampoline is stored (often prefixed with `_O`) and the hook itself (often prefixed with `_H`). Calling the original is done by calling the trampoline stored in the function pointer. 38 | 39 | The Minhook is first initialized with `MH_Initialize`, then hooks are added with `MH_CreateHook` with parameters address, the address of the hook function, and the address of the static function pointer, then `MH_EnableHook` is called to enable the hooks themselves. 40 | 41 | ```cpp 42 | bool (__thiscall* MenuLayer_init_O)(gd::MenuLayer* self); 43 | bool __fastcall MenuLayer_init_H(gd::MenuLayer* self, void*) { 44 | if (!MenuLayer_init_O(self)) return false; 45 | return true; 46 | } 47 | // ... 48 | MH_Initialize(); 49 | MH_CreateHook((void*)base + 0x1907b0, (void*)&MenuLayer_init_H, (void**)&MenuLayer_init_O); 50 | MH_EnableHook(MH_ALL_HOOKS); 51 | ``` 52 | 53 | ### MAT dash 54 | 55 | This is also used by some people, mainly by newer traditional modders. Compared to other methods, MAT dash uses the return type of the hook to infer the calling convention (example: `matdash::cc::thiscall`) unless it's membercall, a GD specific calling convention, which needs to be removed for migration. The original is called using `matdash::orig` with a template parameter of a reference to the hook, the parameters are the same with the hook itself. 56 | 57 | MAT dash hooks are added with `matdash::add_hook` with a template parameter of a reference to the hook function and a parameter of the address. 58 | 59 | ```cpp 60 | bool MenuLayer_init(gd::MenuLayer* self) { 61 | if (!matdash::orig<&MenuLayer_init>(self)) return false; 62 | return true; 63 | } 64 | // ... 65 | matdash::add_hook<&MenuLayer_init>(base + 0x1907b0) 66 | ``` 67 | 68 | ### GDMake 69 | 70 | No one will need this section but I'm adding it for the completeness sake. GDMake uses the `GDMAKE_HOOK` with the address and the symbol parameter to hook a specific function and `GDMAKE_ORIG` keyword to call the original function. 71 | 72 | ```cpp 73 | GDMAKE_HOOK(0x1907b0, "_ZN9MenuLayer4initEv") 74 | bool __fastcall MenuLayer_init(gd::MenuLayer* self, void* edx) { 75 | if (!GDMAKE_ORIG(self, edx)) return false; 76 | return true; 77 | } 78 | ``` 79 | 80 | ### Shared 81 | 82 | Since all of these hooks are static functions, a `self` parameter and a parameter for clobbing the edx register is added (except MAT dash) to match the calling convention for member functions. These parameters need to be removed when moving the hook inside a modify class. Likewise, all uses of `self->` need to be either removed or replaced with `this->`. 83 | 84 | Otherwise, all of the hooks can be replaced by a `Modify` class and the original calls can be replaced with a call the `OriginalClass::function` inside the modify hook with the needed parameters. 85 | 86 | ```cpp 87 | class $modify(MenuLayer) { 88 | bool init() { 89 | if (!MenuLayer::init()) return false; 90 | return true; 91 | } 92 | } 93 | ``` 94 | 95 | ## Patches 96 | 97 | Patches are pretty easy to migrate. Geode has a `Mod::patch` function that takes in a byte vector and an address, which can be used for mod specific patches. 98 | 99 | ## Miscellaneous 100 | 101 | Some function signatures in gd.h are wrong: wrong as in they work for Windows, but not for any other platform. Geode uses the Android binary symbols to infer the function signatures, so the wrong function calls relating to this issue need to be fixed while migrating from gd.h. 102 | 103 | ## TODO 104 | 105 | Add settings, data saving 106 | -------------------------------------------------------------------------------- /tutorials/modify-geode.md: -------------------------------------------------------------------------------- 1 | # Modifying Geode UI 2 | 3 | Since Geode v3.4.0, mods are able to also modify the Geode UI itself in limited ways. This is so mods can do things like add extra buttons to their own mod's popup, make their mod logo have particles, etc. This tutorial shows how to use these APIs to modify Geode's own UIs. 4 | 5 | ## Listening for UI events 6 | 7 | Geode currently exposes three UI events through the `` header: [`ModPopupUIEvent`](/classes/geode/ModPopupUIEvent), [`ModItemUIEvent`](/classes/geode/ModItemUIEvent), and [`ModLogoUIEvent`](/classes/geode/ModLogoUIEvent). These are for being notified of whenever a mod popup is created, a mod list mod item is created, and a mod logo is created respectively. 8 | 9 | Note that **all Geode UI events may and will be posted _multiple times_!** This is because Geode UIs often have an initial created "loading" state, and then fetch data from the Geode servers that is updated onto the created node asynchronously. To allow mods to be notified of when this data is loaded, **the UI events are reposted whenever the state of the UI node changes**. For this reason, **mods can never add nodes without checking if they already exist first**. 10 | 11 | You can listen to these events by using Geode's events system as usual, using the [`EventFilter`](/classes/geode/EventFilter) helper class. Note that you should always return `ListenerResult::Propagate` to allow other mods to modify the layer as well, like calling the original in a `$modify` hook! 12 | 13 | ## Guidelines 14 | 15 | There are some guidelines on what you are and are not allowed to do when modifying the Geode UI. These are: 16 | 17 | 1. The mod may **only access nodes by ID or member**. No matching types or indices, if the node the mod wants to modify doesn't have an ID and is not accessible by a member variable or direct class getter, the mod can not edit it 18 | 2. The mod must **always check for null** on every node they access. It should never assume the existence of a node, even if it's trivial 19 | 3. The mod must **safely handle all possible failure states**, such as missing sprites. This includes failure states of other logic it runs; if the mod makes a web request, it must not crash on unexpected behaviour! 20 | 4. If the mod finds any missing IDs, it must **undo any/all of its changes** and not make any more. The recommended way to do this is to first use `querySelector` to grab all the nodes in the scene it intends to modify beforehand, and then returning early if any of them are null 21 | 5. The mod must **give all of its own nodes IDs prefixed by its mod ID (note) and check beforehand if its nodes have been added on all events** 22 | 23 | These rules are in place because the Geode UI is a highly volatile place that **may change at any time**, and any mod causing the Geode UI itself to crash would make it impossible to disable without safe mode. 24 | 25 | > (note): Nodes nested inside other nodes don't need to be prefixed, as long as the topmost parent is prefixed. For example, `my-mod.id/container > button` is completely fair! 26 | 27 | ## Example 28 | 29 | ```cpp 30 | #include 31 | 32 | using namespace geode::prelude; 33 | 34 | $execute { 35 | new EventListener>(+[](ModLogoUIEvent* event) { 36 | if (event->getModID() == "geode.loader") { 37 | // Remember: no assumptions, even trivial ones! 38 | if (auto fart = CCSprite::createWithSpriteFrameName("GJ_demonIcon_001.png")) { 39 | fart->setScaleX(5); 40 | fart->setScaleY(3); 41 | // `event->getSprite()` is guaranteed to not be `nullptr` though 42 | event->getSprite()->addChildAtPosition(fart, Anchor::Center); 43 | } 44 | } 45 | // You should always propagate Geode UI events 46 | return ListenerResult::Propagate; 47 | }); 48 | new EventListener>(+[](ModItemUIEvent* event) { 49 | if (event->getModID() == "geode.loader") { 50 | // Find the Geode nodes you want to access first 51 | auto menu = event->getItem()->querySelector("developers-menu"); 52 | auto dev = event->getItem()->querySelector("developers-button"); 53 | 54 | // Only then do stuff with them 55 | if (menu && dev) { 56 | if (auto fart = CCSprite::createWithSpriteFrameName("GJ_demonIcon_001.png")) { 57 | fart->setScaleX(4); 58 | fart->setScaleY(2); 59 | dev->addChildAtPosition(fart, Anchor::Center, ccp(-15, 0)); 60 | } 61 | } 62 | } 63 | return ListenerResult::Propagate; 64 | }); 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /tutorials/nodetree.md: -------------------------------------------------------------------------------- 1 | # Getting nodes 2 | 3 | There comes a time in every GD modder's life when they are faced with the same eternal question: how to get a specific node from a certain layer. The easiest, and **incontestably best** way to do this is through **member variable access**. If the desired node is stored as a member in the layer, for example `m_playtestBtn` in `EditorUI`, you should __always__ access it through that. 4 | 5 | However, this is far from always possible, as not all nodes are stored as members in their containing layers. This poses a problem, the easiest solution to which has been the source of **hours of pain and misery**. The solution referred to here is **getting nodes by their absolute index**. 6 | 7 | It should be stressed here, at the very start, that **you should __NEVER__ get nodes by their index unless ABSOLUTELY __necessary__**. It should always be left as a last resort. The Cocos2d node tree is _not_ a stabile or predictable environment; child indexes can change rapidly due to game features, Z-order reordering, or most commonly due to other mods. If you do `node->getChildren()->objectAtIndex(5)`, you have no guarantees of getting the same node every time. 8 | 9 | Using absolute indexes also requires a lot of work, as the **indexes are shuffled around** after a layer's `init` function due to Z-order reordering. This means that if you look at a node through [CocosExplorer](https://github.com/matcool/CocosExplorer) or [DevTools](https://github.com/geode-sdk/devtools) and see that is is at index 5, it is more than likely that during the layer's `init` function (where you will most likely be working with the node) this will not be the case. 10 | 11 | On top of this, in old GD modding frameworks, there were unfortunately basically no alternatives to members and getting by index. There are node **tags**, however tags are mostly used to implement differing functionality for buttons that use the same callback. The result of this was a lot of mods using raw indexes to position their own additions to GD's UI. Surprise surprise, this lead to [even mods by the same developer messing up their button positions due to indexes being different than expected](https://discord.com/channels/822510988409831486/858820729234391063/881436739250585610). Using raw indexes are one of the biggest sources of **mod incompatability**; and as Geode is meant to help with solving that, it introduces a whole new way of getting nodes: 12 | 13 | ## String IDs 14 | 15 | String IDs are a **Geode-specific addition** to the `CCNode` class that come in three main functions: `CCNode::getID`, `CCNode::setID` and `CCNode::getChildByID`. In all their simplicity, it means that you can assign any `CCNode` a string ID, and get a child by its string ID. On the surface, this may seem quite trivial, but this is actually an **incredibly powerful alternative** to using raw indexes. Whereas raw indexes are shuffled around and unreliable, you can be **almost certain** that `node->getChildByID("specific-node-id")` will **always** return the same node. 16 | 17 | For example, using string IDs, adding a new button to the left of the icon kit button in the main menu is as simple as this: 18 | ```cpp 19 | class $modify(MenuLayer) { 20 | bool init() { 21 | auto menu = this->getChildByID("main-menu"); 22 | auto iconKitBtn = menu->getChildByID("icon-kit-button"); 23 | 24 | auto btn = /* create button */; 25 | btn->setPosition(iconKitBtn->getPosition() - CCPoint { -50.f, 0.f }); 26 | menu->addChild(btn); 27 | } 28 | }; 29 | ``` 30 | Notice that there are no hardcoded indexes. Even if `main-menu` is at a completely different index than expected, as long as the main menu in `MenuLayer` has the ID `main-menu`, the code will always find it and work as expected. 31 | 32 | Although, this code does also have another interoperability concern: sure, we can be sure we added our node to the right place, but what if some other mod does the same? If another mod adds a button to the left of the icon kit button, they would overlap. To solve this issue, Geode introduces [layouts](/tutorials/layouts.md), which are discussed in their own tutorial. 33 | 34 | This also highlights the **problem with string IDs**: **someone has to assign them**. Whereas raw indexes are an intrinsic property of all CCNodes, the default ID of a node is an empty string (`""`). Geode does come with [default IDs for common classes like `MenuLayer`](https://github.com/geode-sdk/geode/blob/91cecf3843d246939be4057cdf8e7d5d607aeeb1/loader/src/hooks/MenuLayer.cpp#L150-L197), but there are unfortunately too many layers in GD to label all of them. 35 | 36 | This has, however, been taken in mind when designing the string ID system. If you find a layer you want to get a child of is missing string IDs, what you can do is **add them yourself** (using members or raw indexes), and either [send your code for adding them to Geode](https://github.com/geode-sdk/geode/pulls/new) or [let others know your mod adds these IDs](https://discord.gg/9e43WMKzhp). This way, someone else who wants to get the same children from the same layer can easily piggyback off of your logic. While the string ID system is ultimately and unfortunately based on raw indexes, its point is to **provide a single source of truth for those indexes**; once someone has done the work, no one should have to do it again. 37 | 38 | ## Viewing String IDs 39 | 40 | You can see if a layer's nodes have string IDs using the [DevTools](https://github.com/geode-sdk/devtools) mod, which shows them in the **Tree** view. 41 | 42 | ## String IDs in your own layers 43 | 44 | It is recommended for you to also use string IDs in your own layers, as this makes mod-modifying-mod interoperability much easier. 45 | 46 | ## Naming String IDs 47 | 48 | String IDs you give to nodes should be in kebab case with only lowercase a-z letters and no spaces. 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tutorials/touchpriority.md: -------------------------------------------------------------------------------- 1 | # Touch Priority 2 | 3 | You probably had this issue of "Why does my button not work?" whenever you added a button into a popup before. If not, consider yourself lucky. This tutorial will cover how Geometry Dash handles touch priority, and how you should use it. 4 | 5 | ## What is touch priority? 6 | 7 | Every layer that has `setTouchEnabled(true)` has an assigned touch priority to it. This priority is used to determine which layer will consume the given touch. 8 | 9 | This priority value is completely independent of the Z order of the layer, which would be the preferred way of doing this kind of thing. But we need to deal with what we have. 10 | 11 | The smaller the priority value is, the higher its priority. This means a priority of -1 will be checked first than a priority of 0. 12 | 13 | ## How Geometry Dash uses touch priority 14 | 15 | Disregarding all popups, Geometry Dash touch priority is completely equivalent to how Cocos2d handles it. Every layer has touch priority 0 by default. `CCMenu`'s have a touch priority of -128. Other than these, Robtop classes such as `CCTextInputNode` and `SliderTouchLogic` have a touch priority of -500. 16 | 17 | ### Force priority 18 | 19 | If you've been in the Geometry Dash modding community enough, you've probably heard of this term at least once. Force priority is the system Robtop implemented into Cocos2d in order to handle his popups. One of the most hated additions of Geometry Dash solely because how unintuitive it is. 20 | 21 | There are two values related to force priority: `forcePrio` and `targetPrio` 22 | 23 | Force priority tracks the current force priority applied to the popup. Popups (mainly `FLAlertLayer` but other popups do it as well) change this value to set the current force priority. It starts from -500 with a single popup, and decreases by 2 for each consequent popup. 24 | 25 | Target priority tracks the target priority that should apply to layers such as `CCTextInputNode` or `SliderTouchLogic` and `CCMenu`. If the touch priority requested by these classes are bigger value than the target priority, they are set to the target priority instead. Target priority values are the same as the force priority values. 26 | 27 | Robtop has 3 main functions for dealing with force priority: `CCTouchDispatcher::registerForcePrio`, `CCTouchDispatcher::unregisterForcePrio`, `CCTouchDispatcher::addPrioTargetedDelegate`. Registering decreases the target priority, unregistering increases the target priority. Adding a prio targeted delegate allows the layer to use the target priority decreased by 1 if force priority is active. 28 | 29 | ## How to use it myself 30 | 31 | If you're not dealing with any popups, chances are you don't need to deal with any of these either. But if you are, you should follow these guidelines. 32 | 33 | If your popup subclasses `geode::Popup<>`, the registering and unregistering is handled automatically. 34 | 35 | If you're not using `geode::Popup<>` class and directly subclassing `FLAlertLayer` instead, you should call `FLAlertLayer::init(int opacity)` inside your init. This will handle the registering of the force priority, along with creating the `m_mainLayer`. You should also override `registerWithTouchDispatcher` and call `CCTouchDispatcher::addTargetedDelegate` in it, since that will allow you to register the popup not as a prio targeted delegate. If you leave it the default, `FLAlertLayer` will have one less priority than your layers (-503 vs -502), meaning none of the touches will go to your layers since they will be consumed. And lastly, `~FLAlertLayer` handles unregistering of the force priority. 36 | 37 | If you want to set the priorities manually (such as for an overlay), you can call `CCLayer::setTouchPriority`/`CCMenu::setHandlerPriority` on your layer with the `CCTouchDispatcher::getTargetPrio` value. -------------------------------------------------------------------------------- /tutorials/utils.md: -------------------------------------------------------------------------------- 1 | # Geode Utils 2 | 3 | Here is a list of all the utilities that Geode provides. 4 | 5 | ## Platform related 6 | 7 | ### geode::addresser 8 | 9 | ```cpp 10 | // Get the address of a non-virtual function 11 | auto address1 = addresser::getNonVirtual(&MenuLayer::onMoreGames); 12 | // Get the address of a virtual function 13 | auto address2 = addresser::getVirtual(&MenuLayer::init); 14 | ``` 15 | 16 | ### geode::cast 17 | 18 | ```cpp 19 | float f = 3.14f; 20 | 21 | // Performs a union cast, UB! 22 | auto p1 = cast::union_cast(f); 23 | // Performs a type punning cast, UB! 24 | auto p2 = cast::reference_cast(f); 25 | 26 | // Performs a dynamic cast void* with a static cast 27 | auto p3 = cast::base_cast(obj); 28 | // Performs a exact type match cast 29 | auto p4 = cast::exact_cast(obj); 30 | // Performs a dynamic cast that works with GD classes 31 | auto p5 = cast::typeinfo_cast(obj); 32 | ``` 33 | 34 | ### geode::utils::file 35 | 36 | ```cpp 37 | // readJson and readBinary also exist 38 | auto data1 = GEODE_UNWRAP(file::readString("file.txt")); 39 | // Reading json into a struct 40 | auto data2 = GEODE_UNWRAP(file::readFromJson("serialized.json")); 41 | 42 | // writeBinary also exists 43 | file::writeString("file.txt", "Hello, World!"); 44 | // Writing a struct to json 45 | file::writeToJson("serialized.json", MyStruct()); 46 | 47 | // createDirectoryAll also exists 48 | file::createDirectory("dir"); 49 | auto files = GEODE_UNWRAP(file::readDirectory("dir")); 50 | ``` 51 | 52 | `Zip` and `Unzip` classes exist 53 | 54 | ```cpp 55 | file::openFolder("dir"); 56 | 57 | // File picking, check the enum FilePickMode and class FilePickOptions 58 | auto task1 = file::pick(mode, options); 59 | auto task2 = file::pickMany(options); 60 | 61 | // Combined with FileWatchEvent and FileWatchFilter 62 | GEODE_UNWRAP(file::watchFile("file.txt")); 63 | ``` 64 | 65 | ### geode::utils::clipboard 66 | 67 | ```cpp 68 | auto written = clipboard::write("Hello, World!"); 69 | auto read = clipboard::read(); 70 | ``` 71 | 72 | ### geode::utils::game 73 | 74 | ```cpp 75 | game::exit(); 76 | game::restart(); 77 | ``` 78 | 79 | ### geode::utils::thread 80 | 81 | ```cpp 82 | auto name = thread::getName(); 83 | thread::setName("MyThread"); 84 | ``` 85 | 86 | ### geode::utils::permission 87 | 88 | ```cpp 89 | auto status = permission::getPermissionStatus(Permission::ReadAllFiles); 90 | permission::requestPermission(Permission::ReadAllFiles); 91 | ``` 92 | 93 | ### Abortion 94 | 95 | ```cpp 96 | utils::terminate("Error message"); 97 | utils::unreachable(); 98 | ``` 99 | 100 | ### geode::utils::web 101 | 102 | TODO: this ones long just check `Geode/utils/web.hpp` 103 | 104 | ### ObjcHook 105 | 106 | ```cpp 107 | // Hooking a method 108 | auto hook1 = GEODE_UNWRAP(ObjcHook::create("EAGLView", "initWithFrame:", &MyFunc)); 109 | auto hook2 = GEODE_UNWRAP(ObjcHook::create("EAGLView", "initWithFrame:", &MyFunc, &emptyFunc)); 110 | ``` 111 | 112 | ## Tasks 113 | 114 | Visit the [tasks](tasks.md) page for more information. 115 | 116 | ## Cocos related 117 | 118 | ### Ref and WeakRef 119 | 120 | A shared_ptr and weak_ptr like class for GD objects. 121 | 122 | ```cpp 123 | Ref m_node; 124 | ``` 125 | 126 | ### EventListenerNode 127 | 128 | A class that listens for events. It is automatically removed when the node is removed. 129 | 130 | ### ObjWrapper 131 | 132 | A class that wraps a GD object. 133 | 134 | ### geode::cocos 135 | 136 | ```cpp 137 | // children 138 | getChild 139 | getChildByTagRecursive 140 | findFirstChildRecursive 141 | getChildBySpriteFrameName 142 | getChildBySpriteName 143 | // safe check 144 | nodeOrDefault 145 | isSpriteFrameName 146 | isSpriteName 147 | nodeIsVisible 148 | // sizes 149 | calculateNodeCoverage 150 | calculateChildCoverage 151 | limitNodeSize 152 | limitNodeWidth 153 | limitNodeHeight 154 | // scene 155 | switchToScene 156 | // textures 157 | reloadTextures 158 | // file 159 | fileExistsInSearchPaths 160 | // color 161 | ccDrawColor4B 162 | invert4B 163 | invert3B 164 | lighten3B 165 | darken3B 166 | to3B 167 | to4B 168 | to4F 169 | cc3bFromHexString 170 | cc4bFromHexString 171 | // containers 172 | vectorToCCArray 173 | vectorToCCArray 174 | ccArrayToVector 175 | mapToCCDict 176 | mapToCCDict 177 | getMousePos 178 | ``` 179 | 180 | ### CCArrayInserter 181 | 182 | A `std::back_inserter` like class for CCArrays. 183 | 184 | ### CCArrayExt 185 | 186 | A wrapper for CCArray that provides iteration and indexing. 187 | 188 | ### CCDictionaryExt 189 | 190 | A wrapper for CCDictionary that provides iteration and indexing. 191 | 192 | ### CCMenuItemExt 193 | 194 | A wrapper for CCMenuItem that provides more useful constructors like taking a lambda. 195 | 196 | ### CallFuncExt 197 | 198 | A wrapper for CallFunc that takes a lambda. 199 | 200 | ### Touch priority 201 | 202 | ```cpp 203 | // recursively sets the touch priority of the children based on itself 204 | handleTouchPriority(this) 205 | ``` 206 | 207 | ## Miscellaneous 208 | 209 | ### Version stuff 210 | 211 | `VersionInfo`, `ComparableVersionInfo` and `semverCompare` 212 | 213 | ### Timer stuff 214 | 215 | `Timer` and `LogPerformance` 216 | 217 | ### geode::utils::string 218 | 219 | ``` 220 | wideToUtf8 221 | utf8ToWide 222 | toLower 223 | toUpper 224 | replace 225 | split 226 | join 227 | contains 228 | containsAny 229 | containsAll 230 | count 231 | trimLeft 232 | trimRight 233 | trim 234 | normalize 235 | startsWith 236 | endsWith 237 | caseInsensitiveCompare 238 | ``` 239 | 240 | ### SeedValue 241 | 242 | GD headers use them, its mostly transparent to you. 243 | 244 | ### geode::utils::ranges 245 | 246 | ``` 247 | find 248 | indexOf 249 | move 250 | join 251 | push 252 | concat 253 | remove 254 | filter 255 | reduce 256 | map 257 | min 258 | max 259 | reverse 260 | ``` 261 | 262 | ### geode::node_ids 263 | 264 | ``` 265 | setIDSafe 266 | setIDs 267 | switchToMenu 268 | switchChildToMenu 269 | switchChildrenToMenu 270 | detachAndCreateNode 271 | getSizeSafe 272 | ``` 273 | 274 | ### geode::utils::map 275 | 276 | ``` 277 | contains 278 | select 279 | selectAll 280 | values 281 | keys 282 | remap 283 | ``` 284 | 285 | ### JsonExpectedValue 286 | 287 | ```cpp 288 | auto json = checkJson(value, "[root]"); 289 | // check the class JsonExpectedValue 290 | ``` 291 | 292 | ### Random stuff 293 | 294 | ``` 295 | toBytes 296 | getOr 297 | hash 298 | intToHex 299 | numToString 300 | numToAbbreviatedString 301 | numFromString 302 | timePointAsString` 303 | ``` 304 | 305 | ### ColorProvider 306 | 307 | No idea how to use this one. --------------------------------------------------------------------------------