Merge branch 'main' into layout

This commit is contained in:
HJfod 2022-10-15 18:49:07 +03:00
commit c926f69790
135 changed files with 3119 additions and 1429 deletions

65
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View file

@ -0,0 +1,65 @@
name: Bug Report
description: Report a bug where something is not working as expected, which does not crash the game.
labels: [ "unverified", "bug" ]
body:
- type: dropdown
id: platform
attributes:
label: Platform
description: The platform you were using when this bug was encountered.
options:
- "MacOS"
- "Windows"
validations:
required: true
- type: input
id: version
attributes:
label: Geode Version
description: The version of Geode you were using when this bug was encountered. If you do not know where to find this, look for the mods listing.
placeholder: "Example: v0.5.4"
validations:
required: true
- type: textarea
id: mods
attributes:
label: Mods Installed
description: The mods and their respective versions used when this bug was encountered. If you were not using mods, please leave this field blank.
placeholder: "Example: geode.test v1.0.0, geode.testdep v0.1.0"
validations:
required: false
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What you expected to happen. Attach screenshots here as necessary.
placeholder: "Example: Expected to produce X by consuming Y."
validations:
required: true
- type: textarea
id: result
attributes:
label: Actual Behavior
description: What happened despite your expectations. Attach screenshots here as necessary.
placeholder: "Example: Produced one X but Y was not consumed."
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to Reproduce
description: How to reproduce the bug.
placeholder: "Example: 1) I did X..."
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: Any additional information you wish to provide. Please add anything which did not fit into the other sections here.
placeholder: "Example: This is likely caused by X because..."
validations:
required: false
- type: markdown
attributes:
value: Thank you for taking the time to fill out this bug report.

4
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,4 @@
blank_issues_enabled: false
contact_links:
- name: Discord link
about: Find us at https://discord.gg/9e43WMKzhp

57
.github/ISSUE_TEMPLATE/crash-report.yml vendored Normal file
View file

@ -0,0 +1,57 @@
name: Crash Report
description: Report a bug that crashes the game or prevents startup.
labels: [ "unverified", "crash" ]
body:
- type: dropdown
id: platform
attributes:
label: Platform
description: The platform you were using when this bug was encountered.
options:
- "MacOS"
- "Windows"
validations:
required: true
- type: input
id: version
attributes:
label: Geode Version
description: The version of Geode you were using when this bug was encountered. If you do not know where to find this, look for the mods listing.
placeholder: "Example: v0.5.4"
validations:
required: true
- type: textarea
id: mods
attributes:
label: Mods Installed
description: The mods and their respective versions used when this bug was encountered. If you were not using mods, please leave this field blank.
placeholder: "Example: geode.test v1.0.0, geode.testdep v0.1.0"
validations:
required: false
- type: textarea
id: reproduction
attributes:
label: Steps to Reproduce
description: How to reproduce the crash.
placeholder: "Example: 1) I did X..."
validations:
required: true
- type: textarea
id: crash-report
attributes:
label: Crash Report
description: "You can find the crash report in `geode/crashlogs/`, if one was created. If you are using MacOS you can find the crash report in `Console.app` too. Additionally, please attach latest log in `geode/log/`. If either or both files do not exist, state such. Please link to a paste site with their content, such as GitHub Gists or Pastebin. **Do not paste the contents of either these files directly into the text box.**"
placeholder: "Example: a link to a paste site with the crash report and latest.log."
validations:
required: false
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: Any additional information you wish to provide. Please add anything which did not fit into the other sections here.
placeholder: "Example: This is likely caused by X because..."
validations:
required: false
- type: markdown
attributes:
value: Thank you for taking the time to fill out this crash report.

View file

@ -0,0 +1,39 @@
name: Request a Feature
description: Request a new feature or a change to an existing one.
labels: [ "feature" ]
body:
- type: input
id: version
attributes:
label: Geode Version
description: The version of Geode you are using for suggesting a feature on. If you do not know where to find this, look for the mods listing.
placeholder: "Example: v0.5.4"
validations:
required: true
- type: textarea
id: problem
attributes:
label: Related Problem
description: If the feature you wish to change is related to a problem, please describe it. Leave this field blank if it is not related to a problem.
placeholder: "Example: I'm always frustrated when..."
validations:
required: false
- type: textarea
id: solution
attributes:
label: Your Solution
description: Describe the solution you would like to have happen.
placeholder: "Example: If I could..."
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: Any additional information you wish to provide. Please add anything which did not fit into the other sections here.
placeholder: "Example: This is likely achieveable by doing X because..."
validations:
required: false
- type: markdown
attributes:
value: Thank you for taking the time to fill out this feature request.

19
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View file

@ -0,0 +1,19 @@
name: Ask a Question
description: Ask a question regarding this project.
labels: [ "question" ]
body:
- type: markdown
attributes:
value: |
If you are in need of quick response, Discord may be a better place. We are quite active on Discord, so you may get responses quicker.
- type: textarea
id: question
attributes:
label: Your Question
description: Feel free to ask any question regarding this project here.
placeholder: "Example: How can I...?"
validations:
required: true
- type: markdown
attributes:
value: Thank you for taking the time to ask me a question.

View file

@ -17,12 +17,18 @@ jobs:
os: windows-2019
prefixes: ''
extra_flags: '-G "Visual Studio 16 2019" -T host=x86 -A win32'
out_paths: './bin/nightly/geode.dll ./bin/nightly/GeodeBootstrapper.dll ./bin/nightly/geode.lib ./bin/nightly/XInput9_1_0.dll'
out_paths: './bin/nightly/Geode.dll ./bin/nightly/GeodeBootstrapper.dll ./bin/nightly/Geode.lib ./bin/nightly/XInput9_1_0.dll'
cli_name: '*-win.zip'
cli_cmd: ''
- name: "macOS"
os: macos-latest
prefixes: 'PATH="/usr/local/opt/ccache/libexec:$PATH"'
extra_flags: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON"
extra_flags: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
out_paths: './bin/nightly/Geode.dylib ./bin/nightly/GeodeBootstrapper.dylib'
cli_name: '*-mac.zip'
cli_cmd: 'chmod +x $GITHUB_WORKSPACE/cli/geode'
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
@ -42,14 +48,33 @@ jobs:
key: ${{ matrix.config.os }}
if: matrix.config.os == 'macos-latest'
- name: Download CLI
uses: robinraju/release-downloader@v1.5
with:
repository: geode-sdk/cli
latest: true
fileName: ${{ matrix.config.cli_name }}
tarBall: false
zipBall: false
out-file-path: "cli"
- name: Unzip CLI
run: |
7z x "${{ github.workspace }}/cli/${{ matrix.config.cli_name }}" -o"${{ github.workspace }}/cli"
- name: Add CLI to Path
run: |
${{ matrix.config.cli_cmd }}
echo "${{ github.workspace }}/cli" >> $GITHUB_PATH
- name: Configure CMake
run: |
${{ matrix.config.prefixes }} cmake -B ${{ github.workspace }}/build ${{ matrix.config.extra_flags }} -DGEODE_DISABLE_CLI_CALLS=ON
${{ matrix.config.prefixes }} cmake -B ${{ github.workspace }}/build ${{ matrix.config.extra_flags }} -DGEODE_DISABLE_CLI_CALLS=ON -DCLI_PATH="${{ github.workspace }}/cli"
- name: Build
run: |
cd build
cmake --build . --config Release
cmake --build . --config RelWithDebInfo
- name: Move to output folder
shell: bash

1
.gitignore vendored
View file

@ -43,6 +43,7 @@ build
bin
loader/src/internal/about.hpp
loader/src/internal/resources.hpp
loader/resources/mod.json
fods-catgirl-hideout.txt

View file

@ -1,5 +1,56 @@
# Geode Changelog
## v0.4.8
- CLI issues fixed in v1.0.6 so loader again verifies if loader resources are missing / corrupt on startup
- Resource download text is no longer a popup on top of the title but instead just replaces the loading text
- Add delegates to `EditLevelLayer`
## v0.4.7
- Loader resources check would always fail due to CLI issues, so for now loader just checks if the resources folder exists
## v0.4.6
- Automatically checks & downloads loader resources if they are missing / corrupt on startup
- CMake rework; `GeodeFile.cmake` now checks and verifies CLI version
- Add optional `DONT_INSTALL` parameter to `create_geode_file`
- Test mods are now no longer automatically installed
- Add `package_geode_resources_now` command for packaging resources at configure time and creating a header with their calculated hashes
- Fix `getSceneDelegate`
- Change `CCArrayExt` to use `Ref`
## v0.4.5
- Rework bindings and codegen to improve compile times, now individual bindings can be included with `<Geode/binding/{ClassName}.hpp>`
- Modify has also been separated, you can now include individual modifiers with `<Geode/modify/{ClassName}.hpp>`
- Various other fixes to improve compile times
- Fix mod resources not being loaded when installed from Index
- Fix crashes related to downloading mods
- Fix `Loader::queueInGDThread` sometimes leaving out functions
- Fix crashes related to logging
- Add new overloads to `file` utils and deprecate ones that don't use `ghc::filesystem::path`
- Index mods now show their `about.md` files
- More addresses
- Various other fixes & improvements
- Index reworked
- Fix issues with `VERSION` file
- Add `GEODE_DEBUG` macro for enabling `log::debug` to actually print stuff
- Show crashlog on crash when `GEODE_DEBUG` is enabled
- Add `JsonChecker::at` and `JsonChecker::array` for dealing with arrays
- Add `geode::utils::web::fetchBytes` for fetching a byte array synchronously
- Add `geode::utils::web::AsyncWebRequest` for creating thread-safe asynchronous web requests
- Add `Loader::updateModResourcePaths` for adding a mods' resources to search paths. Not recommended to be called manually
- Add an overload to `geode::createQuickPopup` for specifying popup width
- `ModInfo::createFromFile` now checks for `about.md` and other special files in the same directory
- Remove automatic mod updating for now, however automatic update checking for mods is still there
## v0.4.4
- New `listenForSettingChanges` API for more ergonomically listening for setting changes
- Fixed bug where GD was unopenable through Steam
- Various other internal fixes
## v0.4.3
- Simplified the minimum and maximum loader versions, loader will now load any mod whose target version major and minor match. In practice, this means that for example mods whose target version is v0.4.8 can be loaded by loader of version v0.4.6.

View file

@ -2,19 +2,20 @@ cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build libraries static" FORCE)
file(READ VERSION GEODE_VERSION)
string(STRIP "${GEODE_VERSION}" GEODE_VERSION)
project(geode-sdk VERSION ${GEODE_VERSION} LANGUAGES CXX C)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (APPLE)
if (PROJECT_IS_TOP_LEVEL AND APPLE)
set(CMAKE_BUILD_TYPE Debug)
endif()
add_library(${PROJECT_NAME} INTERFACE)
if (GEODE_DEBUG)
target_compile_definitions(${PROJECT_NAME} INTERFACE GEODE_DEBUG)
if (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo)
target_compile_definitions(${PROJECT_NAME} INTERFACE -DGEODE_DEBUG)
endif()
# Rerun CMake on VERSION file change
@ -22,6 +23,10 @@ set_target_properties(${PROJECT_NAME} PROPERTIES CMAKE_CONFIGURE_DEPENDS VERSION
target_compile_definitions(${PROJECT_NAME} INTERFACE -DPROJECT_NAME=${CMAKE_PROJECT_NAME})
if (GEODE_DEBUG)
target_compile_definitions(${PROJECT_NAME} INTERFACE GEODE_DEBUG)
endif()
set(GEODE_CODEGEN_PATH ${CMAKE_CURRENT_BINARY_DIR}/codegenned)
set(GEODE_BIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
set(GEODE_LOADER_PATH ${CMAKE_CURRENT_SOURCE_DIR}/loader)
@ -38,21 +43,22 @@ add_custom_target(CodegenRun ALL
COMMAND Codegen ${GEODE_TARGET_PLATFORM} bindings ${GEODE_CODEGEN_PATH}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Run Codegen"
BYPRODUCTS ${GEODE_CODEGEN_PATH}/GeneratedSource.cpp
BYPRODUCTS ${GEODE_CODEGEN_PATH}/Geode/GeneratedSource.cpp
)
add_dependencies(${PROJECT_NAME} CodegenRun)
add_dependencies(CodegenRun Codegen)
# Hacky way to supress the not generated error
if (NOT EXISTS ${GEODE_CODEGEN_PATH}/GeneratedSource.cpp)
if (NOT EXISTS ${GEODE_CODEGEN_PATH}/Geode/GeneratedSource.cpp)
make_directory(${GEODE_CODEGEN_PATH})
file(TOUCH ${GEODE_CODEGEN_PATH}/GeneratedSource.cpp)
make_directory(${GEODE_CODEGEN_PATH}/Geode)
file(TOUCH ${GEODE_CODEGEN_PATH}/Geode/GeneratedSource.cpp)
endif()
target_sources(${PROJECT_NAME} INTERFACE ${GEODE_CODEGEN_PATH}/GeneratedSource.cpp)
target_include_directories(${PROJECT_NAME} INTERFACE ${GEODE_CODEGEN_PATH}/..)
target_sources(${PROJECT_NAME} INTERFACE ${GEODE_CODEGEN_PATH}/Geode/GeneratedSource.cpp)
target_include_directories(${PROJECT_NAME} INTERFACE
${GEODE_CODEGEN_PATH}
${GEODE_LOADER_PATH}/include
${GEODE_LOADER_PATH}/include/Geode/cocos/
${GEODE_LOADER_PATH}/include/Geode/cocos/cocos2dx
@ -87,7 +93,13 @@ if (PROJECT_IS_TOP_LEVEL)
target_link_libraries(${PROJECT_NAME} INTERFACE geode-loader)
elseif(EXISTS ${GEODE_PLATFORM_BIN_PATH})
target_link_libraries(${PROJECT_NAME} INTERFACE "${GEODE_PLATFORM_BIN_PATH}")
target_precompile_headers(${PROJECT_NAME} INTERFACE "${GEODE_LOADER_PATH}/include/Geode/Geode.hpp")
target_precompile_headers(${PROJECT_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/DefaultInclude.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/Loader.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/UI.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/cocos/cocos2dx/include/cocos2d.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/cocos/extensions/cocos-ext.h"
)
else()
message(FATAL_ERROR "No valid loader binary to link to! Install a pre-built binary for version ${PROJECT_VERSION} with Geode CLI or build it from source.")
endif()

View file

@ -9,7 +9,8 @@
There's nothing worse than having to read thousands of words just to see what some library's code actually looks like, so instead, here's a **Hello World** in Geode right off the bat:
```cpp
#include <Geode/Geode.hpp>
#include <Geode/bindings.hpp>
#include <Geode/modify/MenuLayer.hpp>
USE_GEODE_NAMESPACE();

View file

@ -1 +1 @@
0.4.3
0.4.8

View file

@ -115,9 +115,6 @@ class cocos2d::CCDirector {
virtual auto getAccelerometer() = mac 0x24b0e0;
virtual auto setAccelerometer(cocos2d::CCAccelerometer*) = mac 0x24b0a0;
virtual auto getDeltaTime() = mac 0x249bd0;
virtual auto getSceneDelegate() = mac 0x24b320;
virtual auto setSceneDelegate(cocos2d::CCSceneDelegate*) = mac 0x24b330;
auto getWinSize() = mac 0x24a0f0, ios 0xece34;
auto pushScene(cocos2d::CCScene*) = mac 0x24a620;
@ -341,17 +338,17 @@ class cocos2d::CCLayerColor {
CCLayerColor() = mac 0x274320, ios 0xc8aec;
static cocos2d::CCLayerColor* create(cocos2d::_ccColor4B const&, float, float) = mac 0x2745e0;
static cocos2d::CCLayerColor* create(cocos2d::_ccColor4B const&) = mac 0x2744c0;
auto draw() = mac 0x274b50, ios 0xc8fe0;
auto getBlendFunc() = mac 0x274480, ios 0xc8bcc;
auto init() = mac 0x274800, ios 0xc8de8;
auto initWithColor(cocos2d::_ccColor4B const&) = mac 0x2749a0, ios 0xc8f14;
auto initWithColor(cocos2d::_ccColor4B const&, float, float) = mac 0x274850, ios 0xc8e34;
auto setBlendFunc(cocos2d::_ccBlendFunc) = mac 0x2744a0, ios 0xc8bdc;
auto setColor(cocos2d::_ccColor3B const&) = mac 0x274c20, ios 0xc90ac;
auto setContentSize(cocos2d::CCSize const&) = mac 0x2749f0, ios 0xc8f64;
auto setOpacity(unsigned char) = mac 0x274db0, ios 0xc9108;
auto updateColor() = mac 0x274ae0, ios 0xc8f80;
~CCLayerColor() = mac 0x2743d0, ios 0x2743e0;
virtual auto draw() = mac 0x274b50, ios 0xc8fe0;
virtual auto getBlendFunc() = mac 0x274480, ios 0xc8bcc;
virtual auto init() = mac 0x274800, ios 0xc8de8;
virtual auto initWithColor(cocos2d::_ccColor4B const&) = mac 0x2749a0, ios 0xc8f14;
virtual auto initWithColor(cocos2d::_ccColor4B const&, float, float) = mac 0x274850, ios 0xc8e34;
virtual auto setBlendFunc(cocos2d::_ccBlendFunc) = mac 0x2744a0, ios 0xc8bdc;
virtual auto setColor(cocos2d::_ccColor3B const&) = mac 0x274c20, ios 0xc90ac;
virtual auto setContentSize(cocos2d::CCSize const&) = mac 0x2749f0, ios 0xc8f64;
virtual auto setOpacity(unsigned char) = mac 0x274db0, ios 0xc9108;
virtual auto updateColor() = mac 0x274ae0, ios 0xc8f80;
virtual ~CCLayerColor() = mac 0x2743d0, ios 0x2743e0;
}
class cocos2d::CCLayerRGBA {
@ -644,6 +641,10 @@ class cocos2d::CCPoolManager {
static cocos2d::CCPoolManager* sharedPoolManager() = mac 0x2142c0;
}
class cocos2d::CCRemoveSelf {
static cocos2d::CCRemoveSelf* create(bool) = mac 0x454700;
}
class cocos2d::CCRenderTexture {
auto begin() = mac 0x35ce10;
auto end() = mac 0x35d2c0;

View file

@ -92,8 +92,8 @@ class AudioEffectsLayer {
}
class BoomListView : cocos2d::CCLayer, TableViewDelegate, TableViewDataSource {
inline BoomListView() {}
inline ~BoomListView() {
BoomListView() {}
~BoomListView() {
CC_SAFE_RELEASE(m_entries);
}
@ -1034,7 +1034,7 @@ class EditButtonBar : cocos2d::CCNode {
cocos2d::CCArray* m_pagesArray;
}
class EditLevelLayer : cocos2d::CCLayer {
class EditLevelLayer : cocos2d::CCLayer, FLAlertLayerProtocol, TextInputDelegate, UploadActionDelegate, UploadPopupDelegate, SetIDPopupDelegate {
static void scene(GJGameLevel* level) {
auto scene = cocos2d::CCScene::create();
@ -3760,7 +3760,7 @@ class LevelPage {
class LevelSearchLayer : cocos2d::CCLayer {
static LevelSearchLayer* create() = mac 0x0, win 0x17d9c0, ios 0x0;
bool init() = mac 0x384770, win 0x0, ios 0x0;
bool init() = mac 0x384770, win 0x17da60, ios 0x0;
GJSearchObject* getSearchObject(SearchType, gd::string) = mac 0x388a50, win 0x1805f0, ios 0x0;
void onMoreOptions(cocos2d::CCObject*) = mac 0x0, win 0x17f500, ios 0x0;
void onSearch(cocos2d::CCObject*) = mac 0x0, win 0x180fc0, ios 0x0;
@ -3865,6 +3865,7 @@ class LoadingLayer : cocos2d::CCLayer {
m_fromRefresh = value;
}
LoadingLayer() {}
static LoadingLayer* create(bool fromReload) = mac 0x1df1f0, win 0x18bfe0, ios 0x130278;
bool init(bool fromReload) = mac 0x1df2f0, win 0x18c080, ios 0x0;
const char* getLoadingString() = mac 0x0, win 0x18cf40, ios 0x0;
@ -4122,6 +4123,8 @@ class PlayLayer : GJBaseGameLayer, CCCircleWaveDelegate, CurrencyRewardDelegate,
return GameManager::sharedState()->getPlayLayer();
}
PlayLayer() = win 0x1FAA90;
void addCircle(CCCircleWave*) = mac 0x7e0f0, win 0x0, ios 0x0;
void addObject(GameObject*) = mac 0x70e50, win 0x2017e0, ios 0x0;
void addToGroupOld(GameObject*) = mac 0x77680, win 0x0, ios 0x0;

View file

@ -1,11 +1,39 @@
set(GEODE_CLI_MINIMUM_VERSION 1.0.5)
# for passing CLI through CMake arguments
if (DEFINED CLI_PATH)
list(APPEND CMAKE_PROGRAM_PATH ${CLI_PATH})
endif()
# Find Geode CLI
if (NOT DEFINED GEODE_CLI)
find_program(GEODE_CLI NAMES geode.exe geode-cli.exe geode geode-cli)
endif()
# Check if CLI was found
if (GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
message(STATUS "Unable to find Geode CLI")
else()
message(STATUS "Found Geode CLI: ${GEODE_CLI}")
# `geode --version` returns `geode x.x.x\n` so gotta do some wacky shit
execute_process(
COMMAND ${GEODE_CLI} --version
OUTPUT_VARIABLE GEODE_CLI_VERSION
)
# Remove trailing newline
string(STRIP ${GEODE_CLI_VERSION} GEODE_CLI_VERSION)
# Remove program name
string(REPLACE "geode " "" GEODE_CLI_VERSION ${GEODE_CLI_VERSION})
# Need at least v1.0.5 (--shut-up arg in geode package resources)
if (${GEODE_CLI_VERSION} VERSION_LESS ${GEODE_CLI_MINIMUM_VERSION})
message(FATAL_ERROR
"Found Geode CLI: ${GEODE_CLI}, however it is version ${GEODE_CLI_VERSION} "
"while minimum required is version ${GEODE_CLI_MINIMUM_VERSION}. Please update: "
"https://github.com/geode-sdk/cli/releases/latest"
)
endif()
message(STATUS "Found Geode CLI: ${GEODE_CLI} (version ${GEODE_CLI_VERSION})")
endif()
function(create_geode_file_old proname)
@ -36,6 +64,10 @@ function(create_geode_file_old proname)
endfunction()
function(create_geode_file proname)
# Get DONT_INSTALL argument
set(options DONT_INSTALL)
cmake_parse_arguments(CREATE_GEODE_FILE "${options}" "" "" ${ARGN})
if (GEODE_DISABLE_CLI_CALLS)
message("Skipping creating geode file for ${proname}")
return()
@ -57,11 +89,17 @@ function(create_geode_file proname)
OUTPUT_VARIABLE MOD_ID
)
if (CREATE_GEODE_FILE_DONT_INSTALL)
set(INSTALL_ARG "")
else()
set(INSTALL_ARG "--install")
endif()
set_target_properties(${proname} PROPERTIES PREFIX "")
set_target_properties(${proname} PROPERTIES OUTPUT_NAME ${MOD_ID})
add_custom_target(${proname}_PACKAGE ALL
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR} --binary $<TARGET_FILE:${proname}> --output $<TARGET_FILE_DIR:${proname}>/${proname}.geode --install
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR} --binary $<TARGET_FILE:${proname}> --output $<TARGET_FILE_DIR:${proname}>/${proname}.geode ${INSTALL_ARG}
VERBATIM USES_TERMINAL
)
endfunction()
@ -75,7 +113,10 @@ function(package_geode_resources proname src dest)
message(STATUS "Packaging resources from ${src} into ${dest}")
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
message(WARNING "package_geode_resources called, but Geode CLI was not found - You will need to manually package the resources")
message(WARNING
"package_geode_resources called, but Geode CLI was "
"not found - You will need to manually package the resources"
)
else()
add_custom_target(${proname}_PACKAGE ALL
@ -83,5 +124,64 @@ function(package_geode_resources proname src dest)
COMMAND ${GEODE_CLI} package resources ${src} ${dest}
VERBATIM USES_TERMINAL
)
endif()
endfunction()
function(package_geode_resources_now proname src dest header_dest)
if (GEODE_DISABLE_CLI_CALLS)
message(WARNING
"package_geode_resources_now called, but GEODE_DISABLE_CLI_CALLS
is set to true - Ignoring it as this function requires CLI calls
in order to work"
)
endif()
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
message(FATAL_ERROR
"package_geode_resources_now called, but Geode CLI "
"was not found - Please install Geode CLI from "
"https://github.com/geode-sdk/cli/releases/latest"
)
return()
endif()
message(STATUS "Packaging resources now from ${src} into ${dest}")
execute_process(
COMMAND ${GEODE_CLI} package resources ${src} ${dest} --shut-up
RESULT_VARIABLE GEODE_PACKAGE_RES
)
if (NOT GEODE_PACKAGE_RES EQUAL "0")
message(FATAL_ERROR
"Command \"${GEODE_CLI} package resources ${src} ${dest}\" returned "
"${GEODE_PACKAGE_RES} - Expected 0"
)
endif()
file(GLOB RESOURCE_FILES "${dest}/*.*")
set(HEADER_FILE
"#include <unordered_map>\n\n"
"static const std::unordered_map<std::string, std::string> "
"LOADER_RESOURCE_HASHES {\n"
)
foreach(file ${RESOURCE_FILES})
cmake_path(GET file FILENAME FILE_NAME)
if (NOT FILE_NAME STREQUAL ".geode_cache")
file(SHA256 ${file} COMPUTED_HASH)
list(APPEND HEADER_FILE "\t{ \"${FILE_NAME}\", \"${COMPUTED_HASH}\" },\n")
endif()
endforeach()
list(APPEND HEADER_FILE "}\;\n")
file(WRITE ${header_dest} ${HEADER_FILE})
message(STATUS "Wrote resource hashes to ${header_dest}")
endfunction()

View file

@ -3,7 +3,7 @@
namespace { namespace format_strings {
char const* declare_address = R"GEN(
GEODE_NOINLINE GEODE_HIDDEN inline static uintptr_t address{index}() {{
GEODE_INLINE GEODE_HIDDEN static uintptr_t address{index}() {{
static uintptr_t ret = {address};
return ret;
}}

View file

@ -3,34 +3,43 @@
#include <set>
namespace { namespace format_strings {
char const* class_predeclare = "class {class_name};\n";
// requires: base_classes, class_name
char const* binding_include = R"GEN(#include "binding/{file_name}"
)GEN";
char const* class_includes = R"GEN(#pragma once
#include <Geode/c++stl/gdstdlib.hpp>
#include <cocos2d.h>
#include <cocos-ext.h>
#include <Geode/GeneratedPredeclare.hpp>
#include <Geode/Enums.hpp>
)GEN";
char const* class_include_prereq = R"GEN(#include "{file_name}"
)GEN";
char const* class_start = R"GEN(
class {class_name}{base_classes} {{
public:
)GEN";
char const* monostate_constructor = R"GEN(
GEODE_MONOSTATE_CONSTRUCTOR_GD({class_name}, {first_base})
char const* monostate_constructor = R"GEN( GEODE_MONOSTATE_CONSTRUCTOR_GD({class_name}, {first_base})
)GEN";
char const* monostate_constructor_cutoff = R"GEN(
GEODE_MONOSTATE_CONSTRUCTOR_CUTOFF({class_name}, {first_base})
char const* monostate_constructor_cutoff = R"GEN( GEODE_MONOSTATE_CONSTRUCTOR_CUTOFF({class_name}, {first_base})
)GEN";
char const* function_definition = R"GEN(
{docs}{static}{virtual}{return_type} {function_name}({parameters}){const};
char const* function_definition = R"GEN({docs} {static}{virtual}{return_type} {function_name}({parameters}){const};
)GEN";
char const* error_definition = R"GEN(
template <bool T=false>
char const* error_definition = R"GEN( template <bool T=false>
{static}{return_type} {function_name}({parameters}){const}{{
static_assert(T, "Implement {class_name}::{function_name}");
}}
)GEN";
char const* error_definition_virtual = R"GEN(
[[deprecated("Use of undefined virtual function - will crash at runtime!!!")]]
char const* error_definition_virtual = R"GEN( [[deprecated("Use of undefined virtual function - will crash at runtime!!!")]]
{virtual}{return_type} {function_name}({parameters}){const}{{
#ifdef GEODE_NO_UNDEFINED_VIRTUALS
static_assert(false, "Undefined virtual function - implement in GeometryDash.bro");
@ -43,52 +52,52 @@ public:
{function_name}({parameters});)GEN";
// requires: type, member_name, array
char const* member_definition = R"GEN(
{type} {member_name};)GEN";
char const* member_definition = R"GEN( {type} {member_name};
)GEN";
char const* pad_definition = R"GEN(
GEODE_PAD({hardcode});)GEN";
char const* unimplemented_definition = R"GEN(
GEODE_UNIMPLEMENTED_PAD)GEN";
char const* pad_definition = R"GEN( GEODE_PAD({hardcode});
)GEN";
// requires: hardcode_macro, type, member_name, hardcode
char const* hardcode_definition = R"GEN(
CLASSPARAM({type}, {member_name}, {hardcode});)GEN";
char const* class_end = R"GEN(
};
char const* class_end = R"GEN(};
)GEN";
}}
std::string generateGDHeader(Root& root) {
std::string output("#pragma once\n#include <Geode/c++stl/gdstdlib.hpp>\n#include <cocos2d.h>\n");
for (auto& cls : root.classes) {
if (can_find(cls.name, "cocos2d"))
continue;
output += fmt::format(::format_strings::class_predeclare,
fmt::arg("class_name", cls.name)
);
}
std::string generateBindingHeader(Root& root, ghc::filesystem::path const& singleFolder) {
std::string output;
for (auto& cls : root.classes) {
if (can_find(cls.name, "cocos2d"))
continue;
std::string filename = (codegen::getUnqualifiedClassName(cls.name) + ".hpp");
output += fmt::format(format_strings::binding_include,
fmt::arg("file_name", filename)
);
std::string single_output;
single_output += format_strings::class_includes;
for (auto dep : cls.depends) {
if (can_find(dep, "cocos2d::")) continue;
std::string depfilename = (codegen::getUnqualifiedClassName(dep) + ".hpp");
single_output += fmt::format(format_strings::class_include_prereq, fmt::arg("file_name", depfilename));
}
std::string supers = str_if(
fmt::format(" : public {}", fmt::join(cls.superclasses, ", ")),
!cls.superclasses.empty()
);
output += fmt::format(::format_strings::class_start,
single_output += fmt::format(::format_strings::class_start,
fmt::arg("class_name", cls.name),
fmt::arg("base_classes", supers)
);
// what.
if (!cls.superclasses.empty()) {
output += fmt::format(
single_output += fmt::format(
can_find(cls.superclasses[0], "cocos2d")
? format_strings::monostate_constructor_cutoff
: format_strings::monostate_constructor,
@ -97,15 +106,17 @@ std::string generateGDHeader(Root& root) {
);
}
bool unimplementedField = false;
for (auto field : cls.fields) {
FunctionBegin* fb;
char const* used_format = format_strings::function_definition;
if (auto i = field.get_as<InlineField>()) {
output += "\t" + i->inner + "\n";
single_output += "\t" + i->inner + "\n";
continue;
} else if (auto m = field.get_as<MemberField>()) {
output += fmt::format(format_strings::member_definition,
if (unimplementedField) single_output += "\t[[deprecated(\"Member placed incorrectly - will crash at runtime!!!\")]]\n";
single_output += fmt::format(format_strings::member_definition,
fmt::arg("type", m->type.name),
fmt::arg("member_name", m->name + str_if(fmt::format("[{}]", m->count), m->count))
);
@ -114,9 +125,9 @@ std::string generateGDHeader(Root& root) {
auto hardcode = codegen::platformNumber(p->amount);
if (hardcode) {
output += fmt::format(format_strings::pad_definition, fmt::arg("hardcode", hardcode));
single_output += fmt::format(format_strings::pad_definition, fmt::arg("hardcode", hardcode));
} else {
output += "\n GEODE_UNIMPLEMENTED_PAD";
unimplementedField = true;
}
continue;
} else if (auto fn = field.get_as<OutOfLineField>()) {
@ -135,7 +146,7 @@ std::string generateGDHeader(Root& root) {
}
}
output += fmt::format(used_format,
single_output += fmt::format(used_format,
fmt::arg("virtual", str_if("virtual ", fb->is_virtual)),
fmt::arg("static", str_if("static ", fb->is_static)),
fmt::arg("class_name", cls.name),
@ -149,9 +160,9 @@ std::string generateGDHeader(Root& root) {
}
// if (hasClass)
output += ::format_strings::class_end;
single_output += ::format_strings::class_end;
// queued.pop_front();
writeFile(singleFolder / filename, single_output);
}
return output;

View file

@ -3,21 +3,6 @@
using namespace codegen;
void writeFile(ghc::filesystem::path const& writePath, std::string const& output) {
std::ifstream readfile;
readfile >> std::noskipws;
readfile.open(writePath);
std::string data((std::istreambuf_iterator<char>(readfile)), std::istreambuf_iterator<char>());
readfile.close();
if (data != output) {
std::ofstream writefile;
writefile.open(writePath);
writefile << output;
writefile.close();
}
}
int main(int argc, char** argv) try {
if (argc != 4) throw codegen::error("Invalid number of parameters (expected 3 found {})", argc-1);
@ -31,8 +16,10 @@ int main(int argc, char** argv) try {
chdir(argv[2]);
ghc::filesystem::path writeDir = argv[3];
auto writeDir = ghc::filesystem::path(argv[3]) / "Geode";
ghc::filesystem::create_directories(writeDir);
ghc::filesystem::create_directories(writeDir / "modify");
ghc::filesystem::create_directories(writeDir / "binding");
Root root = broma::parse_file("Entry.bro");
@ -45,11 +32,12 @@ int main(int argc, char** argv) try {
}
writeFile(writeDir / "GeneratedAddress.hpp", generateAddressHeader(root));
writeFile(writeDir / "GeneratedModify.hpp", generateModifyHeader(root)); // pretty much obsolete with a custom compiler
writeFile(writeDir / "GeneratedModify.hpp", generateModifyHeader(root, writeDir / "modify")); // pretty much obsolete with a custom compiler
writeFile(writeDir / "GeneratedWrapper.hpp", generateWrapperHeader(root)); // pretty much obsolete with a custom compiler
writeFile(writeDir / "GeneratedType.hpp", generateTypeHeader(root)); // pretty much obsolete with a custom compiler
writeFile(writeDir / "GeneratedHeader.hpp", generateGDHeader(root));
writeFile(writeDir / "GeneratedSource.cpp", generateGDSource(root));
writeFile(writeDir / "GeneratedBinding.hpp", generateBindingHeader(root, writeDir / "binding"));
writeFile(writeDir / "GeneratedPredeclare.hpp", generatePredeclareHeader(root));
writeFile(writeDir / "GeneratedSource.cpp", generateBindingSource(root));
} catch(std::exception& e) {
std::cout << "Codegen error: " << e.what() << "\n";
return 1;

View file

@ -3,34 +3,51 @@
namespace { namespace format_strings {
// requires: class_name
char const* modify_start = R"GEN(
template<class Derived>
struct Modify<Derived, {class_name}> : ModifyBase<Modify<Derived, {class_name}>> {{
using ModifyBase<Modify<Derived, {class_name}>>::ModifyBase;
using Base = {class_name};
static void apply() {{
using namespace geode::core::meta;
char const* modify_start = R"GEN(#pragma once
#include <Geode/modify/Modify.hpp>
#include <Geode/modify/Field.hpp>
#include <Geode/modify/InternalMacros.hpp>
using namespace geode::modifier;
namespace geode::modifier {{
template<class Derived>
struct Modify<Derived, {class_name}> : ModifyBase<Modify<Derived, {class_name}>> {{
using ModifyBase<Modify<Derived, {class_name}>>::ModifyBase;
using Base = {class_name};
static void apply() {{
using namespace geode::core::meta;
)GEN";
// requires: index, class_name, arg_types, function_name, raw_arg_types, non_virtual
char const* apply_function = R"GEN(
GEODE_APPLY_MODIFY_FOR_FUNCTION({index}, {function_convention}, {class_name}, {function_name}))GEN";
GEODE_APPLY_MODIFY_FOR_FUNCTION({index}, {function_convention}, {class_name}, {function_name}))GEN";
char const* modify_end = R"GEN(
}
};
}
};
}
)GEN";
char const* modify_include = R"GEN(#include "modify/{file_name}"
)GEN";
}}
std::string generateModifyHeader(Root& root) {
std::string generateModifyHeader(Root& root, ghc::filesystem::path const& singleFolder) {
std::string output;
for (auto c : root.classes) {
if (c.name == "cocos2d")
continue;
output += fmt::format(format_strings::modify_start,
std::string filename = (codegen::getUnqualifiedClassName(c.name) + ".hpp");
output += fmt::format(format_strings::modify_include,
fmt::arg("file_name", filename)
);
std::string single_output;
single_output += fmt::format(format_strings::modify_start,
fmt::arg("class_name", c.name)
);
@ -52,7 +69,7 @@ std::string generateModifyHeader(Root& root) {
break;
}
output += fmt::format(format_strings::apply_function,
single_output += fmt::format(format_strings::apply_function,
fmt::arg("index", f.field_id),
fmt::arg("class_name", c.name),
fmt::arg("function_name", function_name),
@ -61,7 +78,9 @@ std::string generateModifyHeader(Root& root) {
}
}
output += format_strings::modify_end;
single_output += format_strings::modify_end;
writeFile(singleFolder / filename, single_output);
}
return output;

View file

@ -0,0 +1,22 @@
#include "Shared.hpp"
#include <iostream>
#include <set>
namespace { namespace format_strings {
char const* class_predeclare = "class {class_name};\n";
}}
std::string generatePredeclareHeader(Root& root) {
std::string output("#pragma once\n");
for (auto& cls : root.classes) {
if (can_find(cls.name, "cocos2d"))
continue;
output += fmt::format(::format_strings::class_predeclare,
fmt::arg("class_name", cls.name)
);
}
return output;
}

View file

@ -5,6 +5,7 @@
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <fstream>
#include <fs/filesystem.hpp> // bruh
using std::istreambuf_iterator;
@ -19,13 +20,29 @@ using std::istreambuf_iterator;
#endif
std::string generateAddressHeader(Root& root);
std::string generateModifyHeader(Root& root);
std::string generateModifyHeader(Root& root, ghc::filesystem::path const& singleFolder);
std::string generateWrapperHeader(Root& root);
std::string generateTypeHeader(Root& root);
std::string generateGDHeader(Root& root);
std::string generateGDSource(Root& root);
std::string generateBindingHeader(Root& root, ghc::filesystem::path const& singleFolder);
std::string generatePredeclareHeader(Root& root);
std::string generateBindingSource(Root& root);
std::string generateTidyHeader(Root& root);
inline void writeFile(ghc::filesystem::path const& writePath, std::string const& output) {
std::ifstream readfile;
readfile >> std::noskipws;
readfile.open(writePath);
std::string data((std::istreambuf_iterator<char>(readfile)), std::istreambuf_iterator<char>());
readfile.close();
if (data != output) {
std::ofstream writefile;
writefile.open(writePath);
writefile << output;
writefile.close();
}
}
inline std::string str_if(std::string&& str, bool cond) {
return cond ? str : "";
}

View file

@ -72,7 +72,7 @@ types::ret{index} {class_name}::{function_name}({parameters}){const} {{
)GEN";
}}
std::string generateGDSource(Root& root) {
std::string generateBindingSource(Root& root) {
std::string output(format_strings::source_start);
for (auto& c : root.classes) {

View file

@ -1,6 +1,6 @@
// included by default in every geode project
#include <Geode/Geode.hpp>
#include <Geode/Loader.hpp>
GEODE_API bool GEODE_CALL geode_implicit_load(geode::Mod* m) {
geode::Mod::setSharedMod(m);

View file

@ -82,14 +82,13 @@ if (GEODE_NO_UNDEFINED_VIRTUALS)
target_compile_definitions(${PROJECT_NAME} PUBLIC GEODE_NO_UNDEFINED_VIRTUALS)
endif()
if (NOT GEODE_DISABLE_CLI_CALLS)
# Package resources for UI
package_geode_resources(
${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/resources
${GEODE_BIN_PATH}/nightly/resources
)
endif()
# Package resources for UI
package_geode_resources_now(
${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/resources
${GEODE_BIN_PATH}/nightly/resources
${CMAKE_CURRENT_SOURCE_DIR}/src/internal/resources.hpp
)
target_include_directories(${PROJECT_NAME} PRIVATE
src/internal/
@ -118,12 +117,13 @@ target_link_libraries(${PROJECT_NAME} z lilac_hook geode-sdk)
set_source_files_properties(${OBJC_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
target_precompile_headers(${PROJECT_NAME} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/DefaultInclude.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/Utils.hpp"
# "${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/Utils.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/Loader.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/UI.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/Bindings.hpp"
# "${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/Bindings.hpp"
# "${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/Modify.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/cocos/cocos2dx/include/cocos2d.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/Geode/cocos/extensions/cocos-ext.h"
)
# Create launcher

View file

@ -6,4 +6,6 @@ project(GeodeChecksum VERSION 1.0)
add_executable(${PROJECT_NAME} hash.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC filesystem)
message(STATUS "Building Checksum Exe")

View file

@ -1,11 +1,11 @@
#include <iostream>
#include "hash.hpp"
int main(int ac, char* av[]) {
if (ac < 2) {
int main(int argc, char** argv) {
if (argc < 2 || !ghc::filesystem::exists(argv[1])) {
std::cout << "Usage: \"checksum <file>\"\n";
return 1;
}
std::cout << calculateHash(av[1]) << std::hex;
std::cout << calculateHash(argv[1]) << std::endl;
return 0;
}

View file

@ -4,11 +4,24 @@
#include <fstream>
#include <ciso646>
#include "picosha3.h"
#include "picosha2.h"
#include <vector>
#include <fs/filesystem.hpp>
static std::string calculateHash(std::string const& path) {
static std::string calculateSHA3_256(ghc::filesystem::path const& path) {
std::vector<uint8_t> s(picosha3::bits_to_bytes(256));
auto sha3_256 = picosha3::get_sha3_generator<256>();
std::ifstream file(path);
std::ifstream file(path, std::ios::binary);
return sha3_256.get_hex_string(file);
}
static std::string calculateSHA256(ghc::filesystem::path const& path) {
std::vector<uint8_t> hash(picosha2::k_digest_size);
std::ifstream file(path, std::ios::binary);
picosha2::hash256(file, hash.begin(), hash.end());
return picosha2::bytes_to_hex_string(hash.begin(), hash.end());
}
static std::string calculateHash(ghc::filesystem::path const& path) {
return calculateSHA3_256(path);
}

377
loader/hash/picosha2.h Normal file
View file

@ -0,0 +1,377 @@
/*
The MIT License (MIT)
Copyright (C) 2017 okdshin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef PICOSHA2_H
#define PICOSHA2_H
// picosha2:20140213
#ifndef PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR
#define PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR \
1048576 //=1024*1024: default is 1MB memory
#endif
#include <algorithm>
#include <cassert>
#include <iterator>
#include <sstream>
#include <vector>
#include <fstream>
namespace picosha2 {
typedef unsigned long word_t;
typedef unsigned char byte_t;
static const size_t k_digest_size = 32;
namespace detail {
inline byte_t mask_8bit(byte_t x) { return x & 0xff; }
inline word_t mask_32bit(word_t x) { return x & 0xffffffff; }
const word_t add_constant[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
const word_t initial_message_digest[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372,
0xa54ff53a, 0x510e527f, 0x9b05688c,
0x1f83d9ab, 0x5be0cd19};
inline word_t ch(word_t x, word_t y, word_t z) { return (x & y) ^ ((~x) & z); }
inline word_t maj(word_t x, word_t y, word_t z) {
return (x & y) ^ (x & z) ^ (y & z);
}
inline word_t rotr(word_t x, std::size_t n) {
assert(n < 32);
return mask_32bit((x >> n) | (x << (32 - n)));
}
inline word_t bsig0(word_t x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); }
inline word_t bsig1(word_t x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); }
inline word_t shr(word_t x, std::size_t n) {
assert(n < 32);
return x >> n;
}
inline word_t ssig0(word_t x) { return rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3); }
inline word_t ssig1(word_t x) { return rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10); }
template <typename RaIter1, typename RaIter2>
void hash256_block(RaIter1 message_digest, RaIter2 first, RaIter2 last) {
assert(first + 64 == last);
static_cast<void>(last); // for avoiding unused-variable warning
word_t w[64];
std::fill(w, w + 64, word_t(0));
for (std::size_t i = 0; i < 16; ++i) {
w[i] = (static_cast<word_t>(mask_8bit(*(first + i * 4))) << 24) |
(static_cast<word_t>(mask_8bit(*(first + i * 4 + 1))) << 16) |
(static_cast<word_t>(mask_8bit(*(first + i * 4 + 2))) << 8) |
(static_cast<word_t>(mask_8bit(*(first + i * 4 + 3))));
}
for (std::size_t i = 16; i < 64; ++i) {
w[i] = mask_32bit(ssig1(w[i - 2]) + w[i - 7] + ssig0(w[i - 15]) +
w[i - 16]);
}
word_t a = *message_digest;
word_t b = *(message_digest + 1);
word_t c = *(message_digest + 2);
word_t d = *(message_digest + 3);
word_t e = *(message_digest + 4);
word_t f = *(message_digest + 5);
word_t g = *(message_digest + 6);
word_t h = *(message_digest + 7);
for (std::size_t i = 0; i < 64; ++i) {
word_t temp1 = h + bsig1(e) + ch(e, f, g) + add_constant[i] + w[i];
word_t temp2 = bsig0(a) + maj(a, b, c);
h = g;
g = f;
f = e;
e = mask_32bit(d + temp1);
d = c;
c = b;
b = a;
a = mask_32bit(temp1 + temp2);
}
*message_digest += a;
*(message_digest + 1) += b;
*(message_digest + 2) += c;
*(message_digest + 3) += d;
*(message_digest + 4) += e;
*(message_digest + 5) += f;
*(message_digest + 6) += g;
*(message_digest + 7) += h;
for (std::size_t i = 0; i < 8; ++i) {
*(message_digest + i) = mask_32bit(*(message_digest + i));
}
}
} // namespace detail
template <typename InIter>
void output_hex(InIter first, InIter last, std::ostream& os) {
os.setf(std::ios::hex, std::ios::basefield);
while (first != last) {
os.width(2);
os.fill('0');
os << static_cast<unsigned int>(*first);
++first;
}
os.setf(std::ios::dec, std::ios::basefield);
}
template <typename InIter>
void bytes_to_hex_string(InIter first, InIter last, std::string& hex_str) {
std::ostringstream oss;
output_hex(first, last, oss);
hex_str.assign(oss.str());
}
template <typename InContainer>
void bytes_to_hex_string(const InContainer& bytes, std::string& hex_str) {
bytes_to_hex_string(bytes.begin(), bytes.end(), hex_str);
}
template <typename InIter>
std::string bytes_to_hex_string(InIter first, InIter last) {
std::string hex_str;
bytes_to_hex_string(first, last, hex_str);
return hex_str;
}
template <typename InContainer>
std::string bytes_to_hex_string(const InContainer& bytes) {
std::string hex_str;
bytes_to_hex_string(bytes, hex_str);
return hex_str;
}
class hash256_one_by_one {
public:
hash256_one_by_one() { init(); }
void init() {
buffer_.clear();
std::fill(data_length_digits_, data_length_digits_ + 4, word_t(0));
std::copy(detail::initial_message_digest,
detail::initial_message_digest + 8, h_);
}
template <typename RaIter>
void process(RaIter first, RaIter last) {
add_to_data_length(static_cast<word_t>(std::distance(first, last)));
std::copy(first, last, std::back_inserter(buffer_));
std::size_t i = 0;
for (; i + 64 <= buffer_.size(); i += 64) {
detail::hash256_block(h_, buffer_.begin() + i,
buffer_.begin() + i + 64);
}
buffer_.erase(buffer_.begin(), buffer_.begin() + i);
}
void finish() {
byte_t temp[64];
std::fill(temp, temp + 64, byte_t(0));
std::size_t remains = buffer_.size();
std::copy(buffer_.begin(), buffer_.end(), temp);
temp[remains] = 0x80;
if (remains > 55) {
std::fill(temp + remains + 1, temp + 64, byte_t(0));
detail::hash256_block(h_, temp, temp + 64);
std::fill(temp, temp + 64 - 4, byte_t(0));
} else {
std::fill(temp + remains + 1, temp + 64 - 4, byte_t(0));
}
write_data_bit_length(&(temp[56]));
detail::hash256_block(h_, temp, temp + 64);
}
template <typename OutIter>
void get_hash_bytes(OutIter first, OutIter last) const {
for (const word_t* iter = h_; iter != h_ + 8; ++iter) {
for (std::size_t i = 0; i < 4 && first != last; ++i) {
*(first++) = detail::mask_8bit(
static_cast<byte_t>((*iter >> (24 - 8 * i))));
}
}
}
private:
void add_to_data_length(word_t n) {
word_t carry = 0;
data_length_digits_[0] += n;
for (std::size_t i = 0; i < 4; ++i) {
data_length_digits_[i] += carry;
if (data_length_digits_[i] >= 65536u) {
carry = data_length_digits_[i] >> 16;
data_length_digits_[i] &= 65535u;
} else {
break;
}
}
}
void write_data_bit_length(byte_t* begin) {
word_t data_bit_length_digits[4];
std::copy(data_length_digits_, data_length_digits_ + 4,
data_bit_length_digits);
// convert byte length to bit length (multiply 8 or shift 3 times left)
word_t carry = 0;
for (std::size_t i = 0; i < 4; ++i) {
word_t before_val = data_bit_length_digits[i];
data_bit_length_digits[i] <<= 3;
data_bit_length_digits[i] |= carry;
data_bit_length_digits[i] &= 65535u;
carry = (before_val >> (16 - 3)) & 65535u;
}
// write data_bit_length
for (int i = 3; i >= 0; --i) {
(*begin++) = static_cast<byte_t>(data_bit_length_digits[i] >> 8);
(*begin++) = static_cast<byte_t>(data_bit_length_digits[i]);
}
}
std::vector<byte_t> buffer_;
word_t data_length_digits_[4]; // as 64bit integer (16bit x 4 integer)
word_t h_[8];
};
inline void get_hash_hex_string(const hash256_one_by_one& hasher,
std::string& hex_str) {
byte_t hash[k_digest_size];
hasher.get_hash_bytes(hash, hash + k_digest_size);
return bytes_to_hex_string(hash, hash + k_digest_size, hex_str);
}
inline std::string get_hash_hex_string(const hash256_one_by_one& hasher) {
std::string hex_str;
get_hash_hex_string(hasher, hex_str);
return hex_str;
}
namespace impl {
template <typename RaIter, typename OutIter>
void hash256_impl(RaIter first, RaIter last, OutIter first2, OutIter last2, int,
std::random_access_iterator_tag) {
hash256_one_by_one hasher;
// hasher.init();
hasher.process(first, last);
hasher.finish();
hasher.get_hash_bytes(first2, last2);
}
template <typename InputIter, typename OutIter>
void hash256_impl(InputIter first, InputIter last, OutIter first2,
OutIter last2, int buffer_size, std::input_iterator_tag) {
std::vector<byte_t> buffer(buffer_size);
hash256_one_by_one hasher;
// hasher.init();
while (first != last) {
int size = buffer_size;
for (int i = 0; i != buffer_size; ++i, ++first) {
if (first == last) {
size = i;
break;
}
buffer[i] = *first;
}
hasher.process(buffer.begin(), buffer.begin() + size);
}
hasher.finish();
hasher.get_hash_bytes(first2, last2);
}
}
template <typename InIter, typename OutIter>
void hash256(InIter first, InIter last, OutIter first2, OutIter last2,
int buffer_size = PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR) {
picosha2::impl::hash256_impl(
first, last, first2, last2, buffer_size,
typename std::iterator_traits<InIter>::iterator_category());
}
template <typename InIter, typename OutContainer>
void hash256(InIter first, InIter last, OutContainer& dst) {
hash256(first, last, dst.begin(), dst.end());
}
template <typename InContainer, typename OutIter>
void hash256(const InContainer& src, OutIter first, OutIter last) {
hash256(src.begin(), src.end(), first, last);
}
template <typename InContainer, typename OutContainer>
void hash256(const InContainer& src, OutContainer& dst) {
hash256(src.begin(), src.end(), dst.begin(), dst.end());
}
template <typename InIter>
void hash256_hex_string(InIter first, InIter last, std::string& hex_str) {
byte_t hashed[k_digest_size];
hash256(first, last, hashed, hashed + k_digest_size);
std::ostringstream oss;
output_hex(hashed, hashed + k_digest_size, oss);
hex_str.assign(oss.str());
}
template <typename InIter>
std::string hash256_hex_string(InIter first, InIter last) {
std::string hex_str;
hash256_hex_string(first, last, hex_str);
return hex_str;
}
inline void hash256_hex_string(const std::string& src, std::string& hex_str) {
hash256_hex_string(src.begin(), src.end(), hex_str);
}
template <typename InContainer>
void hash256_hex_string(const InContainer& src, std::string& hex_str) {
hash256_hex_string(src.begin(), src.end(), hex_str);
}
template <typename InContainer>
std::string hash256_hex_string(const InContainer& src) {
return hash256_hex_string(src.begin(), src.end());
}
template<typename OutIter>void hash256(std::ifstream& f, OutIter first, OutIter last){
hash256(std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>(), first,last);
}
}// namespace picosha2
#endif // PICOSHA2_H

View file

@ -15,341 +15,11 @@
#include <type_traits>
#include <unordered_map>
#include <Geode/utils/addresser.hpp>
#include "Enums.hpp"
namespace geode::core::meta {}
template<auto F>
struct address_of_t {
static inline auto value = geode::base::get();
};
template<auto F>
inline auto address_of = address_of_t<F>::value;
//thanks pie
enum class SearchType {
Search = 0,
Downloaded = 1,
MostLiked = 2,
Trending = 3,
Recent = 4,
UsersLevels = 5,
Featured = 6,
Magic = 7,
Sends = 8,
MapPack = 9,
MapPackOnClick = 10,
Awarded = 11,
Followed = 12,
Friends = 13,
Users = 14,
LikedGDW = 15,
HallOfFame = 16,
FeaturedGDW = 17,
Similar = 18,
MyLevels = 98,
SavedLevels = 99,
FavouriteLevels = 100
};
// jesus fucking christ (painfully written by @hjfod)
enum class GameObjectType {
Solid = 0,
Hazard = 2,
InverseGravityPortal = 3,
NormalGravityPortal = 4,
ShipPortal = 5,
CubePortal = 6,
Decoration = 7,
YellowJumpPad = 8,
PinkJumpPad = 9,
GravityPad = 10,
YellowJumpRing = 11,
PinkJumpRing = 12,
GravityRing = 13,
InverseMirrorPortal = 14,
NormalMirrorPortal = 15,
BallPortal = 16,
RegularSizePortal = 17,
MiniSizePortal = 18,
UfoPortal = 19,
Modifier = 20,
SecretCoin = 22,
DualPortal = 23,
SoloPortal = 24,
Slope = 25,
WavePortal = 26,
RobotPortal = 27,
TeleportPortal = 28,
GreenRing = 29,
Collectible = 30,
UserCoin = 31,
DropRing = 32,
SpiderPortal = 33,
RedJumpPad = 34,
RedJumpRing = 35,
CustomRing = 36,
DashRing = 37,
GravityDashRing = 38,
CollisionObject = 39,
Special = 40,
};
enum class PulseEffectType {};
enum class TouchTriggerType {};
enum class PlayerButton {};
enum class GhostType {};
enum class TableViewCellEditingStyle {};
enum class UserListType {};
enum class GJErrorCode {};
enum class AccountError {};
enum class GJSongError {};
enum class LikeItemType {
Unknown = 0,
Level = 1,
Comment = 2,
AccountComment = 3
};
enum class GJStoreItem {};
enum class CommentError {};
enum class BackupAccountError {};
enum class BoomListType {
Default = 0x0,
User = 0x2,
Stats = 0x3,
Achievement = 0x4,
Level = 0x5,
Level2 = 0x6,
Comment = 0x7,
Comment2 = 0x8,
Song = 0xb,
Score = 0xc,
MapPack = 0xd,
CustomSong = 0xe,
Comment3 = 0xf,
User2 = 0x10,
Request = 0x11,
Message = 0x12,
LevelScore = 0x13,
Artist = 0x14,
};
enum class MenuAnimationType {
Scale = 0,
Move = 1,
};
enum class ShopType {
Normal,
Secret,
Community
};
// Geode Addition
enum class ZLayer {
B4 = -3,
B3 = -1,
B2 = 1,
B1 = 3,
Default = 0,
T1 = 5,
T2 = 7,
T3 = 9,
};
enum class UpdateResponse {
Unknown,
UpToDate,
GameVerOutOfDate,
UpdateSuccess,
};
enum class UnlockType {
Cube = 0x1,
Col1 = 0x2,
Col2 = 0x3,
Ship = 0x4,
Ball = 0x5,
Bird = 0x6,
Dart = 0x7,
Robot = 0x8,
Spider = 0x9,
Streak = 0xA,
Death = 0xB,
GJItem = 0xC,
};
enum class SpecialRewardItem {
FireShard = 0x1,
IceShard = 0x2,
PoisonShard = 0x3,
ShadowShard = 0x4,
LavaShard = 0x5,
BonusKey = 0x6,
Orbs = 0x7,
Diamonds = 0x8,
CustomItem = 0x9,
};
enum class EditCommand {
SmallLeft = 1,
SmallRight = 2,
SmallUp = 3,
SmallDown = 4,
Left = 5,
Right = 6,
Up = 7,
Down = 8,
BigLeft = 9,
BigRight = 10,
BigUp = 11,
BigDown = 12,
TinyLeft = 13,
TinyRight = 14,
TinyUp = 15,
TinyDown = 16,
FlipX = 17,
FlipY = 18,
RotateCW = 19,
RotateCCW = 20,
RotateCW45 = 21,
RotateCCW45 = 22,
RotateFree = 23,
RotateSnap = 24,
Scale = 25,
};
// Geode Addition
enum class PlaybackMode {
Not = 0,
Playing = 1,
Paused = 2,
};
enum class SelectArtType {
Background = 0,
Ground = 1,
};
enum class UndoCommand {
Delete = 1,
New = 2,
Paste = 3,
DeleteMulti = 4,
Transform = 5,
Select = 6,
};
enum class EasingType {
None = 0,
EaseInOut = 1,
EaseIn = 2,
EaseOut = 3,
ElasticInOut = 4,
ElasticIn = 5,
ElasticOut = 6,
BounceInOut = 7,
BounceIn = 8,
BounceOut = 9,
ExponentialInOut = 10,
ExponentialIn = 11,
ExponentialOut = 12,
SineInOut = 13,
SineIn = 14,
SineOut = 15,
BackInOut = 16,
BackIn = 17,
BackOut = 18,
};
enum class GJDifficulty {
Auto = 0,
Easy = 1,
Normal = 2,
Hard = 3,
Harder = 4,
Insane = 5,
Demon = 6,
DemonEasy = 7,
DemonMedium = 8,
DemonInsane = 9,
DemonExtreme = 10
};
enum class GJLevelType {
Local = 1,
Editor = 2,
Saved = 3
};
enum class IconType {
Cube = 0,
Ship = 1,
Ball = 2,
Ufo = 3,
Wave = 4,
Robot = 5,
Spider = 6,
DeathEffect = 98,
Special = 99,
};
enum class GJChallengeType {
Unknown = 0,
Orbs = 1,
UserCoins = 2,
Stars = 3
};
enum class GJScoreType {
Unknown = 0,
Creator = 1
};
enum class LevelLeaderboardType {
Friends = 0,
Global = 1,
Weekly = 2
};
// Geode Addition
enum class ComparisonType {
Equals = 0,
Larger = 1,
Smaller = 2,
};
// Geode Addition
enum class MoveTargetType {
Both = 0,
XOnly = 1,
YOnly = 2,
};
// Geode Addition
enum class TouchToggleMode {
Normal = 0,
ToggleOn = 1,
ToggleOff = 2,
};
// Geode Addition
enum class LeaderboardState {
Default = 0,
Top100 = 1,
Global = 2,
Creator = 3,
Friends = 4,
};
#define CLASSPARAM(...)
#define STRUCTPARAM(...)
#include <codegenned/GeneratedHeader.hpp>
#include <Geode/GeneratedBinding.hpp>

View file

@ -103,82 +103,82 @@ _61,_62,_63,N,...) N
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
#define GEODE_NEST1(macro, begin) \
macro(GEODE_CONCAT(begin, 0)), \
macro(GEODE_CONCAT(begin, 1)), \
macro(GEODE_CONCAT(begin, 2)), \
macro(GEODE_CONCAT(begin, 3)), \
macro(GEODE_CONCAT(begin, 4)), \
macro(GEODE_CONCAT(begin, 5)), \
macro(GEODE_CONCAT(begin, 6)), \
macro(GEODE_CONCAT(begin, 7)), \
macro(GEODE_CONCAT(begin, 8)), \
macro(GEODE_CONCAT(begin, 9)), \
macro(GEODE_CONCAT(begin, a)), \
macro(GEODE_CONCAT(begin, b)), \
macro(GEODE_CONCAT(begin, c)), \
macro(GEODE_CONCAT(begin, d)), \
macro(GEODE_CONCAT(begin, e)), \
macro(GEODE_CONCAT(begin, f))
// #define GEODE_NEST1(macro, begin) \
// macro(GEODE_CONCAT(begin, 0)), \
// macro(GEODE_CONCAT(begin, 1)), \
// macro(GEODE_CONCAT(begin, 2)), \
// macro(GEODE_CONCAT(begin, 3)), \
// macro(GEODE_CONCAT(begin, 4)), \
// macro(GEODE_CONCAT(begin, 5)), \
// macro(GEODE_CONCAT(begin, 6)), \
// macro(GEODE_CONCAT(begin, 7)), \
// macro(GEODE_CONCAT(begin, 8)), \
// macro(GEODE_CONCAT(begin, 9)), \
// macro(GEODE_CONCAT(begin, a)), \
// macro(GEODE_CONCAT(begin, b)), \
// macro(GEODE_CONCAT(begin, c)), \
// macro(GEODE_CONCAT(begin, d)), \
// macro(GEODE_CONCAT(begin, e)), \
// macro(GEODE_CONCAT(begin, f))
#define GEODE_NEST2(macro, begin) \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 0)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 1)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 2)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 3)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 4)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 5)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 6)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 7)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 8)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, 9)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, a)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, b)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, c)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, d)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, e)), \
GEODE_NEST1(macro, GEODE_CONCAT(begin, f))
// #define GEODE_NEST2(macro, begin) \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 0)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 1)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 2)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 3)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 4)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 5)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 6)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 7)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 8)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, 9)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, a)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, b)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, c)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, d)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, e)), \
// GEODE_NEST1(macro, GEODE_CONCAT(begin, f))
#define GEODE_NEST3(macro, begin) \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 0)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 1)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 2)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 3)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 4)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 5)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 6)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 7)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 8)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, 9)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, a)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, b)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, c)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, d)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, e)), \
GEODE_NEST2(macro, GEODE_CONCAT(begin, f))
// #define GEODE_NEST3(macro, begin) \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 0)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 1)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 2)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 3)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 4)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 5)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 6)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 7)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 8)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, 9)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, a)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, b)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, c)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, d)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, e)), \
// GEODE_NEST2(macro, GEODE_CONCAT(begin, f))
#define GEODE_NEST4(macro, begin) \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 0)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 1)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 2)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 3)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 4)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 5)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 6)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 7)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 8)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, 9)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, a)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, b)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, c)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, d)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, e)), \
GEODE_NEST3(macro, GEODE_CONCAT(begin, f))
// #define GEODE_NEST4(macro, begin) \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 0)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 1)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 2)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 3)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 4)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 5)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 6)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 7)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 8)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, 9)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, a)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, b)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, c)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, d)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, e)), \
// GEODE_NEST3(macro, GEODE_CONCAT(begin, f))
#define GEODE_ENUM_OFFSETS_DEFINE(hex) GEODE_CONCAT($, hex)
#define GEODE_ENUM_OFFSETS_SET() GEODE_NEST4(GEODE_ENUM_OFFSETS_DEFINE, 0x)
// #define GEODE_ENUM_OFFSETS_DEFINE(hex) GEODE_CONCAT($, hex)
// #define GEODE_ENUM_OFFSETS_SET() GEODE_NEST4(GEODE_ENUM_OFFSETS_DEFINE, 0x)
enum class PrinterOffsets {
GEODE_ENUM_OFFSETS_SET()
};
// enum class PrinterOffsets {
// GEODE_ENUM_OFFSETS_SET()
// };

View file

@ -0,0 +1,324 @@
#pragma once
//thanks pie
enum class SearchType {
Search = 0,
Downloaded = 1,
MostLiked = 2,
Trending = 3,
Recent = 4,
UsersLevels = 5,
Featured = 6,
Magic = 7,
Sends = 8,
MapPack = 9,
MapPackOnClick = 10,
Awarded = 11,
Followed = 12,
Friends = 13,
Users = 14,
LikedGDW = 15,
HallOfFame = 16,
FeaturedGDW = 17,
Similar = 18,
MyLevels = 98,
SavedLevels = 99,
FavouriteLevels = 100
};
// jesus fucking christ (painfully written by @hjfod)
enum class GameObjectType {
Solid = 0,
Hazard = 2,
InverseGravityPortal = 3,
NormalGravityPortal = 4,
ShipPortal = 5,
CubePortal = 6,
Decoration = 7,
YellowJumpPad = 8,
PinkJumpPad = 9,
GravityPad = 10,
YellowJumpRing = 11,
PinkJumpRing = 12,
GravityRing = 13,
InverseMirrorPortal = 14,
NormalMirrorPortal = 15,
BallPortal = 16,
RegularSizePortal = 17,
MiniSizePortal = 18,
UfoPortal = 19,
Modifier = 20,
SecretCoin = 22,
DualPortal = 23,
SoloPortal = 24,
Slope = 25,
WavePortal = 26,
RobotPortal = 27,
TeleportPortal = 28,
GreenRing = 29,
Collectible = 30,
UserCoin = 31,
DropRing = 32,
SpiderPortal = 33,
RedJumpPad = 34,
RedJumpRing = 35,
CustomRing = 36,
DashRing = 37,
GravityDashRing = 38,
CollisionObject = 39,
Special = 40,
};
enum class PulseEffectType {};
enum class TouchTriggerType {};
enum class PlayerButton {};
enum class GhostType {};
enum class TableViewCellEditingStyle {};
enum class UserListType {};
enum class GJErrorCode {};
enum class AccountError {};
enum class GJSongError {};
enum class LikeItemType {
Unknown = 0,
Level = 1,
Comment = 2,
AccountComment = 3
};
enum class GJStoreItem {};
enum class CommentError {};
enum class BackupAccountError {};
enum class BoomListType {
Default = 0x0,
User = 0x2,
Stats = 0x3,
Achievement = 0x4,
Level = 0x5,
Level2 = 0x6,
Comment = 0x7,
Comment2 = 0x8,
Song = 0xb,
Score = 0xc,
MapPack = 0xd,
CustomSong = 0xe,
Comment3 = 0xf,
User2 = 0x10,
Request = 0x11,
Message = 0x12,
LevelScore = 0x13,
Artist = 0x14,
};
enum class MenuAnimationType {
Scale = 0,
Move = 1,
};
enum class ShopType {
Normal,
Secret,
Community
};
// Geode Addition
enum class ZLayer {
B4 = -3,
B3 = -1,
B2 = 1,
B1 = 3,
Default = 0,
T1 = 5,
T2 = 7,
T3 = 9,
};
enum class UpdateResponse {
Unknown,
UpToDate,
GameVerOutOfDate,
UpdateSuccess,
};
enum class UnlockType {
Cube = 0x1,
Col1 = 0x2,
Col2 = 0x3,
Ship = 0x4,
Ball = 0x5,
Bird = 0x6,
Dart = 0x7,
Robot = 0x8,
Spider = 0x9,
Streak = 0xA,
Death = 0xB,
GJItem = 0xC,
};
enum class SpecialRewardItem {
FireShard = 0x1,
IceShard = 0x2,
PoisonShard = 0x3,
ShadowShard = 0x4,
LavaShard = 0x5,
BonusKey = 0x6,
Orbs = 0x7,
Diamonds = 0x8,
CustomItem = 0x9,
};
enum class EditCommand {
SmallLeft = 1,
SmallRight = 2,
SmallUp = 3,
SmallDown = 4,
Left = 5,
Right = 6,
Up = 7,
Down = 8,
BigLeft = 9,
BigRight = 10,
BigUp = 11,
BigDown = 12,
TinyLeft = 13,
TinyRight = 14,
TinyUp = 15,
TinyDown = 16,
FlipX = 17,
FlipY = 18,
RotateCW = 19,
RotateCCW = 20,
RotateCW45 = 21,
RotateCCW45 = 22,
RotateFree = 23,
RotateSnap = 24,
Scale = 25,
};
// Geode Addition
enum class PlaybackMode {
Not = 0,
Playing = 1,
Paused = 2,
};
enum class SelectArtType {
Background = 0,
Ground = 1,
};
enum class UndoCommand {
Delete = 1,
New = 2,
Paste = 3,
DeleteMulti = 4,
Transform = 5,
Select = 6,
};
enum class EasingType {
None = 0,
EaseInOut = 1,
EaseIn = 2,
EaseOut = 3,
ElasticInOut = 4,
ElasticIn = 5,
ElasticOut = 6,
BounceInOut = 7,
BounceIn = 8,
BounceOut = 9,
ExponentialInOut = 10,
ExponentialIn = 11,
ExponentialOut = 12,
SineInOut = 13,
SineIn = 14,
SineOut = 15,
BackInOut = 16,
BackIn = 17,
BackOut = 18,
};
enum class GJDifficulty {
Auto = 0,
Easy = 1,
Normal = 2,
Hard = 3,
Harder = 4,
Insane = 5,
Demon = 6,
DemonEasy = 7,
DemonMedium = 8,
DemonInsane = 9,
DemonExtreme = 10
};
enum class GJLevelType {
Local = 1,
Editor = 2,
Saved = 3
};
enum class IconType {
Cube = 0,
Ship = 1,
Ball = 2,
Ufo = 3,
Wave = 4,
Robot = 5,
Spider = 6,
DeathEffect = 98,
Special = 99,
};
enum class GJChallengeType {
Unknown = 0,
Orbs = 1,
UserCoins = 2,
Stars = 3
};
enum class GJScoreType {
Unknown = 0,
Creator = 1
};
enum class LevelLeaderboardType {
Friends = 0,
Global = 1,
Weekly = 2
};
// Geode Addition
enum class ComparisonType {
Equals = 0,
Larger = 1,
Smaller = 2,
};
// Geode Addition
enum class MoveTargetType {
Both = 0,
XOnly = 1,
YOnly = 2,
};
// Geode Addition
enum class TouchToggleMode {
Normal = 0,
ToggleOn = 1,
ToggleOff = 2,
};
// Geode Addition
enum class LeaderboardState {
Default = 0,
Top100 = 1,
Global = 2,
Creator = 3,
Friends = 4,
};

View file

@ -2,8 +2,7 @@
#include <Geode/DefaultInclude.hpp>
#include "modify/Traits.hpp"
#include "modify/Modify.hpp"
#include <Geode/GeneratedModify.hpp>
#include "modify/Field.hpp"
#include "modify/InternalMacros.hpp"

View file

@ -529,7 +529,7 @@ protected:
CCDirectorDelegate *m_pProjectionDelegate;
RT_ADD(
CC_PROPERTY(CCSceneDelegate*, m_pAppDelegate, SceneDelegate);
CC_SYNTHESIZE(CCSceneDelegate*, m_pAppDelegate, SceneDelegate);
CCSize m_obScaleFactor;
CCSize m_obResolutionInPixels;
TextureQuality m_eTextureQuality;

View file

@ -107,7 +107,6 @@ It's new in cocos2d-x since v0.99.5
class GeodeNodeMetadata;
namespace geode {
struct modify;
struct temp_name_find_better;
namespace modifier {
struct addresses;
struct types;
@ -115,10 +114,9 @@ namespace geode {
}
}
#define GEODE_FRIEND_MODIFY GEODE_ADD(\
friend struct geode::modify;\
friend struct geode::modifier::addresses;\
friend struct geode::modifier::types;\
friend struct geode::temp_name_find_better;\
friend struct ::geode::modify;\
friend struct ::geode::modifier::addresses;\
friend struct ::geode::modifier::types;\
friend class ::GeodeNodeMetadata;\
)
#define GEODE_ADD(...) __VA_ARGS__

View file

@ -49,6 +49,7 @@ public:
virtual void setupGLView();
virtual void platformShutdown();
void toggleVerticalSync(bool);
bool getVerticalSyncEnabled() const;
)
/**

View file

@ -1,6 +1,6 @@
#pragma once
#include <Event.hpp>
#include "Event.hpp"
#include <string>
#include <tuple>
#include <functional>

View file

@ -5,7 +5,7 @@
#include <Geode/utils/types.hpp>
#include <string_view>
#include "../hook-core/Hook.hpp"
#include <Geode/hook-core/Hook.hpp>
namespace geode {
class Mod;

View file

@ -10,9 +10,7 @@
#include <functional>
#include <unordered_set>
#include <fs/filesystem.hpp>
#include <Geode/utils/json.hpp>
#include "Log.hpp"
#include <Geode/utils/VersionInfo.hpp>
namespace geode {
#pragma warning(disable: 4251)
@ -27,6 +25,7 @@ namespace geode {
class Mod;
class Hook;
struct ModInfo;
class VersionInfo;
namespace modifier {
template<class, class, class>
@ -183,6 +182,7 @@ namespace geode {
* Mod::m_addResourcesToSearchPath to true
* first
*/
void updateModResourcePaths(Mod*);
void updateResourcePaths();
void updateModResources(Mod* mod);
void updateResources();

View file

@ -3,15 +3,14 @@
#include <Geode/DefaultInclude.hpp>
#include "Types.hpp"
#include "Hook.hpp"
#include "../utils/types.hpp"
#include "../utils/Result.hpp"
#include "../utils/VersionInfo.hpp"
#include <Geode/utils/types.hpp>
#include <Geode/utils/Result.hpp>
#include <Geode/utils/VersionInfo.hpp>
#include <Geode/utils/json.hpp>
#include <string_view>
#include <vector>
#include <unordered_map>
#include <type_traits>
#include <cocos2d.h>
#include "Setting.hpp"
#include <optional>
@ -176,6 +175,14 @@ namespace geode {
* format
*/
static Result<ModInfo> createFromSchemaV010(ModJson const& json);
Result<> addSpecialFiles(ghc::filesystem::path const& dir);
Result<> addSpecialFiles(cocos2d::ZipFile& zip);
std::vector<std::pair<
std::string,
std::optional<std::string>*
>> getSpecialFiles();
};
/**

View file

@ -3,12 +3,12 @@
#include <Geode/DefaultInclude.hpp>
#include <optional>
#include <unordered_set>
#include "../utils/container.hpp"
#include "../utils/json.hpp"
#include "../utils/Result.hpp"
#include "../utils/JsonValidation.hpp"
#include "../utils/convert.hpp"
#include "../utils/platform.hpp"
#include <Geode/utils/container.hpp>
#include <Geode/utils/json.hpp>
#include <Geode/utils/Result.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <Geode/utils/convert.hpp>
#include <Geode/utils/platform.hpp>
#include <regex>
#pragma warning(push)

View file

@ -1,7 +1,7 @@
#pragma once
#include "Setting.hpp"
#include <Geode/Bindings.hpp>
#include <cocos2d.h>
namespace geode {
class SettingNode;

View file

@ -4,6 +4,6 @@
namespace geode::modifier {
struct addresses {
#include <codegenned/GeneratedAddress.hpp>
#include <Geode/GeneratedAddress.hpp>
};
}

View file

@ -26,6 +26,6 @@ namespace geode::modifier {
>> {
constexpr static inline bool value = true;
};
#include <codegenned/GeneratedCompare.hpp>
#include <Geode/GeneratedCompare.hpp>
};
}

View file

@ -1,10 +1,13 @@
#pragma once
#include <Geode/Bindings.hpp>
#include "Traits.hpp"
#include <Geode/loader/Loader.hpp>
#include <vector>
namespace cocos2d {
class CCNode;
}
namespace geode::modifier {
class FieldContainer {
private:

View file

@ -43,6 +43,4 @@ namespace geode::modifier {
static_assert(core::meta::always_false<Derived>, "Custom Modify not implemented.");
}
};
#include <codegenned/GeneratedModify.hpp>
}

View file

@ -28,7 +28,7 @@ namespace geode::modifier {
using type = FunctionType*;
};
using geode::core::meta::always_false;
using ::geode::core::meta::always_false;
/**
* The ~unevaluated~ function that gets the appropriate
* version of a function type from its return, parameters, and classes.

View file

@ -3,6 +3,6 @@
namespace geode::modifier {
struct types {
#include <codegenned/GeneratedType.hpp>
#include <Geode/GeneratedType.hpp>
};
}

View file

@ -68,7 +68,7 @@ namespace geode::modifier {
struct wrap {
GEODE_WRAPPER_FOR_IDENTIFIER(constructor)
GEODE_WRAPPER_FOR_IDENTIFIER(destructor)
#include <codegenned/GeneratedWrapper.hpp>
#include <Geode/GeneratedWrapper.hpp>
};
// template <template<class, class, class=void> class Identifier, class Base, class Derived, class ...Types>

View file

@ -71,7 +71,7 @@ namespace std {
#define GEODE_PLATFORM_TARGET PlatformID::Windows
#define GEODE_HIDDEN
#define GEODE_DUPABLE __forceinline
#define GEODE_INLINE __forceinline
#define GEODE_VIRTUAL_CONSTEXPR
#define GEODE_NOINLINE __declspec(noinline)
@ -90,7 +90,7 @@ namespace std {
#define GEODE_PLATFORM_TARGET PlatformID::MacOS
#define GEODE_HIDDEN __attribute__((visibility("hidden")))
#define GEODE_DUPABLE __attribute__((always_inline))
#define GEODE_INLINE inline __attribute__((always_inline))
#define GEODE_VIRTUAL_CONSTEXPR constexpr
#define GEODE_NOINLINE __attribute__((noinline))
@ -109,7 +109,7 @@ namespace std {
#define GEODE_PLATFORM_TARGET PlatformID::iOS
#define GEODE_HIDDEN __attribute__((visibility("hidden")))
#define GEODE_DUPABLE __attribute__((always_inline))
#define GEODE_INLINE inline __attribute__((always_inline))
#define GEODE_VIRTUAL_CONSTEXPR constexpr
#define GEODE_NOINLINE __attribute__((noinline))
@ -128,7 +128,7 @@ namespace std {
#define GEODE_PLATFORM_TARGET PlatformID::Android
#define GEODE_HIDDEN __attribute__((visibility("hidden")))
#define GEODE_DUPABLE __attribute__((always_inline))
#define GEODE_INLINE inline __attribute__((always_inline))
#define GEODE_VIRTUAL_CONSTEXPR constexpr
#define GEODE_NOINLINE __attribute__((noinline))

View file

@ -1,6 +1,7 @@
#pragma once
#include "BasedButtonSprite.hpp"
#include <Geode/binding/CCMenuItemToggler.hpp>
#pragma warning(disable : 4275)

View file

@ -1,6 +1,6 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <cocos2d.h>
namespace geode {
enum class CircleBaseSize {

View file

@ -3,6 +3,8 @@
#include "Popup.hpp"
#include "InputNode.hpp"
#include <Geode/binding/TextInputDelegate.hpp>
namespace geode {
class ColorPickPopupDelegate {
public:

View file

@ -1,6 +1,10 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <cocos2d.h>
namespace cocos2d::extension {
class CCScale9Sprite;
}
namespace geode {
class GEODE_DLL IconButtonSprite :

View file

@ -1,6 +1,6 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <cocos2d.h>
namespace geode {
class GEODE_DLL InputNode : public cocos2d::CCMenuItem {

View file

@ -1,6 +1,7 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <Geode/binding/TableViewCell.hpp>
#include <Geode/binding/CustomListView.hpp>
namespace geode {
class GEODE_DLL GenericListCell : public TableViewCell {

View file

@ -1,10 +1,11 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <Geode/binding/FLAlertLayerProtocol.hpp>
#include "TextRenderer.hpp"
#include "ScrollLayer.hpp"
struct MDParser;
class CCScrollLayerExt;
namespace geode {
/**

View file

@ -1,6 +1,6 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <cocos2d.h>
#include "SceneManager.hpp"
#include <chrono>
#include "../utils/Ref.hpp"

View file

@ -1,6 +1,7 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <Geode/binding/FLAlertLayer.hpp>
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
namespace geode {
template<typename... InitArgs>
@ -101,6 +102,16 @@ namespace geode {
std::function<void(FLAlertLayer*, bool)> selected,
bool doShow = true
);
GEODE_DLL FLAlertLayer* createQuickPopup(
const char* title,
std::string const& content,
const char* btn1,
const char* btn2,
float width,
std::function<void(FLAlertLayer*, bool)> selected,
bool doShow = true
);
}

View file

@ -1,6 +1,9 @@
#pragma once
#include <Geode/Bindings.hpp>
namespace cocos2d {
class CCArray;
class CCNode;
}
namespace geode {
class GEODE_DLL SceneManager {

View file

@ -1,6 +1,7 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <Geode/binding/CCContentLayer.hpp>
#include <Geode/binding/CCScrollLayerExt.hpp>
namespace geode {
/**

View file

@ -1,6 +1,6 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <cocos2d.h>
namespace geode {
class GEODE_DLL Scrollbar : public cocos2d::CCLayer {

View file

@ -1,6 +1,6 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
namespace geode {

View file

@ -1,6 +1,6 @@
#pragma once
#include <Geode/Bindings.hpp>
#include <cocos2d.h>
namespace geode {
enum class TextAlignment {

View file

@ -157,6 +157,11 @@ namespace geode {
return *this;
}
JsonMaybeValue<Json> array() {
this->as<value_t::array>();
return *this;
}
template<nlohmann::detail::value_t... T>
JsonMaybeValue<Json> asOneOf() {
if (this->isError()) return *this;
@ -273,6 +278,24 @@ namespace geode {
}
};
JsonMaybeValue<Json> at(size_t i) {
this->as<value_t::array>();
if (this->isError()) return *this;
if (self().m_json.size() <= i) {
this->setError(
self().m_hierarchy + ": has " +
std::to_string(self().m_json.size()) + "items "
", expected to have at least " + std::to_string(i + 1)
);
return *this;
}
return JsonMaybeValue<Json>(
self().m_checker, self().m_json.at(i),
self().m_hierarchy + "." + std::to_string(i),
self().m_hasValue
);
}
Iterator<JsonMaybeValue<Json>> iterate() {
this->as<value_t::array>();
Iterator<JsonMaybeValue<Json>> iter;

View file

@ -4,6 +4,7 @@
#include <cocos2d.h>
#include <functional>
#include <type_traits>
#include "Ref.hpp"
namespace geode::cocos {
/**
@ -247,14 +248,20 @@ namespace geode::cocos {
}
};
}
namespace std {
template <typename T>
struct std::iterator_traits<CCArrayIterator<T>> {
struct iterator_traits<geode::cocos::CCArrayIterator<T>> {
using difference_type = ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::random_access_iterator_tag; // its random access but im too lazy to implement it
};
}
namespace geode::cocos {
struct GEODE_DLL CCArrayInserter {
public:
@ -278,25 +285,16 @@ namespace geode::cocos {
template <typename _Type>
class CCArrayExt {
protected:
cocos2d::CCArray* m_arr;
Ref<cocos2d::CCArray> m_arr;
using T = std::remove_pointer_t<_Type>;
public:
CCArrayExt() : m_arr(cocos2d::CCArray::create()) {
m_arr->retain();
}
CCArrayExt(cocos2d::CCArray* arr) : m_arr(arr) {
m_arr->retain();
}
CCArrayExt(CCArrayExt const& a) : m_arr(a.m_arr) {
m_arr->retain();
}
CCArrayExt() : m_arr(cocos2d::CCArray::create()) {}
CCArrayExt(cocos2d::CCArray* arr) : m_arr(arr) {}
CCArrayExt(CCArrayExt const& a) : m_arr(a.m_arr) {}
CCArrayExt(CCArrayExt&& a) : m_arr(a.m_arr) {
a.m_arr = nullptr;
}
~CCArrayExt() {
if (m_arr)
m_arr->release();
}
~CCArrayExt() {}
auto begin() {
return CCArrayIterator<T*>(reinterpret_cast<T**>(m_arr->data->arr));

View file

@ -4,10 +4,18 @@
#include <fs/filesystem.hpp>
#include "Result.hpp"
#include "json.hpp"
#include <mutex>
namespace geode::utils::web {
using FileProgressCallback = std::function<bool(double, double)>;
/**
* Synchronously fetch data from the internet
* @param url URL to fetch
* @returns Returned data as bytes, or error on error
*/
GEODE_DLL Result<byte_array> fetchBytes(std::string const& url);
/**
* Synchronously fetch data from the internet
* @param url URL to fetch
@ -46,5 +54,283 @@ namespace geode::utils::web {
return Err(e.what());
}
}
class SentAsyncWebRequest;
template<class T>
class AsyncWebResult;
class AsyncWebResponse;
class AsyncWebRequest;
using AsyncProgress = std::function<void(SentAsyncWebRequest&, double, double)>;
using AsyncExpect = std::function<void(std::string const&)>;
using AsyncThen = std::function<void(SentAsyncWebRequest&, byte_array const&)>;
using AsyncCancelled = std::function<void(SentAsyncWebRequest&)>;
/**
* A handle to an in-progress sent asynchronous web request. Use this to
* cancel the request / query information about it
*/
class SentAsyncWebRequest {
private:
std::string m_id;
std::string m_url;
std::vector<AsyncThen> m_thens;
std::vector<AsyncExpect> m_expects;
std::vector<AsyncProgress> m_progresses;
std::vector<AsyncCancelled> m_cancelleds;
std::atomic<bool> m_paused = true;
std::atomic<bool> m_cancelled = false;
std::atomic<bool> m_finished = false;
std::atomic<bool> m_cleanedUp = false;
mutable std::mutex m_mutex;
std::variant<
std::monostate,
std::ostream*,
ghc::filesystem::path
> m_target = std::monostate();
template<class T>
friend class AsyncWebResult;
friend class AsyncWebRequest;
void pause();
void resume();
void error(std::string const& error);
void doCancel();
public:
/**
* Do not call this manually.
*/
SentAsyncWebRequest(AsyncWebRequest const&, std::string const& id);
/**
* Cancel the request. Cleans up any downloaded files, but if you run
* extra code in `then`, you will have to clean it up manually in
* `cancelled`
*/
void cancel();
/**
* Check if the request is finished
*/
bool finished() const;
};
using SentAsyncWebRequestHandle = std::shared_ptr<SentAsyncWebRequest>;
template<class T>
using DataConverter = Result<T>(*)(byte_array const&);
/**
* An asynchronous, thread-safe web request. Downloads data from the
* internet without slowing the main thread. All callbacks are run in the
* GD thread, so interacting with the Cocos2d UI is perfectly safe
*/
class GEODE_DLL AsyncWebRequest {
private:
std::optional<std::string> m_joinID;
std::string m_url;
AsyncThen m_then = nullptr;
AsyncExpect m_expect = nullptr;
AsyncProgress m_progress = nullptr;
AsyncCancelled m_cancelled = nullptr;
bool m_sent = false;
std::variant<
std::monostate,
std::ostream*,
ghc::filesystem::path
> m_target;
template<class T>
friend class AsyncWebResult;
friend class SentAsyncWebRequest;
friend class AsyncWebResponse;
public:
/**
* An asynchronous, thread-safe web request. Downloads data from the
* internet without slowing the main thread. All callbacks are run in the
* GD thread, so interacting with the Cocos2d UI is perfectly safe
*/
AsyncWebRequest() = default;
/**
* If you only want one instance of this web request to run (for example,
* you're downloading some global data for a manager), then use this
* to specify a Join ID. If another request with the same ID is
* already running, this request's callbacks will be appended to the
* existing one instead of creating a new request
* @param requestID The Join ID of the request. Can be anything,
* recommended to be something unique
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& join(std::string const& requestID);
/**
* URL to fetch from the internet asynchronously
* @param url URL of the data to download. Redirects will be
* automatically followed
* @returns Same AsyncWebRequest
*/
AsyncWebResponse fetch(std::string const& url);
/**
* Specify a callback to run if the download fails. Runs in the GD
* thread, so interacting with UI is safe
* @param handler Callback to run if the download fails
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& expect(AsyncExpect handler);
/**
* Specify a callback to run when the download progresses. Runs in the
* GD thread, so interacting with UI is safe
* @param handler Callback to run when the download progresses
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& progress(AsyncProgress handler);
/**
* Specify a callback to run if the download is cancelled. Runs in the
* GD thread, so interacting with UI is safe. Web requests may be
* cancelled after they are finished (for example, if downloading files
* in bulk and one fails). In that case, handle freeing up the results
* of `then` in this handler
* @param handler Callback to run if the download is cancelled
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& cancelled(AsyncCancelled handler);
/**
* Begin the web request. It's not always necessary to call this as the
* destructor calls it automatically, but if you need access to the
* handle of the sent request, use this
* @returns Handle to the sent web request
*/
SentAsyncWebRequestHandle send();
~AsyncWebRequest();
};
template<class T>
class AsyncWebResult {
private:
AsyncWebRequest& m_request;
DataConverter<T> m_converter;
AsyncWebResult(AsyncWebRequest& request, DataConverter<T> converter)
: m_request(request), m_converter(converter) {}
friend class AsyncWebResponse;
public:
/**
* Specify a callback to run after a download is finished. Runs in the
* GD thread, so interacting with UI is safe
* @param handle Callback to run
* @returns The original AsyncWebRequest, where you can specify more
* aspects about the request like failure and progress callbacks
*/
AsyncWebRequest& then(std::function<void(T)> handle);
/**
* Specify a callback to run after a download is finished. Runs in the
* GD thread, so interacting with UI is safe
* @param handle Callback to run
* @returns The original AsyncWebRequest, where you can specify more
* aspects about the request like failure and progress callbacks
*/
AsyncWebRequest& then(std::function<void(SentAsyncWebRequest&, T)> handle);
};
class GEODE_DLL AsyncWebResponse {
private:
AsyncWebRequest& m_request;
inline AsyncWebResponse(AsyncWebRequest& request) : m_request(request) {}
friend class AsyncWebRequest;
public:
/**
* Download into a stream. Make sure the stream lives for the entire
* duration of the request. If you want to download a file, use the
* `ghc::filesystem::path` overload of `into` instead
* @param stream Stream to download into. Make sure it lives long
* enough, otherwise the web request will crash
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished. The result has a `std::monostate`
* template parameter, as it can be assumed you know what you passed
* into `into`
*/
AsyncWebResult<std::monostate> into(std::ostream& stream);
/**
* Download into a file
* @param path File to download into. If it already exists, it will
* be overwritten.
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished. The result has a `std::monostate`
* template parameter, as it can be assumed you know what you passed
* into `into`
*/
AsyncWebResult<std::monostate> into(ghc::filesystem::path const& path);
/**
* Download into memory as a string
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished
*/
AsyncWebResult<std::string> text();
/**
* Download into memory as a byte array
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished
*/
AsyncWebResult<byte_array> bytes();
/**
* Download into memory as JSON
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished
*/
AsyncWebResult<nlohmann::json> json();
/**
* Download into memory as a custom type. The data will first be
* downloaded into memory as a byte array, and then converted using
* the specified converter function
* @param converter Function that converts the data from a byte array
* to the desired type
* @returns AsyncWebResult, where you can specify the `then` action for
* after the download is finished
*/
template<class T>
AsyncWebResult<T> as(DataConverter<T> converter) {
return AsyncWebResult(m_request, converter);
}
};
template<class T>
AsyncWebRequest& AsyncWebResult<T>::then(std::function<void(T)> handle) {
m_request.m_then = [
converter = m_converter,
handle
](SentAsyncWebRequest& req, byte_array const& arr) {
auto conv = converter(arr);
if (conv) {
handle(conv.value());
} else {
req.error("Unable to convert value: " + conv.error());
}
};
return m_request;
}
template<class T>
AsyncWebRequest& AsyncWebResult<T>::then(std::function<void(SentAsyncWebRequest&, T)> handle) {
m_request.m_then = [
converter = m_converter,
handle
](SentAsyncWebRequest& req, byte_array const& arr) {
auto conv = converter(arr);
if (conv) {
handle(req, conv.value());
} else {
req.error("Unable to convert value: " + conv.error());
}
};
return m_request;
}
}

View file

@ -7,22 +7,34 @@
#include <fs/filesystem.hpp>
namespace geode::utils::file {
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<std::string> readString(std::string const& path);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<std::string> readString(std::wstring const& path);
GEODE_DLL Result<std::string> readString(ghc::filesystem::path const& path);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<byte_array> readBinary(std::string const& path);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<byte_array> readBinary(std::wstring const& path);
GEODE_DLL Result<byte_array> readBinary(ghc::filesystem::path const& path);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<> writeString(std::string const& path, std::string const& data);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<> writeString(std::wstring const& path, std::string const& data);
GEODE_DLL Result<> writeString(ghc::filesystem::path const& path, std::string const& data);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<> writeBinary(std::string const& path, byte_array const& data);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<> writeBinary(std::wstring const& path, byte_array const& data);
GEODE_DLL Result<> writeBinary(ghc::filesystem::path const& path, byte_array const& data);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<> createDirectory(std::string const& path);
GEODE_DLL Result<> createDirectory(ghc::filesystem::path const& path);
[[deprecated("Use the ghc::filesystem::path version")]]
GEODE_DLL Result<> createDirectoryAll(std::string const& path);
GEODE_DLL Result<> createDirectoryAll(ghc::filesystem::path const& path);
GEODE_DLL Result<std::vector<std::string>> listFiles(std::string const& path);
GEODE_DLL Result<std::vector<std::string>> listFilesRecursively(std::string const& path);

View file

@ -1,6 +1,6 @@
#pragma once
#include <Geode/Geode.hpp>
#include <cocos2d.h>
namespace geode {
static cocos2d::CCPoint& operator*=(cocos2d::CCPoint & pos, float mul) {

View file

@ -176,7 +176,7 @@ namespace geode::utils::vector {
* @returns Reference to vector.
*/
template<class T>
std::vector<T>& erase(std::vector<T>& vec, T& element) {
std::vector<T>& erase(std::vector<T>& vec, T const& element) {
vec.erase(std::remove(vec.begin(), vec.end(), element), vec.end());
return vec;
}

View file

@ -11,8 +11,6 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
add_subdirectory("test")
endif()
message(STATUS ${GEODE_LOADER_PATH}/include)
target_include_directories(
lilac_hook INTERFACE
${lilac_SOURCE_DIR}/include/geode

View file

@ -1,7 +1,7 @@
#include <Geode/Bindings.hpp>
#include <cocos2d.h>
#include <Geode/utils/Ref.hpp>
#include <Geode/Modify.hpp>
#include <Geode/utils/WackyGeodeMacros.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/modify/Field.hpp>
USE_GEODE_NAMESPACE();
using namespace geode::modifier;
@ -61,6 +61,7 @@ public:
};
// proxy forwards
#include <Geode/modify/CCNode.hpp>
class $modify(ProxyCCNode, CCNode) {
virtual CCObject* getUserObject() {
return GeodeNodeMetadata::set(this)->m_userObject;
@ -84,7 +85,7 @@ void CCNode::setID(std::string const& id) {
}
CCNode* CCNode::getChildByID(std::string const& id) {
CCARRAY_FOREACH_B_TYPE(m_pChildren, child, CCNode) {
for (auto child : CCArrayExt<CCNode>(m_pChildren)) {
if (child->getID() == id) {
return child;
}
@ -96,7 +97,7 @@ CCNode* CCNode::getChildByIDRecursive(std::string const& id) {
if (auto child = this->getChildByID(id)) {
return child;
}
CCARRAY_FOREACH_B_TYPE(m_pChildren, child, CCNode) {
for (auto child : CCArrayExt<CCNode>(m_pChildren)) {
if ((child = child->getChildByIDRecursive(id))) {
return child;
}

View file

@ -1,9 +1,14 @@
#include <Geode/Modify.hpp>
#include <Geode/modify/LoadingLayer.hpp>
#include <array>
#include <InternalLoader.hpp>
USE_GEODE_NAMESPACE();
class $modify(CustomLoadingLayer, LoadingLayer) {
bool m_updatingResources;
CustomLoadingLayer() : m_updatingResources(false) {}
bool init(bool fromReload) {
if (!LoadingLayer::init(fromReload))
return false;
@ -21,9 +26,89 @@ class $modify(CustomLoadingLayer, LoadingLayer) {
);
label->setPosition(winSize.width / 2, 30.f);
label->setScale(.45f);
label->setTag(5);
label->setID("geode-loaded-info");
this->addChild(label);
// verify loader resources
if (!InternalLoader::get()->verifyLoaderResources(
std::bind(
&CustomLoadingLayer::updateResourcesProgress, this,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3
)
)) {
// auto bg = CCScale9Sprite::create(
// "square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
// );
// bg->setScale(.6f);
// bg->setColor({ 0, 0, 0 });
// bg->setOpacity(150);
// bg->setPosition(winSize / 2);
// this->addChild(bg);
// m_fields->m_updatingResourcesBG = bg;
// auto label = CCLabelBMFont::create("", "goldFont.fnt");
// label->setScale(1.1f);
// bg->addChild(label);
m_fields->m_updatingResources = true;
this->setUpdateText("Downloading Resources");
}
return true;
}
void setUpdateText(std::string const& text) {
m_textArea->setString(text.c_str());
// m_fields->m_updatingResources->setString(text.c_str());
// m_fields->m_updatingResourcesBG->setContentSize({
// m_fields->m_updatingResources->getScaledContentSize().width + 30.f,
// 50.f
// });
// m_fields->m_updatingResources->setPosition(
// m_fields->m_updatingResourcesBG->getContentSize() / 2
// );
}
void updateResourcesProgress(
UpdateStatus status,
std::string const& info,
uint8_t progress
) {
switch (status) {
case UpdateStatus::Progress: {
this->setUpdateText(
"Downloading Resources: " +
std::to_string(progress) + "%"
);
} break;
case UpdateStatus::Finished: {
this->setUpdateText("Resources Downloaded");
m_fields->m_updatingResources = false;
this->loadAssets();
} break;
case UpdateStatus::Failed: {
InternalLoader::platformMessageBox(
"Error updating resources",
"Unable to update Geode resources: " + info + ".\n"
"The game will be loaded as normal, but please be aware "
"that it may very likely crash."
);
this->setUpdateText("Resource Download Failed");
m_fields->m_updatingResources = false;
this->loadAssets();
} break;
}
}
void loadAssets() {
if (m_fields->m_updatingResources) {
return;
}
LoadingLayer::loadAssets();
}
};

View file

@ -1,5 +1,7 @@
#include <Geode/Bindings.hpp>
#include <Geode/utils/WackyGeodeMacros.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/utils/Ref.hpp>
#include <Geode/ui/BasedButtonSprite.hpp>
#include <Geode/ui/Notification.hpp>
#include <Index.hpp>
@ -32,42 +34,6 @@ static void addUpdateIcon(const char* icon = "updates-available.png"_spr) {
}
}
static void updateModsProgress(
UpdateStatus status,
std::string const& info,
uint8_t progress
) {
if (status == UpdateStatus::Failed) {
g_indexUpdateNotif->hide();
g_indexUpdateNotif = nullptr;
NotificationBuilder()
.title("Some Updates Failed")
.text("Some mods failed to update, click for details")
.icon("info-alert.png"_spr)
.clicked([info](auto) -> void {
FLAlertLayer::create("Info", info, "OK")->show();
})
.show();
addUpdateIcon("updates-failed.png"_spr);
}
if (status == UpdateStatus::Finished) {
g_indexUpdateNotif->hide();
g_indexUpdateNotif = nullptr;
NotificationBuilder()
.title("Updates Installed")
.text(
"Mods have been updated, please "
"restart to apply changes"
)
.icon("updates-available.png"_spr)
.clicked([info](auto) -> void {
FLAlertLayer::create("Info", info, "OK")->show();
})
.show();
}
}
static void updateIndexProgress(
UpdateStatus status,
std::string const& info,
@ -88,49 +54,20 @@ static void updateIndexProgress(
g_indexUpdateNotif->hide();
g_indexUpdateNotif = nullptr;
if (Index::get()->areUpdatesAvailable()) {
if (Mod::get()->getSettingValue<bool>("auto-update-mods")) {
auto ticket = Index::get()->installUpdates(updateModsProgress);
if (!ticket) {
NotificationBuilder()
.title("Unable to auto-update")
.text("Unable to update mods :(")
.icon("updates-failed.png"_spr)
.show();
} else {
g_indexUpdateNotif = NotificationBuilder()
.title("Installing updates")
.text("Installing updates...")
.clicked([ticket](auto) -> void {
createQuickPopup(
"Cancel Updates",
"Do you want to <cr>cancel</c> updates?",
"Don't Cancel", "Cancel Updates",
[ticket](auto, bool btn2) -> void {
if (g_indexUpdateNotif && btn2) {
ticket.value()->cancel();
}
}
);
}, false)
.loading()
.stay()
.show();
}
} else {
NotificationBuilder()
.title("Updates available")
.text("Some mods have updates available!")
.icon("updates-available.png"_spr)
.clicked([](auto) -> void {
ModListLayer::scene();
})
.show();
}
NotificationBuilder()
.title("Updates available")
.text("Some mods have updates available!")
.icon("updates-available.png"_spr)
.clicked([](auto) -> void {
ModListLayer::scene();
})
.show();
addUpdateIcon();
}
}
}
#include <Geode/modify/MenuLayer.hpp>
class $modify(CustomMenuLayer, MenuLayer) {
void destructor() {
g_geodeButton = nullptr;
@ -170,7 +107,19 @@ class $modify(CustomMenuLayer, MenuLayer) {
btn->setID("geode-button");
bottomMenu->addChild(btn);
<<<<<<< HEAD
bottomMenu->updateLayout();
=======
bottomMenu->alignItemsHorizontallyWithPadding(3.f);
for (auto node : CCArrayExt<CCNode>(bottomMenu->getChildren())) {
node->setPositionY(y);
}
if (chest) {
bottomMenu->addChild(chest);
chest->release();
}
>>>>>>> main
if (auto node = this->getChildByID("settings-gamepad-icon")) {
node->setPositionX(bottomMenu->getChildByID(

View file

@ -1,6 +1,6 @@
#include <Geode/Modify.hpp>
#ifdef GEODE_IS_WINDOWS
#include <Geode/loader/Mod.hpp>
#include <Geode/modify/InternalMacros.hpp>
USE_GEODE_NAMESPACE();
using geode::core::meta::x86::Thiscall;

View file

@ -1,7 +1,6 @@
#include <Geode/Modify.hpp>
USE_GEODE_NAMESPACE();
#include <Geode/modify/CCTouchDispatcher.hpp>
class $modify(CCTouchDispatcher) {
void addTargetedDelegate(CCTouchDelegate *delegate, int priority, bool swallowsTouches) {
m_bForcePrio = false;

View file

@ -1,4 +1,3 @@
// #include <Geode/Geode.hpp>
// // this is the fix for the dynamic_cast problems
// USE_GEODE_NAMESPACE();

View file

@ -1,13 +1,10 @@
#include <Geode/Modify.hpp>
// this is the fix for the dynamic_cast problems
// TODO: completely replace dynamic_cast on macos
using namespace cocos2d;
using namespace geode::modifier;
#if defined(GEODE_IS_IOS) || defined(GEODE_IS_MACOS)
namespace geode::fixes {
using namespace geode::cast;
#define HandlerFixFor(CCUtility) \
@ -49,10 +46,14 @@ class $modify(CCUtility##HandlerTypeinfoFix, CCUtility##Handler) {
} \
}
#include <Geode/modify/CCKeypadHandler.hpp>
HandlerFixFor(CCKeypad);
#include <Geode/modify/CCKeyboardHandler.hpp>
HandlerFixFor(CCKeyboard);
#include <Geode/modify/CCMouseHandler.hpp>
HandlerFixFor(CCMouse);
#include <Geode/modify/CCTargetedTouchHandler.hpp>
class $modify(CCTargetedTouchHandlerTypeinfoFix, CCTargetedTouchHandler) {
void destructor() {
if (m_pDelegate) {
@ -99,6 +100,7 @@ class $modify(CCTargetedTouchHandlerTypeinfoFix, CCTargetedTouchHandler) {
}
};
#include <Geode/modify/CCStandardTouchHandler.hpp>
class $modify(CCStandardTouchHandlerTypeinfoFix, CCStandardTouchHandler) {
void destructor() {
if (m_pDelegate) {
@ -140,6 +142,4 @@ class $modify(CCStandardTouchHandlerTypeinfoFix, CCStandardTouchHandler) {
}
};
} // geode::fixes
#endif

View file

@ -1,8 +1,8 @@
#include <Geode/Modify.hpp>
#include <Geode/ui/SceneManager.hpp>
USE_GEODE_NAMESPACE();
#include <Geode/modify/AchievementNotifier.hpp>
class $modify(AchievementNotifier) {
void willSwitchToScene(CCScene* scene) {
AchievementNotifier::willSwitchToScene(scene);

View file

@ -1,15 +1,15 @@
#include <Geode/Modify.hpp>
#include <Geode/loader/Loader.hpp>
USE_GEODE_NAMESPACE();
#include <Geode/modify/AppDelegate.hpp>
class $modify(AppDelegate) {
void trySaveGame() {
log::log(Severity::Info, Loader::getInternalMod(), "Saving...");
auto r = Loader::get()->saveSettings();
if (!r) {
log::log(Severity::Error, Loader::getInternalMod(), r.error());
log::log(Severity::Error, Loader::getInternalMod(), "{}", r.error());
}
log::log(Severity::Info, Loader::getInternalMod(), "Saved");

View file

@ -1,8 +1,8 @@
#include <Geode/Modify.hpp>
#include <InternalLoader.hpp>
USE_GEODE_NAMESPACE();
#include <Geode/modify/CCScheduler.hpp>
class $modify(CCScheduler) {
void update(float dt) {
InternalLoader::get()->executeGDThreadQueue();

View file

@ -1,8 +1,8 @@
#include <Geode/Modify.hpp>
#include <Geode/loader/Loader.hpp>
USE_GEODE_NAMESPACE();
#include <Geode/modify/GameManager.hpp>
class $modify(GameManager) {
void reloadAllStep2() {
GameManager::reloadAllStep2();
@ -10,6 +10,7 @@ class $modify(GameManager) {
}
};
#include <Geode/modify/LoadingLayer.hpp>
class $modify(LoadingLayer) {
void loadAssets() {
LoadingLayer::loadAssets();

View file

@ -3,6 +3,15 @@
#include <Geode/utils/json.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <Geode/utils/fetch.hpp>
#include <hash.hpp>
#include <Geode/utils/file.hpp>
#include <Geode/utils/string.hpp>
#include <Geode/utils/vector.hpp>
#include <Geode/utils/map.hpp>
#include <Geode/utils/general.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/binding/FLAlertLayer.hpp>
#define GITHUB_DONT_RATE_LIMIT_ME_PLS 0
@ -31,7 +40,6 @@ static PlatformID platformFromString(std::string const& str) {
}
}
Index* Index::get() {
static auto ret = new Index();
return ret;
@ -59,159 +67,17 @@ bool Index::isFeaturedItem(std::string const& item) const {
return m_featured.count(item);
}
void Index::updateIndexThread(bool force) {
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
// download index
#if GITHUB_DONT_RATE_LIMIT_ME_PLS == 0
indexUpdateProgress(
UpdateStatus::Progress, "Fetching index metadata", 0
);
// get all commits in index repo
auto commit = web::fetchJSON(
"https://api.github.com/repos/geode-sdk/mods/commits"
);
if (!commit) {
return indexUpdateProgress(UpdateStatus::Failed, commit.error());
}
auto json = commit.value();
if (
json.is_object() &&
json.contains("documentation_url") &&
json.contains("message")
) {
// whoops! got rate limited
return indexUpdateProgress(
UpdateStatus::Failed,
json["message"].get<std::string>()
);
}
indexUpdateProgress(
UpdateStatus::Progress, "Checking index status", 25
);
// read sha of latest commit
if (!json.is_array()) {
return indexUpdateProgress(
UpdateStatus::Failed,
"Fetched commits, expected 'array', got '" +
std::string(json.type_name()) + "'. "
"Report this bug to the Geode developers!"
);
}
if (!json.at(0).is_object()) {
return indexUpdateProgress(
UpdateStatus::Failed,
"Fetched commits, expected 'array.object', got 'array." +
std::string(json.type_name()) + "'. "
"Report this bug to the Geode developers!"
);
}
if (!json.at(0).contains("sha")) {
return indexUpdateProgress(
UpdateStatus::Failed,
"Fetched commits, missing '0.sha'. "
"Report this bug to the Geode developers!"
);
}
auto upcomingCommitSHA = json.at(0)["sha"];
// read sha of currently installed commit
std::string currentCommitSHA = "";
if (ghc::filesystem::exists(indexDir / "current")) {
auto data = utils::file::readString(indexDir / "current");
if (data) {
currentCommitSHA = data.value();
}
}
// update if forced or latest commit has
// different sha
if (force || currentCommitSHA != upcomingCommitSHA) {
// save new sha in file
utils::file::writeString(indexDir / "current", upcomingCommitSHA);
// download latest commit (by downloading
// the repo as a zip)
indexUpdateProgress(
UpdateStatus::Progress,
"Downloading index",
50
);
auto gotZip = web::fetchFile(
"https://github.com/geode-sdk/mods/zipball/main",
indexDir / "index.zip"
);
if (!gotZip) {
return indexUpdateProgress(
UpdateStatus::Failed,
gotZip.error()
);
}
// delete old index
if (ghc::filesystem::exists(indexDir / "index")) {
ghc::filesystem::remove_all(indexDir / "index");
}
auto unzip = file::unzipTo(indexDir / "index.zip", indexDir);
if (!unzip) {
return indexUpdateProgress(
UpdateStatus::Failed, unzip.error()
);
}
}
#endif
// update index
indexUpdateProgress(
UpdateStatus::Progress,
"Updating index",
75
);
this->updateIndexFromLocalCache();
m_upToDate = true;
m_updating = false;
indexUpdateProgress(
UpdateStatus::Finished,
"",
100
);
}
void Index::indexUpdateProgress(
UpdateStatus status,
std::string const& info,
uint8_t percentage
) {
Loader::get()->queueInGDThread([this, status, info, percentage]() -> void {
m_callbacksMutex.lock();
for (auto& d : m_callbacks) {
d(status, info, percentage);
}
if (
status == UpdateStatus::Finished ||
status == UpdateStatus::Failed
) {
m_callbacks.clear();
}
m_callbacksMutex.unlock();
});
}
void Index::updateIndex(IndexUpdateCallback callback, bool force) {
#define RETURN_ERROR(str) \
std::string err__ = (str); \
if (callback) callback( \
UpdateStatus::Failed, \
err__, \
0 \
); \
log::info("Index update failed: {}", err__);\
return;
// if already updated and no force, let
// delegate know
if (!force && m_upToDate) {
@ -225,36 +91,134 @@ void Index::updateIndex(IndexUpdateCallback callback, bool force) {
return;
}
// add delegate thread-safely if it's not
// added already
if (callback) {
m_callbacksMutex.lock();
m_callbacks.push_back(callback);
m_callbacksMutex.unlock();
}
// if already updating, let delegate know
// and return
if (m_updating) {
if (callback) {
callback(
UpdateStatus::Progress,
"Waiting for update info",
0
);
}
return;
}
m_updating = true;
// create directory for the local clone of
// the index
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
ghc::filesystem::create_directories(indexDir);
// update index in another thread to avoid
// pausing UI
std::thread(&Index::updateIndexThread, this, force).detach();
#if GITHUB_DONT_RATE_LIMIT_ME_PLS == 1
auto err = this->updateIndexFromLocalCache();
if (!err) {
RETURN_ERROR(err);
}
m_upToDate = true;
m_updating = false;
if (callback) callback(UpdateStatus::Finished, "", 100);
return;
#endif
web::AsyncWebRequest()
.join("index-update")
.fetch("https://api.github.com/repos/geode-sdk/mods/commits")
.json()
.then([this, force, callback](nlohmann::json const& json) {
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
// check if rate-limited (returns object)
JsonChecker checkerObj(json);
auto obj = checkerObj.root("[geode-sdk/mods/commits]").obj();
if (obj.has("documentation_url") && obj.has("message")) {
RETURN_ERROR(obj.has("message").get<std::string>());
}
// get sha of latest commit
JsonChecker checker(json);
auto root = checker.root("[geode-sdk/mods/commits]").array();
std::string upcomingCommitSHA;
if (auto first = root.at(0).obj().needs("sha")) {
upcomingCommitSHA = first.get<std::string>();
} else {
RETURN_ERROR("Unable to get hash from latest commit: " + checker.getError());
}
// read sha of currently installed commit
std::string currentCommitSHA = "";
if (ghc::filesystem::exists(indexDir / "current")) {
auto data = utils::file::readString(indexDir / "current");
if (data) {
currentCommitSHA = data.value();
}
}
// update if forced or latest commit has
// different sha
if (force || currentCommitSHA != upcomingCommitSHA) {
// save new sha in file
utils::file::writeString(indexDir / "current", upcomingCommitSHA);
web::AsyncWebRequest()
.join("index-download")
.fetch("https://github.com/geode-sdk/mods/zipball/main")
.into(indexDir / "index.zip")
.then([this, indexDir, callback](auto) {
// delete old index
try {
if (ghc::filesystem::exists(indexDir / "index")) {
ghc::filesystem::remove_all(indexDir / "index");
}
} catch(std::exception& e) {
RETURN_ERROR("Unable to delete old index " + std::string(e.what()));
}
// unzip new index
auto unzip = file::unzipTo(indexDir / "index.zip", indexDir);
if (!unzip) {
RETURN_ERROR(unzip.error());
}
// update index
auto err = this->updateIndexFromLocalCache();
if (!err) {
RETURN_ERROR(err.error());
}
m_upToDate = true;
m_updating = false;
if (callback) callback(
UpdateStatus::Finished, "", 100
);
})
.expect([callback](std::string const& err) {
RETURN_ERROR(err);
})
.progress([callback](web::SentAsyncWebRequest& req, double now, double total) {
if (callback) callback(
UpdateStatus::Progress,
"Downloading",
static_cast<int>(now / total * 100.0)
);
});
} else {
auto err = this->updateIndexFromLocalCache();
if (!err) {
RETURN_ERROR(err.error());
}
m_upToDate = true;
m_updating = false;
if (callback) callback(
UpdateStatus::Finished,
"", 100
);
}
})
.expect([callback](std::string const& err) {
RETURN_ERROR(err);
})
.progress([callback](web::SentAsyncWebRequest& req, double now, double total) {
if (callback) callback(
UpdateStatus::Progress,
"Downloading",
static_cast<int>(now / total * 100.0)
);
});
}
void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
@ -271,12 +235,7 @@ void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
return;
}
auto readModJson = readJSON<ModJson>(dir / "mod.json");
if (!readModJson) {
log::warn("Error reading mod.json: {}, skipping", readModJson.error());
return;
}
auto info = ModInfo::create(readModJson.value());
auto info = ModInfo::createFromFile(dir / "mod.json");
if (!info) {
log::warn("{}: {}, skipping", dir, info.error());
return;
@ -338,7 +297,7 @@ void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
}
}
void Index::updateIndexFromLocalCache() {
Result<> Index::updateIndexFromLocalCache() {
m_items.clear();
auto baseIndexDir = Loader::get()->getGeodeDirectory() / "index";
@ -352,10 +311,19 @@ void Index::updateIndexFromLocalCache() {
// load index mods
auto modsDir = baseIndexDir / "index";
for (auto const& dir : ghc::filesystem::directory_iterator(modsDir)) {
if (ghc::filesystem::is_directory(dir)) {
this->addIndexItemFromFolder(dir);
if (ghc::filesystem::exists(modsDir)) {
for (auto const& dir : ghc::filesystem::directory_iterator(modsDir)) {
if (ghc::filesystem::is_directory(dir)) {
this->addIndexItemFromFolder(dir);
}
}
log::info("Index updated");
return Ok();
} else {
return Err(
"Index appears not to have been "
"downloaded, or is fully empty"
);
}
}
@ -446,9 +414,8 @@ Result<std::vector<std::string>> Index::checkDependenciesForItem(
}
}
Result<InstallTicket*> Index::installItems(
std::vector<IndexItem> const& items,
ItemInstallCallback progress
Result<InstallHandle> Index::installItems(
std::vector<IndexItem> const& items
) {
std::vector<std::string> ids {};
for (auto& item : items) {
@ -482,32 +449,34 @@ Result<InstallTicket*> Index::installItems(
}
utils::vector::push(ids, list.value());
}
return Ok(new InstallTicket(this, ids, progress));
auto ret = std::make_shared<InstallItems>(
std::unordered_set(ids.begin(), ids.end())
);
m_installations.insert(ret);
return Ok(ret);
}
Result<InstallTicket*> Index::installItem(
IndexItem const& item,
ItemInstallCallback progress
Result<InstallHandle> Index::installItem(
IndexItem const& item
) {
return this->installItems({ item }, progress);
return this->installItems({ item });
}
bool Index::isUpdateAvailableForItem(std::string const& id) const {
if (!Loader::get()->isModInstalled(id)) {
return false;
}
if (!this->isKnownItem(id)) {
return false;
}
return
this->getKnownItem(id).m_info.m_version >
Loader::get()->getInstalledMod(id)->getVersion();
return this->isUpdateAvailableForItem(this->getKnownItem(id));
}
bool Index::isUpdateAvailableForItem(IndexItem const& item) const {
if (!Loader::get()->isModInstalled(item.m_info.m_id)) {
return false;
}
// has the mod been updated (but update not yet been applied by restarting game)
if (m_updated.count(item.m_info.m_id)) {
return false;
}
return
item.m_info.m_version >
Loader::get()->getInstalledMod(item.m_info.m_id)->getVersion();
@ -522,9 +491,7 @@ bool Index::areUpdatesAvailable() const {
return false;
}
Result<InstallTicket*> Index::installUpdates(
IndexUpdateCallback callback, bool force
) {
Result<InstallHandle> Index::installAllUpdates() {
// find items that need updating
std::vector<IndexItem> itemsToUpdate {};
for (auto& item : m_items) {
@ -532,52 +499,222 @@ Result<InstallTicket*> Index::installUpdates(
itemsToUpdate.push_back(item);
}
}
return this->installItems(itemsToUpdate);
}
// generate ticket
auto ticket = this->installItems(
itemsToUpdate,
[itemsToUpdate, callback](
InstallTicket*,
UpdateStatus status,
std::string const& info,
uint8_t progress
) -> void {
switch (status) {
case UpdateStatus::Failed: {
callback(
UpdateStatus::Failed,
"Updating failed: " + info,
0
);
} break;
case UpdateStatus::Finished: {
std::string updatedStr = "";
for (auto& item : itemsToUpdate) {
updatedStr += item.m_info.m_name + " (" +
item.m_info.m_id + ")\n";
}
callback(
UpdateStatus::Finished,
"Updated the following mods: " +
updatedStr +
"Please restart to apply changes.",
100
);
} break;
case UpdateStatus::Progress: {
callback(UpdateStatus::Progress, info, progress);
} break;
}
}
std::vector<InstallHandle> Index::getRunningInstallations() const {
return std::vector<InstallHandle>(
m_installations.begin(),
m_installations.end()
);
if (!ticket) {
return Err(ticket.error());
}
InstallHandle Index::isInstallingItem(std::string const& id) {
for (auto& inst : m_installations) {
if (inst->m_toInstall.count(id)) {
return inst;
}
}
return nullptr;
}
std::unordered_set<std::string> InstallItems::toInstall() const {
return m_toInstall;
}
InstallItems::CallbackID InstallItems::join(ItemInstallCallback callback) {
// already finished?
if (m_started && this->finished()) {
callback(shared_from_this(), UpdateStatus::Finished, "", 100);
return 0;
}
// start at one because 0 means invalid callback
static CallbackID COUNTER = 1;
if (callback) {
auto id = COUNTER++;
m_callbacks.insert({ id, callback });
return id;
}
return 0;
}
void InstallItems::leave(InstallItems::CallbackID id) {
m_callbacks.erase(id);
}
void InstallItems::post(
UpdateStatus status,
std::string const& info,
uint8_t progress
) {
for (auto& [_, cb] : m_callbacks) {
cb(shared_from_this(), status, info, progress);
}
}
void InstallItems::progress(
std::string const& info,
uint8_t progress
) {
this->post(UpdateStatus::Progress, info, progress);
}
void InstallItems::error(std::string const& info) {
this->post(UpdateStatus::Failed, info, 0);
}
void InstallItems::finish(bool replaceFiles) {
// move files from temp dir to geode directory
auto tempDir = Loader::get()->getGeodeDirectory() / "index" / "temp";
for (auto& file : ghc::filesystem::directory_iterator(tempDir)) {
try {
auto modDir = Loader::get()->getGeodeDirectory() / "mods";
auto targetFile = modDir / file.path().filename();
auto targetName = file.path().stem();
if (!replaceFiles) {
// find valid filename that doesn't exist yet
auto filename = ghc::filesystem::path(targetName)
.replace_extension("")
.string();
size_t number = 0;
while (ghc::filesystem::exists(targetFile)) {
targetFile = modDir /
(filename + std::to_string(number) + ".geode");
number++;
}
}
// move file
ghc::filesystem::rename(file, targetFile);
} catch(std::exception& e) {
try { ghc::filesystem::remove_all(tempDir); } catch(...) {}
return this->error(
"Unable to move downloaded file to mods directory: \"" +
std::string(e.what()) + " \" "
"(This might be due to insufficient permissions to "
"write files under SteamLibrary, try running GD as "
"administrator)"
);
}
}
// install updates concurrently
ticket.value()->start(InstallMode::Concurrent);
// load mods
Loader::get()->refreshMods();
// finished
this->post(UpdateStatus::Finished, "", 100);
return ticket;
// let index know these mods have been updated
for (auto& inst : m_toInstall) {
Index::get()->m_updated.insert(inst);
}
// if no one is listening, show a popup anyway
if (!m_callbacks.size()) {
FLAlertLayer::create(
"Mods installed",
"The following <cy>mods</c> have been installed: " +
container::join(m_toInstall, ",") + "\n"
"Please <cr>restart the game</c> to apply",
"OK"
)->show();
}
// no longer need to ensure aliveness
Index::get()->m_installations.erase(shared_from_this());
}
InstallItems::CallbackID InstallItems::start(
ItemInstallCallback callback,
bool replaceFiles
) {
auto id = this->join(callback);
// check if started already, if so, behave like join
if (m_started) return id;
m_started = true;
for (auto& inst : m_toInstall) {
// by virtue of running this function we know item must be valid
auto item = Index::get()->getKnownItem(inst);
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
file::createDirectoryAll(indexDir / "temp");
auto tempFile = indexDir / "temp" / item.m_download.m_filename;
m_downloaded.push_back(tempFile);
auto handle = web::AsyncWebRequest()
.join("install_mod_" + inst)
.fetch(item.m_download.m_url)
.into(tempFile)
.then([this, replaceFiles, item, inst, indexDir, tempFile](auto) {
// check for 404
auto notFound = utils::file::readString(tempFile);
if (notFound && notFound.value() == "Not Found") {
try { ghc::filesystem::remove(tempFile); } catch(...) {}
return this->error(
"Binary file download returned \"Not found\". Report "
"this to the Geode development team."
);
}
// verify checksum
this->progress("Verifying", 100);
if (::calculateHash(tempFile) != item.m_download.m_hash) {
try { ghc::filesystem::remove(tempFile); } catch(...) {}
return this->error(
"Checksum mismatch! (Downloaded file did not match what "
"was expected. Try again, and if the download fails another time, "
"report this to the Geode development team."
);
}
// finished() just checks if the web requests are done
if (this->finished()) {
this->finish(replaceFiles);
}
})
.expect([this, inst](std::string const& error) {
this->error(error);
this->cancel();
})
.cancelled([this, item](auto&) {
this->cancel();
})
.progress([this, inst](web::SentAsyncWebRequest&, double now, double total) {
this->progress(
"Downloading binary",
static_cast<uint8_t>(now / total * 100.0)
);
})
.send();
m_handles.push_back(handle);
}
// manage installation in the index until it's finished so
// even if no one listens to it it doesn't get freed from
// memory
Index::get()->m_installations.insert(shared_from_this());
return id;
}
bool InstallItems::finished() const {
for (auto& inst : m_handles) {
if (!inst->finished()) {
return false;
}
}
return true;
}
void InstallItems::cancel() {
for (auto& inst : m_handles) {
inst->cancel();
}
}

View file

@ -1,14 +1,17 @@
#pragma once
#include <Geode/Geode.hpp>
#include <mutex>
#include <optional>
#include <Geode/utils/fetch.hpp>
#include <unordered_set>
USE_GEODE_NAMESPACE();
class Index;
struct ModInstallUpdate;
class InstallTicket;
struct InstallItems;
using InstallHandle = std::shared_ptr<InstallItems>;
// todo: make index use events
@ -19,7 +22,7 @@ enum class UpdateStatus {
};
using ItemInstallCallback = std::function<void(
InstallTicket*, UpdateStatus, std::string const&, uint8_t
InstallHandle, UpdateStatus, std::string const&, uint8_t
)>;
using IndexUpdateCallback = std::function<void(
UpdateStatus, std::string const&, uint8_t
@ -39,73 +42,38 @@ struct IndexItem {
std::unordered_set<std::string> m_categories;
};
enum class InstallMode {
Order, // download & install one-by-one
Concurrent, // download & install all simultaneously
};
struct InstallItems final : public std::enable_shared_from_this<InstallItems> {
public:
using CallbackID = size_t;
/**
* Used for working with a currently
* happening mod installation from
* the index. Note that once the
* installation is finished / failed,
* the ticket will free its own memory,
* so make sure to let go of any
* pointers you may have to it.
*/
class InstallTicket {
protected:
ItemInstallCallback m_progress;
const std::vector<std::string> m_installList;
mutable std::mutex m_cancelMutex;
bool m_cancelling = false;
bool m_installing = false;
bool m_replaceFiles = true;
Index* m_index;
private:
bool m_started = false;
std::unordered_set<std::string> m_toInstall;
std::vector<web::SentAsyncWebRequestHandle> m_handles;
std::unordered_map<CallbackID, ItemInstallCallback> m_callbacks;
std::vector<ghc::filesystem::path> m_downloaded;
void postProgress(
UpdateStatus status,
std::string const& info = "",
uint8_t progress = 0
);
void install(std::string const& id);
void post(UpdateStatus status, std::string const& info, uint8_t progress);
void progress(std::string const& info, uint8_t progress);
void error(std::string const& info);
void finish(bool replaceFiles);
friend class Index;
public:
/**
* Create a new ticket for installing a list of mods. This method
* should not be called manually; instead, you should always use
* `Index::installItem`. Note that once the installation is
* finished / failed, the ticket will free its own memory, so make
* sure to let go of any pointers you may have to it.
*/
InstallTicket(
Index* index,
std::vector<std::string> const& list,
ItemInstallCallback progress
);
std::unordered_set<std::string> toInstall() const;
/**
* Get list of mods to install
*/
std::vector<std::string> getInstallList() const;
inline InstallItems(
std::unordered_set<std::string> const& toInstall
) : m_toInstall(toInstall) {}
/**
* Cancel all pending installations and revert finished ones. This
* function is thread-safe
*/
void cancel();
bool finished() const;
/**
* Begin installation. Note that this function is *not*
* thread-safe
* @param mode Whether to install the list of mods
* provided concurrently or in order
* @note Use InstallTicket::cancel to cancel the
* installation
*/
void start(InstallMode mode = InstallMode::Concurrent);
CallbackID join(ItemInstallCallback callback);
void leave(CallbackID id);
CallbackID start(ItemInstallCallback callback, bool replaceFiles = true);
};
class Index {
@ -113,50 +81,43 @@ protected:
bool m_upToDate = false;
bool m_updating = false;
mutable std::mutex m_callbacksMutex;
std::vector<IndexUpdateCallback> m_callbacks;
std::vector<IndexItem> m_items;
std::unordered_set<InstallHandle> m_installations;
mutable std::mutex m_ticketsMutex;
std::unordered_set<std::string> m_featured;
std::unordered_set<std::string> m_categories;
std::unordered_set<std::string> m_updated;
void indexUpdateProgress(
UpdateStatus status,
std::string const& info = "",
uint8_t percentage = 0
);
void updateIndexThread(bool force);
void addIndexItemFromFolder(ghc::filesystem::path const& dir);
void updateIndexFromLocalCache();
Result<> updateIndexFromLocalCache();
Result<std::vector<std::string>> checkDependenciesForItem(
IndexItem const& item
);
friend struct InstallItems;
public:
static Index* get();
std::vector<IndexItem> getItems() const;
bool isKnownItem(std::string const& id) const;
IndexItem getKnownItem(std::string const& id) const;
Result<InstallTicket*> installItems(
std::vector<IndexItem> const& item,
ItemInstallCallback progress = nullptr
);
Result<InstallTicket*> installItem(
IndexItem const& item,
ItemInstallCallback progress = nullptr
);
bool isUpdateAvailableForItem(std::string const& id) const;
bool isUpdateAvailableForItem(IndexItem const& item) const;
bool areUpdatesAvailable() const;
Result<InstallTicket*> installUpdates(
IndexUpdateCallback callback = nullptr,
bool force = false
);
std::unordered_set<std::string> getCategories() const;
std::vector<IndexItem> getFeaturedItems() const;
bool isFeaturedItem(std::string const& item) const;
Result<InstallHandle> installItems(std::vector<IndexItem> const& item);
Result<InstallHandle> installItem(IndexItem const& item);
std::vector<InstallHandle> getRunningInstallations() const;
InstallHandle isInstallingItem(std::string const& id);
bool isUpdateAvailableForItem(std::string const& id) const;
bool isUpdateAvailableForItem(IndexItem const& item) const;
bool areUpdatesAvailable() const;
Result<InstallHandle> installAllUpdates();
bool isIndexUpdated() const;
void updateIndex(IndexUpdateCallback callback, bool force = false);
};

View file

@ -1,201 +0,0 @@
#include "Index.hpp"
#include <thread>
#include <Geode/utils/json.hpp>
#include <hash.hpp>
#include <Geode/utils/fetch.hpp>
void InstallTicket::postProgress(
UpdateStatus status,
std::string const& info,
uint8_t percentage
) {
if (m_progress) {
Loader::get()->queueInGDThread([this, status, info, percentage]() -> void {
m_progress(this, status, info, percentage);
});
}
if (status == UpdateStatus::Failed || status == UpdateStatus::Finished) {
log::info("Deleting InstallTicket at {}", this);
// clean up own memory
delete this;
}
}
InstallTicket::InstallTicket(
Index* index,
std::vector<std::string> const& list,
ItemInstallCallback progress
) : m_index(index),
m_installList(list),
m_progress(progress) {}
std::vector<std::string> InstallTicket::getInstallList() const {
return m_installList;
}
void InstallTicket::install(std::string const& id) {
// run installing in another thread in order
// to render progress on screen while installing
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
auto item = m_index->getKnownItem(id);
this->postProgress(UpdateStatus::Progress, "Checking status", 0);
// download to temp file in index dir
auto tempFile = indexDir / item.m_download.m_filename;
this->postProgress(UpdateStatus::Progress, "Fetching binary", 0);
auto res = web::fetchFile(
item.m_download.m_url,
tempFile,
[this, tempFile](double now, double total) -> int {
// check if cancelled
std::lock_guard cancelLock(m_cancelMutex);
if (m_cancelling) {
try { ghc::filesystem::remove(tempFile); } catch(...) {}
return false;
}
// no need to scope the lock guard more as this
// function is going to exit right after anyway
this->postProgress(
UpdateStatus::Progress,
"Downloading binary",
static_cast<uint8_t>(now / total * 100.0)
);
return true;
}
);
if (!res) {
try { ghc::filesystem::remove(tempFile); } catch(...) {}
return this->postProgress(
UpdateStatus::Failed,
"Downloading failed: " + res.error()
);
}
// check if cancelled
{
std::lock_guard cancelLock(m_cancelMutex);
if (m_cancelling) {
ghc::filesystem::remove(tempFile);
return;
}
// scope ends here since we don't need to
// access m_cancelling anymore
}
// check for 404
auto notFound = utils::file::readString(tempFile);
if (notFound && notFound.value() == "Not Found") {
try { ghc::filesystem::remove(tempFile); } catch(...) {}
return this->postProgress(
UpdateStatus::Failed,
"Binary file download returned \"Not found\". Report "
"this to the Geode development team."
);
}
// verify checksum
this->postProgress(UpdateStatus::Progress, "Verifying", 100);
auto checksum = ::calculateHash(tempFile.string());
if (checksum != item.m_download.m_hash) {
try { ghc::filesystem::remove(tempFile); } catch(...) {}
return this->postProgress(
UpdateStatus::Failed,
"Checksum mismatch! (Downloaded file did not match what "
"was expected. Try again, and if the download fails another time, "
"report this to the Geode development team."
);
}
// move temp file to geode directory
try {
auto modDir = Loader::get()->getGeodeDirectory() / "mods";
auto targetFile = modDir / item.m_download.m_filename;
// find valid filename that doesn't exist yet
if (!m_replaceFiles) {
auto filename = ghc::filesystem::path(
item.m_download.m_filename
).replace_extension("").string();
size_t number = 0;
while (ghc::filesystem::exists(targetFile)) {
targetFile = modDir /
(filename + std::to_string(number) + ".geode");
number++;
}
}
// move file
ghc::filesystem::rename(tempFile, targetFile);
} catch(std::exception& e) {
try { ghc::filesystem::remove(tempFile); } catch(...) {}
return this->postProgress(
UpdateStatus::Failed,
"Unable to move downloaded file to mods directory: \"" +
std::string(e.what()) + " \" "
"(This might be due to insufficient permissions to "
"write files under SteamLibrary, try running GD as "
"administrator)"
);
}
// call next in queue or post finish message
Loader::get()->queueInGDThread([this, id]() -> void {
// todo: Loader::get()->refreshMods(m_updateMod);
// where the Loader unloads the mod binary and
// reloads it from disk (this should prolly be
// done only for the installed mods)
Loader::get()->refreshMods();
// already in GD thread, so might aswell do the
// progress posting manually
if (m_progress) {
(m_progress)(
this, UpdateStatus::Finished, "", 100
);
}
// clean up memory
delete this;
});
}
void InstallTicket::cancel() {
// really no point in using std::lock_guard here
// since just plain locking and unlocking the mutex
// will do the job just fine with the same legibility
m_cancelMutex.lock();
m_cancelling = true;
m_cancelMutex.unlock();
}
void InstallTicket::start(InstallMode mode) {
if (m_installing) return;
// make sure we have stuff to install
if (!m_installList.size()) {
return this->postProgress(
UpdateStatus::Failed, "Nothing to install", 0
);
}
m_installing = true;
switch (mode) {
case InstallMode::Concurrent: {
for (auto& id : m_installList) {
std::thread(&InstallTicket::install, this, id).detach();
}
} break;
case InstallMode::Order: {
std::thread([this]() -> void {
for (auto& id : m_installList) {
this->install(id);
}
}).detach();
} break;
}
}

View file

@ -6,9 +6,11 @@
#include "InternalMod.hpp"
#include <Geode/loader/Log.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/Geode.hpp>
#include <Geode/utils/fetch.hpp>
#include <thread>
#include "resources.hpp"
#include <hash.hpp>
#include <Geode/utils/file.hpp>
InternalLoader::InternalLoader() : Loader() {}
@ -40,19 +42,21 @@ bool InternalLoader::setup() {
void InternalLoader::queueInGDThread(ScheduledFunction func) {
std::lock_guard<std::mutex> lock(m_gdThreadMutex);
this->m_gdThreadQueue.push_back(func);
m_gdThreadQueue.push_back(func);
}
void InternalLoader::executeGDThreadQueue() {
// copy queue to avoid locking mutex if someone is
// running addToGDThread inside their function
m_gdThreadMutex.lock();
auto queue = std::move(m_gdThreadQueue);
auto queue = m_gdThreadQueue;
m_gdThreadQueue.clear();
m_gdThreadMutex.unlock();
// call queue
for (auto const& func : queue) {
func();
}
m_gdThreadMutex.lock();
m_gdThreadQueue.clear();
m_gdThreadMutex.unlock();
}
void InternalLoader::logConsoleMessage(std::string const& msg) {
@ -82,6 +86,121 @@ void InternalLoader::loadInfoAlerts(nlohmann::json& json) {
m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>();
}
void InternalLoader::downloadLoaderResources(IndexUpdateCallback callback) {
auto version = this->getVersion().toString();
auto tempResourcesZip = this->getGeodeDirectory() / GEODE_RESOURCE_DIRECTORY / "new.zip";
auto resourcesDir = this->getGeodeDirectory() /
GEODE_RESOURCE_DIRECTORY / InternalMod::get()->getID();
web::AsyncWebRequest()
.join("update-geode-loader-resources")
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/tags/" + version)
.json()
.then([tempResourcesZip, resourcesDir, callback](nlohmann::json const& json) {
auto checker = JsonChecker(json);
auto root = checker.root("[matching geode release]").obj();
// find resources.zip and download it
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").get<std::string>() == "resources.zip") {
auto url = obj.needs("browser_download_url").get<std::string>();
if (url.size()) {
web::AsyncWebRequest()
.fetch(url)
.into(tempResourcesZip)
.then([tempResourcesZip, resourcesDir, callback](auto) {
// unzip resources zip
auto unzip = file::unzipTo(tempResourcesZip, resourcesDir);
if (!unzip) {
if (callback) callback(
UpdateStatus::Failed,
"Unable to unzip new resources: " + unzip.error(),
0
);
return;
}
// delete resources zip
try {
ghc::filesystem::remove(tempResourcesZip);
} catch(...) {}
if (callback) callback(
UpdateStatus::Finished,
"Resources updated",
100
);
})
.expect([callback](std::string const& info) {
if (callback) callback(UpdateStatus::Failed, info, 0);
})
.progress([callback](auto&, double now, double total) {
if (callback) callback(
UpdateStatus::Progress,
"Downloading resources",
static_cast<uint8_t>(now / total * 100.0)
);
});
}
}
}
if (checker.isError()) {
if (callback) callback(UpdateStatus::Failed, checker.getError(), 0);
}
})
.expect([callback](std::string const& info) {
if (callback) callback(UpdateStatus::Failed, info, 0);
});
}
bool InternalLoader::verifyLoaderResources(IndexUpdateCallback callback) {
static std::optional<bool> CACHED = std::nullopt;
if (CACHED.has_value()) {
return CACHED.value();
}
// geode/resources/geode.loader
auto resourcesDir = this->getGeodeDirectory() /
GEODE_RESOURCE_DIRECTORY / InternalMod::get()->getID();
// if the resources dir doesn't exist, then it's probably incorrect
if (!(
ghc::filesystem::exists(resourcesDir) &&
ghc::filesystem::is_directory(resourcesDir)
)) {
this->downloadLoaderResources(callback);
return false;
}
// make sure every file was covered
size_t coverage = 0;
// verify hashes
for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) {
auto name = file.path().filename().string();
// skip unknown files
if (!LOADER_RESOURCE_HASHES.count(name)) {
continue;
}
// verify hash
auto hash = calculateSHA256(file.path());
if (hash != LOADER_RESOURCE_HASHES.at(name)) {
this->downloadLoaderResources(callback);
return false;
}
coverage += 1;
}
// make sure every file was found
if (coverage != LOADER_RESOURCE_HASHES.size()) {
this->downloadLoaderResources(callback);
return false;
}
return true;
}
#if defined(GEODE_IS_WINDOWS)
void InternalLoader::platformMessageBox(const char* title, std::string const& info) {
MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR);

View file

@ -10,6 +10,7 @@
#include <unordered_set>
#include <Geode/utils/json.hpp>
#include <optional>
#include "../index/Index.hpp"
USE_GEODE_NAMESPACE();
@ -27,6 +28,8 @@ protected:
void saveInfoAlerts(nlohmann::json& json);
void loadInfoAlerts(nlohmann::json& json);
void downloadLoaderResources(IndexUpdateCallback callback);
InternalLoader();
~InternalLoader();
@ -54,6 +57,8 @@ public:
void openPlatformConsole();
void closePlatformConsole();
static void platformMessageBox(const char* title, std::string const& info);
bool verifyLoaderResources(IndexUpdateCallback callback);
friend int geodeEntry(void* platformData);
};

View file

@ -42,7 +42,7 @@ InternalMod::InternalMod() : Mod(getInternalModInfo()) {
auto sett = this->loadSettings();
if (!sett) {
log::log(Severity::Error, this, sett.error());
log::log(Severity::Error, this, "{}", sett.error());
}
}

View file

@ -4,7 +4,8 @@
#ifdef GEODE_IS_WINDOWS
#include <Geode/Geode.hpp>
#include <Geode/utils/casts.hpp>
#include <Geode/utils/file.hpp>
#include "../crashlog.hpp"
#include <Windows.h>
@ -266,11 +267,7 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
std::ofstream file;
file.open(
crashlog::getCrashLogDirectory() + "/" + getDateString(true) + ".log",
std::ios::app
);
std::stringstream file;
// init symbols so we can get some juicy debug info
g_symbolsInitialized = SymInitialize(
@ -293,7 +290,7 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
<< "Please submit this crash report to its developer ("
<< faultyMod->getDeveloper() << ") for assistance.\n";
}
// geode info
file << "\n== Geode Information ==\n";
printGeodeInfo(file);
@ -314,6 +311,20 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
file << "\n== Installed Mods ==\n";
printMods(file);
// show message box on debug mode
#ifdef GEODE_DEBUG
MessageBoxA(nullptr, file.str().c_str(), "Geode Crashed", MB_ICONERROR);
#endif
// save actual file
std::ofstream actualFile;
actualFile.open(
crashlog::getCrashLogDirectory() + "/" + getDateString(true) + ".log",
std::ios::app
);
actualFile << file.rdbuf() << std::flush;
actualFile.close();
return EXCEPTION_CONTINUE_SEARCH;
}

View file

@ -86,7 +86,7 @@ bool InternalLoader::loadHooks() {
for (auto const& hook : internalHooks()) {
auto res = hook.mod->addHook(hook.hook);
if (!res) {
log::log(Severity::Error, hook.mod, res.error());
log::log(Severity::Error, hook.mod, "{}", res.error());
thereWereErrors = true;
}
}

View file

@ -10,7 +10,6 @@
#include <Geode/utils/map.hpp>
#include <Geode/utils/types.hpp>
#include <mutex>
#include <Geode/Geode.hpp>
#include <about.hpp>
#include <crashlog.hpp>
@ -51,6 +50,8 @@ void Loader::createDirectories() {
}
void Loader::updateResourcePaths() {
log::debug("Updating resources paths");
// add own geode/resources directory
CCFileUtils::sharedFileUtils()->addSearchPath(
(this->getGeodeDirectory() / GEODE_RESOURCE_DIRECTORY).string().c_str()
@ -62,11 +63,20 @@ void Loader::updateResourcePaths() {
// add geode/temp/mod.id/resources for accessing additional resources in mods
for (auto& [_, mod] : m_mods) {
if (mod->m_addResourcesToSearchPath) {
CCFileUtils::sharedFileUtils()->addSearchPath(
(tempDir / mod->getID() / "resources").string().c_str()
);
}
this->updateModResourcePaths(mod);
}
}
void Loader::updateModResourcePaths(Mod* mod) {
if (mod->m_addResourcesToSearchPath) {
CCFileUtils::sharedFileUtils()->addSearchPath(
(this->getGeodeDirectory() /
GEODE_TEMP_DIRECTORY /
mod->getID() /
"resources"
).string().c_str()
);
log::debug("Added resources path for {}", mod->getID());
}
}
@ -89,11 +99,15 @@ void Loader::updateModResources(Mod* mod) {
CCTextureCache::sharedTextureCache()->addImage(png.c_str(), false);
CCSpriteFrameCache::sharedSpriteFrameCache()
->addSpriteFramesWithFile(plist.c_str());
log::debug("Added resources for {}", mod->getID());
}
}
}
void Loader::updateResources() {
log::debug("Adding mod resources");
// add own spritesheets
this->updateModResources(InternalMod::get());
@ -327,6 +341,12 @@ bool Loader::setup() {
if (m_isSetup)
return true;
if (crashlog::setupPlatformHandler()) {
log::debug("Set up platform crash logger");
} else {
log::debug("Unable to set up platform crash logger");
}
log::debug("Setting up Loader...");
this->createDirectories();
@ -336,14 +356,9 @@ bool Loader::setup() {
// add resources on startup
this->queueInGDThread([]() {
Loader::get()->updateResourcePaths();
Loader::get()->updateResources();
});
if (crashlog::setupPlatformHandler()) {
log::debug("Set up platform crash logger");
} else {
log::debug("Unable to set up platform crash logger");
}
m_isSetup = true;
return true;

View file

@ -1,4 +1,3 @@
#include <Geode/Geode.hpp>
#include <Geode/loader/Log.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/Loader.hpp>

View file

@ -78,7 +78,8 @@ Result<> Mod::loadSettings() {
log::log(
Severity::Warning,
this,
"Encountered unknown setting \"" + key + "\" while loading settings"
"Encountered unknown setting \"{}\" while loading settings",
key
);
}
}
@ -382,13 +383,13 @@ bool Mod::updateDependencyStates() {
auto r = dep.m_mod->load();
if (!r) {
dep.m_state = ModResolveState::Unloaded;
log::log(Severity::Error, dep.m_mod, r.error());
log::log(Severity::Error, dep.m_mod, "{}", r.error());
}
else {
auto r = dep.m_mod->enable();
if (!r) {
dep.m_state = ModResolveState::Disabled;
log::log(Severity::Error, dep.m_mod, r.error());
log::log(Severity::Error, dep.m_mod, "{}", r.error());
}
}
} else {

View file

@ -6,9 +6,9 @@
USE_GEODE_NAMESPACE();
static std::string sanitizeDetailsData(unsigned char* start, unsigned char* end) {
static std::string sanitizeDetailsData(std::string const& str) {
// delete CRLF
return utils::string::replace(std::string(start, end), "\r", "");
return utils::string::replace(str, "\r", "");
}
Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
@ -194,6 +194,9 @@ Result<ModInfo> ModInfo::createFromFile(ghc::filesystem::path const& path) {
if (!res) return res;
auto info = res.value();
info.m_path = path;
if (path.has_parent_path()) {
info.addSpecialFiles(path.parent_path());
}
return Ok(info);
} catch(std::exception& e) {
return Err("Unable to parse mod.json: " + std::string(e.what()));
@ -241,24 +244,51 @@ Result<ModInfo> ModInfo::createFromGeodeFile(ghc::filesystem::path const& path)
}
auto info = res.value();
info.m_path = path;
info.addSpecialFiles(unzip);
return Ok(info);
}
Result<> ModInfo::addSpecialFiles(ZipFile& unzip) {
// unzip known MD files
using God = std::initializer_list<std::pair<std::string, std::optional<std::string>*>>;
for (auto [file, target] : God {
{ "about.md", &info.m_details },
{ "changelog.md", &info.m_changelog },
{ "support.md", &info.m_supportInfo },
}) {
for (auto& [file, target] : getSpecialFiles()) {
if (unzip.fileExists(file)) {
unsigned long readSize = 0;
auto fileData = unzip.getFileData(file, &readSize);
if (!fileData || !readSize) {
return Err("Unable to read \"" + path.string() + "\"/" + file);
return Err("Unable to read \"" + file + "\"");
} else {
*target = sanitizeDetailsData(fileData, fileData + readSize);
*target = sanitizeDetailsData(
std::string(fileData, fileData + readSize)
);
}
}
}
return Ok(info);
return Ok();
}
Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) {
// unzip known MD files
for (auto& [file, target] : getSpecialFiles()) {
if (ghc::filesystem::exists(dir / file)) {
auto data = file::readString(dir / file);
if (!data) {
return Err("Unable to read \"" + file + "\": " + data.error());
}
*target = sanitizeDetailsData(data.value());
}
}
return Ok();
}
std::vector<std::pair<
std::string,
std::optional<std::string>*
>> ModInfo::getSpecialFiles() {
return {
{ "about.md", &m_details },
{ "changelog.md", &m_changelog },
{ "support.md", &m_supportInfo },
};
}

View file

@ -8,7 +8,7 @@ std::string g_lastError = "";
void geode_mod_log(void* cmod, const char* message) {
auto mod = reinterpret_cast<Mod*>(cmod);
log::log(Severity::Debug, mod, message);
log::log(Severity::Debug, mod, "{}", message);
}
bool geode_mod_add_hook(void* cmod, void* address, void* detour) {

View file

@ -2,7 +2,7 @@
#ifdef GEODE_IS_IOS
#include <Geode/Geode.hpp>
#include <Geode/loader/Mod.hpp>
#include <dlfcn.h>
USE_GEODE_NAMESPACE();

View file

@ -41,13 +41,19 @@ Result<Mod*> Loader::loadModFromFile(std::string const& path) {
auto sett = mod->loadSettings();
if (!sett) {
log::log(Severity::Error, mod, sett.error());
log::log(Severity::Error, mod, "{}", sett.error());
}
// enable mod if needed
mod->m_enabled = Loader::get()->shouldLoadMod(mod->m_info.m_id);
this->m_mods.insert({ res.value().m_id, mod });
m_mods.insert({ res.value().m_id, mod });
mod->updateDependencyStates();
return Ok<>(mod);
// add mod resources
this->queueInGDThread([this, mod]() {
this->updateModResourcePaths(mod);
this->updateModResources(mod);
});
return Ok(mod);
}

View file

@ -2,7 +2,7 @@
#ifdef GEODE_IS_WINDOWS
#include <Geode/Geode.hpp>
#include <Geode/loader/Mod.hpp>
USE_GEODE_NAMESPACE();
template<typename T>

View file

@ -1,4 +1,4 @@
#include <Geode/Geode.hpp>
#include <Geode/utils/string.hpp>
USE_GEODE_NAMESPACE();

View file

@ -1,4 +1,5 @@
#include "HookListLayer.hpp"
#include <Geode/binding/GJListLayer.hpp>
bool HookListLayer::init(Mod* mod) {
if (!GJDropDownLayer::init("Hooks", 220.f))

View file

@ -1,6 +1,7 @@
#pragma once
#include "HookListView.hpp"
#include <Geode/binding/GJDropDownLayer.hpp>
class HookListLayer : public GJDropDownLayer {
protected:

Some files were not shown because too many files have changed in this diff Show more