├── .clang-format ├── .clang-tidy ├── .clangd ├── .github ├── actions │ └── canary-ndk │ │ └── action.yml └── workflows │ ├── build-ndk.yml │ └── publish.yml ├── .gitignore ├── .idea └── cmake.xml ├── .vscode ├── c_cpp_properties.json └── settings.json ├── CMakeLists.txt ├── README.md ├── build.ps1 ├── copy.ps1 ├── copy.sh ├── cover.png ├── createqmod.ps1 ├── include ├── Animation │ └── Events.h └── THooks.h ├── mod.template.json ├── ndk-stack.ps1 ├── qpm.json ├── qpm.shared.json ├── shared ├── Animation │ ├── Animation.h │ ├── Easings.h │ ├── GameObjectTrackController.hpp │ ├── PointDefinition.h │ └── Track.h ├── AssociatedData.h ├── Hash.h ├── Json.h ├── StaticHolders.hpp ├── TLogger.h ├── TimeSourceHelper.h ├── Vector.h ├── bindings.h └── sv │ └── small_vector.h ├── src ├── Animation │ ├── Animation.cpp │ ├── Easings.cpp │ ├── Events.cpp │ ├── GameObjectTrackController.cpp │ ├── PointDefinition.cpp │ └── Track.cpp ├── AssociatedData.cpp ├── Hooks │ ├── BaseProviderHooks.cpp │ ├── BeatmapDataTransformHelper.cpp │ ├── BeatmapObjectCallbackController.cpp │ └── UnitySceneChange.cpp ├── TimeSourceHelper.cpp └── main.cpp └── tracks_rs_link ├── .cargo └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── rust-toolchain.toml └── src └── lib.rs /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | 3 | #Stolen from scad 4 | AllowShortBlocksOnASingleLine: false 5 | AllowShortFunctionsOnASingleLine: Empty 6 | AllowShortIfStatementsOnASingleLine: true 7 | CommentPragmas: NOLINT:.* 8 | DerivePointerAlignment: false 9 | IncludeBlocks: Preserve 10 | PointerAlignment: Left 11 | UseTab: Never 12 | Cpp11BracedListStyle: false 13 | QualifierAlignment: Right 14 | SortIncludes: Never 15 | ColumnLimit: 120 16 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: "*,-llvm*, 2 | -google-readability-namespace, 3 | -llvmlib, 4 | -llvmlibc, 5 | -fuchsi, 6 | -fuchsia, 7 | -alter, 8 | -modernize-use-trailing-return-type, 9 | -modernize-use-trailing-return, 10 | -readability-avoid-const-params-in, 11 | -cppcoreguidelines-macro-usage, 12 | -readability-identifier-length, 13 | -google-readability-todo, 14 | -fuchsia-default-arguments-calls, 15 | -altera-unroll-loops, 16 | -altera*, 17 | -readability-implicit-bool-conversion, 18 | -cert-err58-cpp, 19 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 20 | -hicpp-avoid-c-arrays, 21 | 22 | -bugprone-undefined-memory-manipulation, 23 | -misc-use-anonymous-namespace, 24 | -readability-magic-numbers, 25 | -cppcoreguidelines-pro-type-reinterpret-cast, 26 | -cppcoreguidelines-virtual-class-destructor, 27 | -cppcoreguidelines-pro-type-union-access, 28 | -cppcoreguidelines-pro-type-vararg, 29 | -cppcoreguidelines-pro-type-static-cast-downcast, 30 | -misc-non-private-member-variables-in-classes, 31 | -fuchsia-statically-constructed-objects, 32 | -bugprone-easily-swappable-parameters, 33 | -cppcoreguidelines-avoid-magic-numbers, 34 | -hicpp-vararg, 35 | -cppcoreguidelines-pro-bounds-constant-array-index, 36 | -google-build-using-namespace, 37 | -abseil-string-find-str-contains, 38 | clang-diagnostic-*, 39 | clang-analyzer-*, 40 | -bugprone-unchecked-optional-access, 41 | -llvmlibc-callee-namespace, 42 | -readability-identifier-length, 43 | -llvmlibc-implementation-in-namespace, 44 | -llvmlibc-restrict-system-libc-headers, 45 | -llvm-namespace-comment, 46 | -llvm-include-order, 47 | -altera-*, 48 | -fuchsia-*, 49 | -modernize-use-trailing-return-type, 50 | -cppcoreguidelines-avoid-non-const-global-variables, 51 | -llvm-header-guard, 52 | -cppcoreguidelines-macro-usage, 53 | -cppcoreguidelines-pro-type-vararg, 54 | -cppcoreguidelines-avoid-do-while, 55 | -hicpp-vararg, 56 | -concurrency-mt-unsafe, 57 | -abseil-* 58 | " 59 | FormatStyle: file 60 | HeaderFilterRegex: "" 61 | AnalyzeTemporaryDtors: false 62 | 63 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | Diagnostics: 2 | UnusedIncludes: None 3 | 4 | If: 5 | # Note: This is a regexp, notice '.*' at the end of PathMatch string. 6 | PathMatch: ./extern/.* 7 | Index: 8 | # Disable slow background indexing of these files. 9 | Background: Skip -------------------------------------------------------------------------------- /.github/actions/canary-ndk/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup canary ndk" 2 | description: "Sets up canary ndk" 3 | outputs: 4 | ndk-path: 5 | value: ${{ steps.path.outputs.path }} 6 | description: "Output path of the ndk" 7 | cache-hit: 8 | value: ${{ steps.cache.outputs.cache-hit }} 9 | description: "Whether a cache hit occurred for the ndk" 10 | runs: 11 | using: "composite" 12 | steps: 13 | - name: Get Home dir 14 | id: home-dir 15 | run: echo "path=${HOME}" >> ${GITHUB_OUTPUT} 16 | shell: bash 17 | 18 | - name: NDK cache 19 | id: cache 20 | uses: actions/cache@v3 21 | with: 22 | path: ${{ steps.home-dir.outputs.path }}/android-ndk-r27-canary/ 23 | key: ${{ runner.os }}-ndk-r27-canary 24 | 25 | - name: Download canary ndk 26 | if: ${{ !steps.cache.outputs.cache-hit }} 27 | env: 28 | CANARY_URL: https://github.com/QuestPackageManager/ndk-canary-archive/releases/download/27.0.1/android-ndk-10883340-linux-x86_64.zip 29 | run: wget ${CANARY_URL} -O ${HOME}/ndk.zip 30 | shell: bash 31 | 32 | - name: Unzip ndk 33 | if: ${{ !steps.cache.outputs.cache-hit }} 34 | run: 7z x "${HOME}/ndk.zip" -o"${HOME}/" 35 | shell: bash 36 | 37 | - name: Set output 38 | id: path 39 | shell: bash 40 | run: echo "path=${HOME}/android-ndk-r27-canary" >> ${GITHUB_OUTPUT} -------------------------------------------------------------------------------- /.github/workflows/build-ndk.yml: -------------------------------------------------------------------------------- 1 | name: NDK build 2 | 3 | env: 4 | module_id: tracks 5 | qmodName: Tracks 6 | cache-name: tracks_cache 7 | 8 | on: 9 | push: 10 | branches: [ master ] 11 | pull_request: 12 | branches: [ master ] 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | name: Checkout 21 | with: 22 | submodules: true 23 | lfs: true 24 | 25 | - uses: seanmiddleditch/gha-setup-ninja@v3 26 | 27 | # - name: Create ndkpath.txt 28 | # run: | 29 | # echo "$ANDROID_NDK_LATEST_HOME" > ${GITHUB_WORKSPACE}/ndkpath.txt 30 | # cat ${GITHUB_WORKSPACE}/ndkpath.txt 31 | 32 | - name: QPM Rust Action 33 | uses: Fernthedev/qpm-rust-action@v1 34 | with: 35 | #required 36 | workflow_token: ${{secrets.GITHUB_TOKEN}} 37 | 38 | restore: true # will run restore on download 39 | cache: true #will cache dependencies 40 | 41 | # Name of qmod in release asset. Assumes exists, same as prior 42 | qpm_qmod: ${{env.qmodName}}.qmod 43 | 44 | - name: QPM Collapse 45 | run: | 46 | qpm-rust collapse 47 | 48 | - name: Setup Rust 49 | run: | 50 | rustup install nightly 51 | rustup target add aarch64-linux-android --toolchain nightly 52 | cargo install --locked --git https://github.com/bbqsrc/cargo-ndk.git cargo-ndk 53 | 54 | - name: Build 55 | run: | 56 | cd ${GITHUB_WORKSPACE} 57 | qpm-rust s build 58 | qpm-rust qmod zip 59 | 60 | - name: Get Library Name 61 | id: libname 62 | run: | 63 | cd ./build/ 64 | pattern="lib${module_id}*.so" 65 | files=( $pattern ) 66 | echo ::set-output name=NAME::"${files[0]}" 67 | 68 | - name: Upload non-debug artifact 69 | uses: actions/upload-artifact@v4 70 | with: 71 | name: ${{ steps.libname.outputs.NAME }} 72 | path: ./build/${{ steps.libname.outputs.NAME }} 73 | if-no-files-found: error 74 | 75 | - name: Upload qmod artifact 76 | uses: actions/upload-artifact@v4 77 | with: 78 | name: ${{env.qmodName}}.qmod 79 | path: ./${{ env.qmodName }}.qmod 80 | if-no-files-found: error -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish QPM Package 2 | 3 | env: 4 | module_id: tracks 5 | qmodName: Tracks 6 | cache-name: tracks_cache 7 | 8 | on: 9 | push: 10 | tags: 11 | - 'v*' 12 | 13 | jobs: 14 | publish: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | name: Checkout 20 | with: 21 | submodules: true 22 | lfs: true 23 | 24 | - uses: seanmiddleditch/gha-setup-ninja@v3 25 | 26 | # - name: Create ndkpath.txt 27 | # run: | 28 | # echo "$ANDROID_NDK_LATEST_HOME" > ${GITHUB_WORKSPACE}/ndkpath.txt 29 | # cat ${GITHUB_WORKSPACE}/ndkpath.txt 30 | 31 | - name: Get Tag Version 32 | id: get_tag_version 33 | run: | 34 | echo ${GITHUB_REF#refs/tags/} 35 | echo ::set-output name=TAG::${GITHUB_REF#refs/tags/} 36 | echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} 37 | 38 | - name: QPM Rust Action 39 | uses: Fernthedev/qpm-action@v1 40 | with: 41 | #required 42 | workflow_token: ${{secrets.GITHUB_TOKEN}} 43 | 44 | restore: true # will run restore on download 45 | cache: true #will cache dependencies 46 | 47 | publish: "late" 48 | publish_token: ${{secrets.QPM_TOKEN}} 49 | 50 | version: ${{ steps.get_tag_version.outputs.VERSION }} 51 | tag: ${{ steps.get_tag_version.outputs.TAG }} 52 | 53 | # set to true if applicable, ASSUMES the file is already a release asset 54 | qpm_release_bin: true 55 | qpm_debug_bin: true 56 | 57 | # Name of qmod in release asset. Assumes exists, same as prior 58 | qpm_qmod: ${{env.qmodName}}.qmod 59 | 60 | - name: Setup Rust 61 | run: | 62 | rustup install nightly 63 | rustup target add aarch64-linux-android --toolchain nightly 64 | cargo install --locked --git https://github.com/bbqsrc/cargo-ndk.git cargo-ndk 65 | 66 | - name: Build 67 | run: | 68 | cd ${GITHUB_WORKSPACE} 69 | qpm s build 70 | qpm qmod zip 71 | 72 | - name: Get Library Name 73 | id: libname 74 | run: | 75 | cd ./build/ 76 | pattern="lib${module_id}*.so" 77 | files=( $pattern ) 78 | echo ::set-output name=NAME::"${files[0]}" 79 | 80 | - name: Rename debug 81 | run: | 82 | mv ./build/debug/${{ steps.libname.outputs.NAME }} ./build/debug/debug_${{ steps.libname.outputs.NAME }} 83 | 84 | 85 | - name: Upload to Release 86 | id: upload_file_release 87 | uses: softprops/action-gh-release@v0.1.15 88 | with: 89 | name: ${{ github.event.inputs.release_msg }} 90 | tag_name: ${{ github.event.inputs.version }} 91 | files: | 92 | ./${{ env.qmodName }}.qmod 93 | ./build/${{ steps.libname.outputs.NAME }} 94 | ./build/debug/debug_${{ steps.libname.outputs.NAME }} 95 | env: 96 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 97 | 98 | - name: Make PR to QeatMods3 99 | id: qmod-release 100 | uses: QuestPackageManager/qmod-repo-publish-action@main 101 | # TODO: Make this work! 102 | continue-on-error: true 103 | with: 104 | token: ${{secrets.GITHUB_TOKEN}} 105 | # first asset URL 106 | qmod_url: ${{ fromJSON(steps.upload_file_release.outputs.assets)[0].browser_download_url }} 107 | qmod_repo_owner: 'dantheman827' 108 | qmod_repo_name: 'bsqmods' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | debug_scripts* 3 | .idea* 4 | .cache 5 | 6 | # Build files 7 | *.so 8 | obj/ 9 | libs/ 10 | ndkpath.txt 11 | *.qmod 12 | QPM/ 13 | ninja-build/ 14 | build/ 15 | *.cmake 16 | 17 | # QPM 18 | extern/ 19 | *.backup 20 | 21 | # Logs 22 | *.log 23 | 24 | profiling/* -------------------------------------------------------------------------------- /.idea/cmake.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "defines": [ 5 | "__GNUC__", 6 | "__aarch64__", 7 | "VERSION=\"0.0.0\"" 8 | ], 9 | "includePath": [ 10 | "${workspaceFolder}/", 11 | "${workspaceFolder}/include", 12 | "${workspaceFolder}/shared", 13 | "${workspaceFolder}/extern", 14 | "${workspaceFolder}/extern/codegen/include", 15 | "${workspaceFolder}/extern/libil2cpp/il2cpp/libil2cpp", 16 | "${workspaceFolder}/../../android-ndk-r22b/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include", 17 | "${workspaceFolder}/../../android-ndk-r22b/sources/cxx-stl/llvm-libc++/include", 18 | "${workspaceFolder}/../../android-ndk-r22b/sources/cxx-stl/llvm-libc++abi/include" 19 | ], 20 | "name": "Quest", 21 | "compilerPath": "${workspaceFolder}/../../android-ndk-r22b/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++", 22 | "cStandard": "gnu17", 23 | "cppStandard": "gnu++20", 24 | "intelliSenseMode": "clang-x64", 25 | "configurationProvider": "ms-vscode.cmake-tools" 26 | } 27 | ], 28 | "version": 4 29 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "cmath": "cpp", 4 | "complex": "cpp", 5 | "valarray": "cpp", 6 | "string_view": "cpp", 7 | "__config": "cpp", 8 | "__nullptr": "cpp", 9 | "array": "cpp", 10 | "atomic": "cpp", 11 | "*.tcc": "cpp", 12 | "bitset": "cpp", 13 | "cctype": "cpp", 14 | "chrono": "cpp", 15 | "cinttypes": "cpp", 16 | "clocale": "cpp", 17 | "codecvt": "cpp", 18 | "condition_variable": "cpp", 19 | "cstdarg": "cpp", 20 | "cstddef": "cpp", 21 | "cstdint": "cpp", 22 | "cstdio": "cpp", 23 | "cstdlib": "cpp", 24 | "cstring": "cpp", 25 | "ctime": "cpp", 26 | "cwchar": "cpp", 27 | "cwctype": "cpp", 28 | "deque": "cpp", 29 | "forward_list": "cpp", 30 | "list": "cpp", 31 | "unordered_map": "cpp", 32 | "unordered_set": "cpp", 33 | "vector": "cpp", 34 | "exception": "cpp", 35 | "algorithm": "cpp", 36 | "functional": "cpp", 37 | "iterator": "cpp", 38 | "map": "cpp", 39 | "memory": "cpp", 40 | "memory_resource": "cpp", 41 | "numeric": "cpp", 42 | "optional": "cpp", 43 | "random": "cpp", 44 | "ratio": "cpp", 45 | "regex": "cpp", 46 | "set": "cpp", 47 | "string": "cpp", 48 | "system_error": "cpp", 49 | "tuple": "cpp", 50 | "type_traits": "cpp", 51 | "utility": "cpp", 52 | "fstream": "cpp", 53 | "initializer_list": "cpp", 54 | "iomanip": "cpp", 55 | "iosfwd": "cpp", 56 | "iostream": "cpp", 57 | "istream": "cpp", 58 | "limits": "cpp", 59 | "mutex": "cpp", 60 | "new": "cpp", 61 | "ostream": "cpp", 62 | "shared_mutex": "cpp", 63 | "sstream": "cpp", 64 | "stdexcept": "cpp", 65 | "streambuf": "cpp", 66 | "thread": "cpp", 67 | "cfenv": "cpp", 68 | "typeinfo": "cpp", 69 | "__bit_reference": "cpp", 70 | "__debug": "cpp", 71 | "__errc": "cpp", 72 | "__functional_base": "cpp", 73 | "__hash_table": "cpp", 74 | "__locale": "cpp", 75 | "__mutex_base": "cpp", 76 | "__node_handle": "cpp", 77 | "__split_buffer": "cpp", 78 | "__string": "cpp", 79 | "__threading_support": "cpp", 80 | "__tree": "cpp", 81 | "__tuple": "cpp", 82 | "bit": "cpp", 83 | "compare": "cpp", 84 | "ios": "cpp", 85 | "locale": "cpp", 86 | "queue": "cpp", 87 | "stack": "cpp", 88 | "ranges": "cpp", 89 | "variant": "cpp", 90 | "hash_map": "cpp", 91 | "concepts": "cpp", 92 | "stop_token": "cpp", 93 | "__memory": "cpp", 94 | "filesystem": "cpp", 95 | "*.inc": "cpp", 96 | "span": "cpp", 97 | "csignal": "cpp", 98 | "__functional_base_03": "cpp", 99 | "any": "cpp", 100 | "coroutine": "cpp", 101 | "semaphore": "cpp", 102 | "numbers": "cpp", 103 | "*.def": "cpp", 104 | "__functional_03": "cpp" 105 | } 106 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | # include some defines automatically made by qpm 4 | include(qpm_defines.cmake) 5 | 6 | project(${COMPILE_ID}) 7 | 8 | 9 | # c++ standard 10 | set(CMAKE_CXX_STANDARD 20) 11 | set(CMAKE_CXX_STANDARD_REQUIRED 20) 12 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 13 | set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) 14 | 15 | # define that stores the actual source directory 16 | set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) 17 | set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) 18 | 19 | # compile options used 20 | add_compile_options(-frtti -fexceptions) 21 | 22 | add_compile_options(-O3) 23 | 24 | # compile definitions used 25 | add_compile_definitions(VERSION=\"${MOD_VERSION}\") 26 | add_compile_definitions(MOD_ID=\"${MOD_ID}\") 27 | add_compile_definitions(USE_CODEGEN_FIELDS) 28 | 29 | # recursively get all src files 30 | RECURSE_FILES(cpp_file_list ${SOURCE_DIR}/*.cpp) 31 | RECURSE_FILES(c_file_list ${SOURCE_DIR}/*.c) 32 | 33 | 34 | RECURSE_FILES(inline_hook_c ${EXTERN_DIR}/includes/beatsaber-hook/shared/inline-hook/*.c) 35 | RECURSE_FILES(inline_hook_cpp ${EXTERN_DIR}/includes/beatsaber-hook/shared/inline-hook/*.cpp) 36 | 37 | # add all src files to compile 38 | add_library( 39 | ${COMPILE_ID} 40 | SHARED 41 | ${cpp_file_list} 42 | ${c_file_list} 43 | ${inline_hook_c} 44 | ${inline_hook_cpp} 45 | tracks_rs_link/target/aarch64-linux-android/release/libtracks_rs_link.a 46 | ) 47 | 48 | # add src dir as include dir 49 | target_include_directories(${COMPILE_ID} PRIVATE ${SOURCE_DIR}) 50 | 51 | # add include dir as include dir 52 | target_include_directories(${COMPILE_ID} PRIVATE ${INCLUDE_DIR}) 53 | 54 | # add shared dir as include dir 55 | target_include_directories(${COMPILE_ID} PUBLIC ${SHARED_DIR}) 56 | 57 | # codegen includes 58 | target_include_directories(${COMPILE_ID} PRIVATE ${EXTERN_DIR}/includes/${CODEGEN_ID}/include) 59 | 60 | target_link_libraries(${COMPILE_ID} PRIVATE -llog) 61 | target_link_directories(${COMPILE_ID} PUBLIC tracks_rs_link/target/aarch64-linux-android/release) 62 | target_link_libraries(${COMPILE_ID} PUBLIC tracks_rs_link) 63 | 64 | # add extern stuff like libs and other includes 65 | include(extern.cmake) 66 | 67 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 68 | COMMAND ${CMAKE_STRIP} -d --strip-all 69 | "lib${COMPILE_ID}.so" -o "stripped_lib${COMPILE_ID}.so" 70 | COMMENT "Strip debug symbols done on final binary.") 71 | 72 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 73 | COMMAND ${CMAKE_COMMAND} -E make_directory debug 74 | COMMENT "Rename the lib to debug_ since it has debug symbols" 75 | ) 76 | 77 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 78 | COMMAND ${CMAKE_COMMAND} -E rename lib${COMPILE_ID}.so debug/lib${COMPILE_ID}.so 79 | COMMENT "Rename the lib to debug_ since it has debug symbols" 80 | ) 81 | 82 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 83 | COMMAND ${CMAKE_COMMAND} -E rename stripped_lib${COMPILE_ID}.so lib${COMPILE_ID}.so 84 | COMMENT "Rename the stripped lib to regular" 85 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tracks 2 | Heck but not Heck because it's not Heck but cooler than Heck 3 | heck 4 | 5 | # Libraries 6 | Uses small buffer optimization for vector: [https://github.com/KonanM/small_vector/blob/master/include/small_vector/small_vector.h](https://github.com/KonanM/small_vector/blob/master/include/small_vector/small_vector.h) -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory=$false)] 3 | [Switch]$clean 4 | ) 5 | 6 | # if user specified clean, remove all build files 7 | if ($clean.IsPresent) 8 | { 9 | if (Test-Path -Path "build") 10 | { 11 | remove-item build -R 12 | } 13 | } 14 | 15 | $NDKPath = Get-Content $PSScriptRoot/ndkpath.txt 16 | 17 | if (($clean.IsPresent) -or (-not (Test-Path -Path "build"))) 18 | { 19 | $out = new-item -Path build -ItemType Directory 20 | } 21 | 22 | # build the rust code 23 | cd ./tracks_rs_link 24 | cargo ndk --bindgen --no-strip -t arm64-v8a -o build build --release 25 | 26 | cd ../build 27 | & cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" ../ 28 | & cmake --build . 29 | cd .. -------------------------------------------------------------------------------- /copy.ps1: -------------------------------------------------------------------------------- 1 | & $PSScriptRoot/build.ps1 2 | if ($?) { 3 | adb push build/libtracks.so /sdcard/ModData/com.beatgames.beatsaber/Modloader/mods/libtracks.so 4 | if ($?) { 5 | adb shell am force-stop com.beatgames.beatsaber 6 | adb shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity 7 | # adb logcat -c; adb logcat > test.log 8 | if ($args[0] -eq "--log") { 9 | $timestamp = Get-Date -Format "MM-dd HH:mm:ss.fff" 10 | # adb logcat -c 11 | # adb logcat -T "$timestamp" main-modloader:W QuestHook[Tracks`|v0.1.0]:* QuestHook[UtilsLogger`|v1.0.12]:* AndroidRuntime:E *:S 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NDKPath=`cat ndkpath.txt` 4 | 5 | buildScript="$NDKPath/build/ndk-build" 6 | 7 | ./$buildScript NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk -j8 8 | adb push libs/arm64-v8a/libtracks.so /sdcard/Android/data/com.beatgames.beatsaber/files/mods/libtracks.so 9 | adb shell am force-stop com.beatgames.beatsaber 10 | adb shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity 11 | 12 | adb logcat -c && adb logcat > test.log -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StackDoubleFlow/Tracks/642ec8fa2230a8dfaedaaeb3d9fa402582042f7c/cover.png -------------------------------------------------------------------------------- /createqmod.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [String]$qmodname="", 3 | [Parameter(Mandatory=$false)] 4 | [Switch]$clean 5 | ) 6 | 7 | if ($qmodName -eq "") 8 | { 9 | echo "Give a proper qmod name and try again" 10 | exit 11 | } 12 | $mod = "./mod.json" 13 | $modJson = Get-Content $mod -Raw | ConvertFrom-Json 14 | 15 | $filelist = @($mod) 16 | 17 | $cover = "./" + $modJson.coverImage 18 | if ((-not ($cover -eq "./")) -and (Test-Path $cover)) 19 | { 20 | $filelist += ,$cover 21 | } 22 | 23 | foreach ($mod in $modJson.modFiles) 24 | { 25 | $path = "./build/" + $mod 26 | if (-not (Test-Path $path)) 27 | { 28 | $path = "./extern/libs/" + $mod 29 | } 30 | $filelist += $path 31 | } 32 | 33 | foreach ($lib in $modJson.libraryFiles) 34 | { 35 | $path = "./extern/libs/" + $lib 36 | if (-not (Test-Path $path)) 37 | { 38 | $path = "./build/" + $lib 39 | } 40 | $filelist += $path 41 | } 42 | 43 | $zip = $qmodName + ".zip" 44 | $qmod = $qmodName + ".qmod" 45 | 46 | if ((-not ($clean.IsPresent)) -and (Test-Path $qmod)) 47 | { 48 | echo "Making Clean Qmod" 49 | Move-Item $qmod $zip -Force 50 | } 51 | 52 | Compress-Archive -Path $filelist -DestinationPath $zip -Update 53 | Move-Item $zip $qmod -Force -------------------------------------------------------------------------------- /include/Animation/Events.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Animation/PointDefinition.h" 3 | #include "Animation/Track.h" 4 | 5 | namespace GlobalNamespace { 6 | class BeatmapCallbacksController; 7 | } 8 | 9 | namespace Events { 10 | 11 | void AddEventCallbacks(); 12 | void UpdateCoroutines(GlobalNamespace::BeatmapCallbacksController* callbackController); 13 | 14 | struct AnimateTrackContext { 15 | PointDefinitionW points; 16 | PropertyW property; 17 | float duration; 18 | Functions easing; 19 | float startTime; 20 | int repeat; 21 | 22 | constexpr AnimateTrackContext(PointDefinitionW points, PropertyW aProperty, float duration, float startTime, 23 | Functions easing, int repeat) 24 | : points(points), property(aProperty), duration(duration), startTime(startTime), easing(easing), repeat(repeat) {} 25 | 26 | }; 27 | 28 | struct AssignPathAnimationContext { 29 | PathPropertyW property; 30 | float duration; 31 | Functions easing; 32 | float startTime; 33 | int repeat; 34 | 35 | constexpr AssignPathAnimationContext(PathPropertyW aProperty, float duration, float startTime, Functions easing, 36 | int repeat) 37 | : property(aProperty), duration(duration), startTime(startTime), easing(easing), repeat(repeat) {} 38 | }; 39 | 40 | } // end namespace Events -------------------------------------------------------------------------------- /include/THooks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TLogger.h" 3 | #include 4 | 5 | class Hooks { 6 | private: 7 | static inline std::vector installFuncs; 8 | 9 | public: 10 | static inline void AddInstallFunc(void (*installFunc)()) { 11 | installFuncs.push_back(installFunc); 12 | } 13 | 14 | static inline void InstallHooks() { 15 | for (auto installFunc : installFuncs) { 16 | installFunc(); 17 | } 18 | } 19 | }; 20 | 21 | #define TInstallHooks(func) \ 22 | struct __TRegister##func { \ 23 | __TRegister##func() { \ 24 | Hooks::AddInstallFunc(func); \ 25 | __android_log_print(ANDROID_LOG_DEBUG, "TInstallHooks", "Registered install func: " #func); \ 26 | } \ 27 | }; \ 28 | static __TRegister##func __TRegisterInstance##func; 29 | 30 | void InstallAndRegisterAll(); -------------------------------------------------------------------------------- /mod.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "_QPVersion": "0.1.1", 3 | "name": "Tracks", 4 | "id": "${mod_id}", 5 | "coverImage": "cover.png", 6 | "author": "StackDoubleFlow, Fernthedev", 7 | "version": "${version}", 8 | "packageId": "com.beatgames.beatsaber", 9 | "packageVersion": "1.40.4_5283", 10 | "description": "Animation for Noodle and Chroma. Requires CustomJSONData. Don't delete", 11 | "modFiles": [], 12 | "dependencies": [], 13 | "lateModFiles": ["${binary}"], 14 | "libraryFiles": [], 15 | "fileCopies": [], 16 | "copyExtensions": [] 17 | } 18 | -------------------------------------------------------------------------------- /ndk-stack.ps1: -------------------------------------------------------------------------------- 1 | if (Test-Path "./ndkpath.txt") 2 | { 3 | $NDKPath = Get-Content ./ndkpath.txt 4 | } else { 5 | $NDKPath = $ENV:ANDROID_NDK_HOME 6 | } 7 | 8 | $stackScript = "$NDKPath/ndk-stack" 9 | if (-not ($PSVersionTable.PSEdition -eq "Core")) { 10 | $stackScript += ".cmd" 11 | } 12 | 13 | 14 | Get-Content ./log.log | & $stackScript -sym ./build/debug/ > log_unstripped.log -------------------------------------------------------------------------------- /qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.4.0", 3 | "sharedDir": "shared", 4 | "dependenciesDir": "extern", 5 | "info": { 6 | "name": "Tracks", 7 | "id": "tracks", 8 | "version": "1.1.0", 9 | "url": "https://github.com/StackDoubleFlow/Tracks", 10 | "additionalData": { 11 | "overrideSoName": "libtracks.so", 12 | "cmake": true 13 | } 14 | }, 15 | "workspace": { 16 | "scripts": { 17 | "build": [ 18 | "pwsh ./build.ps1" 19 | ] 20 | }, 21 | "ndk": "^27.1.12297006", 22 | "qmodIncludeDirs": [ 23 | "./build", 24 | "./extern/libs" 25 | ], 26 | "qmodIncludeFiles": [], 27 | "qmodOutput": "Tracks.qmod" 28 | }, 29 | "dependencies": [ 30 | { 31 | "id": "beatsaber-hook", 32 | "versionRange": "^6.1.7", 33 | "additionalData": { 34 | "extraFiles": [] 35 | } 36 | }, 37 | { 38 | "id": "bs-cordl", 39 | "versionRange": "^4004.0.0", 40 | "additionalData": { 41 | "includeQmod": true 42 | } 43 | }, 44 | { 45 | "id": "custom-json-data", 46 | "versionRange": "^0.23.0", 47 | "additionalData": {} 48 | }, 49 | { 50 | "id": "sombrero", 51 | "versionRange": "^0.1.43", 52 | "additionalData": {} 53 | }, 54 | { 55 | "id": "scotland2", 56 | "versionRange": "^0.1.6", 57 | "additionalData": { 58 | "includeQmod": false 59 | } 60 | }, 61 | { 62 | "id": "custom-types", 63 | "versionRange": "^0.18.0", 64 | "additionalData": { 65 | "includeQmod": true 66 | } 67 | }, 68 | { 69 | "id": "songcore", 70 | "versionRange": "^1.1.20", 71 | "additionalData": {} 72 | }, 73 | { 74 | "id": "paper2_scotland2", 75 | "versionRange": "^4.6.1", 76 | "additionalData": {} 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /qpm.shared.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "version": "0.4.0", 4 | "sharedDir": "shared", 5 | "dependenciesDir": "extern", 6 | "info": { 7 | "name": "Tracks", 8 | "id": "tracks", 9 | "version": "1.1.0", 10 | "url": "https://github.com/StackDoubleFlow/Tracks", 11 | "additionalData": { 12 | "overrideSoName": "libtracks.so", 13 | "cmake": true 14 | } 15 | }, 16 | "workspace": { 17 | "scripts": { 18 | "build": [ 19 | "pwsh ./build.ps1" 20 | ] 21 | }, 22 | "ndk": "^27.1.12297006", 23 | "qmodIncludeDirs": [ 24 | "./build", 25 | "./extern/libs" 26 | ], 27 | "qmodIncludeFiles": [], 28 | "qmodOutput": "Tracks.qmod" 29 | }, 30 | "dependencies": [ 31 | { 32 | "id": "beatsaber-hook", 33 | "versionRange": "^6.1.7", 34 | "additionalData": { 35 | "extraFiles": [] 36 | } 37 | }, 38 | { 39 | "id": "bs-cordl", 40 | "versionRange": "^4004.0.0", 41 | "additionalData": { 42 | "includeQmod": true 43 | } 44 | }, 45 | { 46 | "id": "custom-json-data", 47 | "versionRange": "^0.23.0", 48 | "additionalData": {} 49 | }, 50 | { 51 | "id": "sombrero", 52 | "versionRange": "^0.1.43", 53 | "additionalData": {} 54 | }, 55 | { 56 | "id": "scotland2", 57 | "versionRange": "^0.1.6", 58 | "additionalData": { 59 | "includeQmod": false 60 | } 61 | }, 62 | { 63 | "id": "custom-types", 64 | "versionRange": "^0.18.0", 65 | "additionalData": { 66 | "includeQmod": true 67 | } 68 | }, 69 | { 70 | "id": "songcore", 71 | "versionRange": "^1.1.20", 72 | "additionalData": {} 73 | }, 74 | { 75 | "id": "paper2_scotland2", 76 | "versionRange": "^4.6.1", 77 | "additionalData": {} 78 | } 79 | ] 80 | }, 81 | "restoredDependencies": [ 82 | { 83 | "dependency": { 84 | "id": "paper2_scotland2", 85 | "versionRange": "=4.6.2", 86 | "additionalData": { 87 | "soLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.2/libpaper2_scotland2.so", 88 | "overrideSoName": "libpaper2_scotland2.so", 89 | "modLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.2/paper2_scotland2.qmod", 90 | "branchName": "version/v4_6_2", 91 | "compileOptions": { 92 | "systemIncludes": [ 93 | "shared/utfcpp/source" 94 | ] 95 | }, 96 | "cmake": false 97 | } 98 | }, 99 | "version": "4.6.2" 100 | }, 101 | { 102 | "dependency": { 103 | "id": "libil2cpp", 104 | "versionRange": "=0.4.0", 105 | "additionalData": { 106 | "headersOnly": true, 107 | "compileOptions": { 108 | "systemIncludes": [ 109 | "il2cpp/external/baselib/Include", 110 | "il2cpp/external/baselib/Platforms/Android/Include" 111 | ] 112 | } 113 | } 114 | }, 115 | "version": "0.4.0" 116 | }, 117 | { 118 | "dependency": { 119 | "id": "custom-types", 120 | "versionRange": "=0.18.2", 121 | "additionalData": { 122 | "soLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/libcustom-types.so", 123 | "debugSoLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/debug_libcustom-types.so", 124 | "overrideSoName": "libcustom-types.so", 125 | "modLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/CustomTypes.qmod", 126 | "branchName": "version/v0_18_2", 127 | "compileOptions": { 128 | "cppFlags": [ 129 | "-Wno-invalid-offsetof" 130 | ] 131 | }, 132 | "cmake": true 133 | } 134 | }, 135 | "version": "0.18.2" 136 | }, 137 | { 138 | "dependency": { 139 | "id": "sombrero", 140 | "versionRange": "=0.1.43", 141 | "additionalData": { 142 | "headersOnly": true, 143 | "branchName": "version/v0_1_43" 144 | } 145 | }, 146 | "version": "0.1.43" 147 | }, 148 | { 149 | "dependency": { 150 | "id": "custom-json-data", 151 | "versionRange": "=0.23.0", 152 | "additionalData": { 153 | "soLink": "https://github.com/StackDoubleFlow/CustomJSONData/releases/download/v0.23.0/libcustom-json-data.so", 154 | "debugSoLink": "https://github.com/StackDoubleFlow/CustomJSONData/releases/download/v0.23.0/debug_libcustom-json-data.so", 155 | "overrideSoName": "libcustom-json-data.so", 156 | "modLink": "https://github.com/StackDoubleFlow/CustomJSONData/releases/download/v0.23.0/custom-json-data.qmod", 157 | "branchName": "version/v0_23_0", 158 | "compileOptions": { 159 | "cppFlags": [ 160 | "-DRAPIDJSON_NEON" 161 | ] 162 | }, 163 | "cmake": true 164 | } 165 | }, 166 | "version": "0.23.0" 167 | }, 168 | { 169 | "dependency": { 170 | "id": "bs-cordl", 171 | "versionRange": "=4004.0.0", 172 | "additionalData": { 173 | "headersOnly": true, 174 | "branchName": "version/v4004_0_0", 175 | "compileOptions": { 176 | "includePaths": [ 177 | "include" 178 | ], 179 | "cppFeatures": [], 180 | "cppFlags": [ 181 | "-DNEED_UNSAFE_CSHARP", 182 | "-fdeclspec", 183 | "-DUNITY_2021", 184 | "-DHAS_CODEGEN", 185 | "-Wno-invalid-offsetof" 186 | ] 187 | } 188 | } 189 | }, 190 | "version": "4004.0.0" 191 | }, 192 | { 193 | "dependency": { 194 | "id": "songcore", 195 | "versionRange": "=1.1.20", 196 | "additionalData": { 197 | "soLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/libsongcore.so", 198 | "debugSoLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/debug_libsongcore.so", 199 | "overrideSoName": "libsongcore.so", 200 | "modLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/SongCore.qmod", 201 | "branchName": "version/v1_1_20", 202 | "cmake": true 203 | } 204 | }, 205 | "version": "1.1.20" 206 | }, 207 | { 208 | "dependency": { 209 | "id": "beatsaber-hook", 210 | "versionRange": "=6.4.1", 211 | "additionalData": { 212 | "soLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/libbeatsaber-hook.so", 213 | "debugSoLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/debug_libbeatsaber-hook.so", 214 | "overrideSoName": "libbeatsaber-hook.so", 215 | "modLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/beatsaber-hook.qmod", 216 | "branchName": "version/v6_4_1", 217 | "cmake": true 218 | } 219 | }, 220 | "version": "6.4.1" 221 | }, 222 | { 223 | "dependency": { 224 | "id": "scotland2", 225 | "versionRange": "=0.1.6", 226 | "additionalData": { 227 | "soLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.6/libsl2.so", 228 | "debugSoLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.6/debug_libsl2.so", 229 | "overrideSoName": "libsl2.so", 230 | "branchName": "version/v0_1_6" 231 | } 232 | }, 233 | "version": "0.1.6" 234 | }, 235 | { 236 | "dependency": { 237 | "id": "fmt", 238 | "versionRange": "=11.0.2", 239 | "additionalData": { 240 | "headersOnly": true, 241 | "branchName": "version/v11_0_2", 242 | "compileOptions": { 243 | "systemIncludes": [ 244 | "fmt/include/" 245 | ], 246 | "cppFlags": [ 247 | "-DFMT_HEADER_ONLY" 248 | ] 249 | } 250 | } 251 | }, 252 | "version": "11.0.2" 253 | } 254 | ] 255 | } 256 | -------------------------------------------------------------------------------- /shared/Animation/Animation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "PointDefinition.h" 3 | #include "Easings.h" 4 | #include "Track.h" 5 | #include "PointDefinition.h" 6 | 7 | #define TRACKS_LIST_OPERATE_MULTIPLE(target, list, op) \ 8 | if (!list.empty()) { \ 9 | target.emplace(); \ 10 | for (auto const& i : list) { \ 11 | target = *target op i; \ 12 | } \ 13 | } 14 | 15 | namespace GlobalNamespace { 16 | class BeatmapData; 17 | } 18 | 19 | namespace TracksAD { 20 | struct BeatmapAssociatedData; 21 | } 22 | 23 | namespace Animation { 24 | 25 | [[nodiscard]] 26 | static auto getCurrentTime() { 27 | return Tracks::ffi::get_time(); 28 | } 29 | 30 | PointDefinitionW TryGetPointData(TracksAD::BeatmapAssociatedData& beatmapAD, rapidjson::Value const& customData, 31 | std::string_view pointName, Tracks::ffi::WrapBaseValueType type); 32 | 33 | #pragma region track_utils 34 | 35 | [[nodiscard]] static constexpr std::optional 36 | MirrorVectorNullable(std::optional const& vector) { 37 | if (!vector) { 38 | return vector; 39 | } 40 | 41 | auto modifiedVector = *vector; 42 | modifiedVector.x *= -1; 43 | 44 | return modifiedVector; 45 | } 46 | 47 | [[nodiscard]] constexpr static std::optional 48 | MirrorQuaternionNullable(std::optional const& quaternion) { 49 | if (!quaternion) { 50 | return quaternion; 51 | } 52 | 53 | auto modifiedVector = *quaternion; 54 | 55 | return NEVector::Quaternion(modifiedVector.x, modifiedVector.y * -1, modifiedVector.z * -1, modifiedVector.w); 56 | } 57 | #pragma region property_utils 58 | 59 | // Conversion functions 60 | [[nodiscard]] constexpr static NEVector::Vector3 ToVector3(Tracks::ffi::WrapBaseValue const& val) { 61 | if (val.ty != Tracks::ffi::WrapBaseValueType::Vec3) return {}; 62 | return { val.value.vec3.x, val.value.vec3.y, val.value.vec3.z }; 63 | } 64 | 65 | [[nodiscard]] constexpr static NEVector::Vector4 ToVector4(Tracks::ffi::WrapBaseValue const& val) { 66 | if (val.ty != Tracks::ffi::WrapBaseValueType::Vec4) return {}; 67 | return { val.value.vec4.x, val.value.vec4.y, val.value.vec4.z, val.value.vec4.w }; 68 | } 69 | 70 | [[nodiscard]] constexpr static NEVector::Quaternion ToQuaternion(Tracks::ffi::WrapBaseValue const& val) { 71 | if (val.ty != Tracks::ffi::WrapBaseValueType::Quat) return {}; 72 | return { val.value.quat.x, val.value.quat.y, val.value.quat.z, val.value.quat.w }; 73 | } 74 | 75 | [[nodiscard]] constexpr static float ToFloat(Tracks::ffi::WrapBaseValue const& val) { 76 | if (val.ty != Tracks::ffi::WrapBaseValueType::Float) return {}; 77 | return val.value.float_v; 78 | } 79 | 80 | // Base versions that return WrapBaseValue 81 | [[nodiscard]] 82 | constexpr static std::vector getProperties(std::span tracks, 83 | PropertyNames name, TimeUnit time) { 84 | std::vector properties; 85 | properties.reserve(tracks.size()); 86 | for (auto const& track : tracks) { 87 | auto property = track.GetPropertyNamed(name); 88 | auto value = property.GetValue(); 89 | if (TimeUnit(value.last_updated) <= time) continue; 90 | if (!value.value.has_value) continue; 91 | properties.push_back(value.value.value); 92 | } 93 | 94 | return properties; 95 | } 96 | 97 | [[nodiscard]] 98 | constexpr static std::vector 99 | getPathProperties(std::span tracks, PropertyNames name, Tracks::ffi::BaseProviderContext* context, 100 | uint64_t time) { 101 | std::vector properties; 102 | bool last = false; 103 | 104 | properties.reserve(tracks.size()); 105 | for (auto const& track : tracks) { 106 | auto property = track.GetPathPropertyNamed(name); 107 | bool tempLast; 108 | auto value = property.Interpolate(time, tempLast, context); 109 | last = last && tempLast; 110 | properties.push_back(value); 111 | } 112 | 113 | return properties; 114 | } 115 | 116 | // Macro to generate type-specific property getters 117 | #define GENERATE_PROPERTY_GETTERS(ReturnType, Suffix, Conversion) \ 118 | [[nodiscard]] \ 119 | constexpr static std::vector getProperties##Suffix(std::span tracks, PropertyNames name, \ 120 | TimeUnit time) { \ 121 | std::vector properties; \ 122 | properties.reserve(tracks.size()); \ 123 | for (auto const& track : tracks) { \ 124 | auto property = track.GetPropertyNamed(name); \ 125 | auto value = property.GetValue(); \ 126 | if (!value.value.has_value) continue; \ 127 | if (TimeUnit(value.last_updated) <= time) continue; \ 128 | properties.push_back(Conversion(value.value.value)); \ 129 | } \ 130 | return properties; \ 131 | } \ 132 | \ 133 | [[nodiscard]] \ 134 | constexpr static std::vector getPathProperties##Suffix( \ 135 | std::span tracks, PropertyNames name, Tracks::ffi::BaseProviderContext* context, float time) { \ 136 | std::vector properties; \ 137 | bool last = false; \ 138 | properties.reserve(tracks.size()); \ 139 | for (auto const& track : tracks) { \ 140 | auto property = track.GetPathPropertyNamed(name); \ 141 | bool tempLast; \ 142 | auto value = property.Interpolate(time, tempLast, context); \ 143 | last = last && tempLast; \ 144 | properties.push_back(Conversion(value)); \ 145 | } \ 146 | return properties; \ 147 | } 148 | 149 | // Generate specialized versions for different types 150 | GENERATE_PROPERTY_GETTERS(NEVector::Vector3, Vec3, ToVector3) 151 | GENERATE_PROPERTY_GETTERS(NEVector::Vector4, Vec4, ToVector4) 152 | GENERATE_PROPERTY_GETTERS(NEVector::Quaternion, Quat, ToQuaternion) 153 | GENERATE_PROPERTY_GETTERS(float, Float, ToFloat) 154 | 155 | // Macro to generate addition functions for spans 156 | #define GENERATE_ADD_FUNCTIONS(Type, TypeName) \ 157 | [[nodiscard]] \ 158 | constexpr static Type add##TypeName##s(std::span values) { \ 159 | if (values.empty()) return (Type){}; \ 160 | Type result = values[0]; \ 161 | for (size_t i = 1; i < values.size(); ++i) { \ 162 | result = result + values[i]; \ 163 | } \ 164 | return result; \ 165 | } 166 | 167 | // Macro to generate multiplication functions for spans 168 | #define GENERATE_MUL_FUNCTIONS(Type, TypeName) \ 169 | [[nodiscard]] \ 170 | constexpr static Type multiply##TypeName##s(std::span values) { \ 171 | if (values.empty()) return (Type){}; \ 172 | Type result = values[0]; \ 173 | for (size_t i = 1; i < values.size(); ++i) { \ 174 | result = result * values[i]; \ 175 | } \ 176 | return result; \ 177 | } 178 | 179 | // Generate addition functions for different types 180 | GENERATE_ADD_FUNCTIONS(NEVector::Vector3, Vector3) 181 | [[nodiscard]] constexpr static NEVector ::Vector4 addVector4s(std ::span values) { 182 | if (values.empty()) return NEVector ::Vector4{}; 183 | NEVector ::Vector4 result = values[0]; 184 | for (size_t i = 1; i < values.size(); ++i) { 185 | result = result + values[i]; 186 | } 187 | return result; 188 | } 189 | // GENERATE_ADD_FUNCTIONS(NEVector::Vector4, Vector4) 190 | GENERATE_ADD_FUNCTIONS(float, Float) 191 | 192 | // Generate multiplication functions for different types 193 | GENERATE_MUL_FUNCTIONS(NEVector::Vector3, Vector3) 194 | GENERATE_MUL_FUNCTIONS(NEVector::Vector4, Vector4) 195 | GENERATE_MUL_FUNCTIONS(NEVector::Quaternion, Quaternion) 196 | GENERATE_MUL_FUNCTIONS(float, Float) 197 | 198 | 199 | #pragma endregion // property_utils 200 | 201 | } // namespace Animation -------------------------------------------------------------------------------- /shared/Animation/Easings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define fM_PI_2 float(M_PI_2) 4 | #define fM_PI float(M_PI) 5 | 6 | #include 7 | #include 8 | 9 | #include "../bindings.h" 10 | 11 | using Functions = Tracks::ffi::Functions; 12 | namespace Easings { 13 | 14 | constexpr static float EaseLinear(float p) { 15 | return p; 16 | } 17 | 18 | constexpr static float EaseStep(float p) { 19 | return std::floor(p); 20 | } 21 | 22 | constexpr static float EaseOutQuad(float p) { 23 | return -(p * (p - 2.0f)); 24 | } 25 | 26 | constexpr static float EaseInQuad(float p) { 27 | return p * p; 28 | } 29 | 30 | constexpr static float EaseInOutQuad(float p) { 31 | if (p < 0.5f) { 32 | return 2.0f * p * p; 33 | } else { 34 | return (-2.0f * p * p) + (4.0f * p) - 1.0f; 35 | } 36 | } 37 | 38 | constexpr static float EaseInCubic(float p) { 39 | return p * p * p; 40 | } 41 | 42 | constexpr static float EaseOutCubic(float p) { 43 | float f = p - 1.0f; 44 | return (f * f * f) + 1.0f; 45 | } 46 | 47 | constexpr static float EaseInOutCubic(float p) { 48 | if (p < 0.5f) { 49 | return 4.0f * p * p * p; 50 | } else { 51 | float f = (2.0f * p) - 2.0f; 52 | return (0.5f * f * f * f) + 1.0f; 53 | } 54 | } 55 | 56 | constexpr static float EaseInQuart(float p) { 57 | return p * p * p * p; 58 | } 59 | 60 | constexpr static float EaseOutQuart(float p) { 61 | float f = p - 1.0f; 62 | return (f * f * f * (1.0f - p)) + 1.0f; 63 | } 64 | 65 | constexpr static float EaseInOutQuart(float p) { 66 | if (p < 0.5f) { 67 | return 8.0f * p * p * p * p; 68 | } else { 69 | float f = p - 1; 70 | return (-8.0f * f * f * f * f) + 1.0f; 71 | } 72 | } 73 | 74 | constexpr static float EaseInQuint(float p) { 75 | return p * p * p * p * p; 76 | } 77 | 78 | constexpr static float EaseOutQuint(float p) { 79 | float f = p - 1.0f; 80 | return (f * f * f * f * f) + 1.0f; 81 | } 82 | 83 | constexpr static float EaseInOutQuint(float p) { 84 | if (p < 0.5f) { 85 | return 16.0f * p * p * p * p * p; 86 | } else { 87 | float f = (2.0f * p) - 2.0f; 88 | return (0.5f * f * f * f * f * f) + 1.0f; 89 | } 90 | } 91 | 92 | constexpr static float EaseInSine(float p) { 93 | return std::sin((p - 1.0f) * fM_PI_2) + 1.0f; 94 | } 95 | 96 | constexpr static float EaseOutSine(float p) { 97 | return std::sin(p * fM_PI_2); 98 | } 99 | 100 | constexpr static float EaseInOutSine(float p) { 101 | return 0.5f * (1.0f - std::cos(p * fM_PI)); 102 | } 103 | 104 | constexpr static float EaseInCirc(float p) { 105 | return 1.0f - std::sqrt(1.0f - (p * p)); 106 | } 107 | 108 | constexpr static float EaseOutCirc(float p) { 109 | return std::sqrt((2.0f - p) * p); 110 | } 111 | 112 | constexpr static float EaseInOutCirc(float p) { 113 | if (p < 0.5f) { 114 | return 0.5f * (1.0f - std::sqrt(1.0f - (4.0f * (p * p)))); 115 | } else { 116 | return 0.5f * (std::sqrt(-((2.0f * p) - 3.0f) * ((2.0f * p) - 1.0f)) + 1.0f); 117 | } 118 | } 119 | 120 | constexpr static float EaseInExpo(float p) { 121 | return (p == 0.0f) ? p : std::pow(2.0f, 10.0f * (p - 1.0f)); 122 | } 123 | 124 | constexpr static float EaseOutExpo(float p) { 125 | return (p == 1.0f) ? p : 1.0f - std::pow(2.0f, -10.0f * p); 126 | } 127 | 128 | constexpr static float EaseInOutExpo(float p) { 129 | if (p == 0.0 || p == 1.0) { 130 | return p; 131 | } 132 | 133 | if (p < 0.5f) { 134 | return 0.5f * std::pow(2.0f, (20.0f * p) - 10.0f); 135 | } else { 136 | return (-0.5f * std::pow(2.0f, (-20.0f * p) + 10.0f)) + 1.0f; 137 | } 138 | } 139 | 140 | constexpr static float EaseInElastic(float p) { 141 | return std::sin(13.0f * fM_PI_2 * p) * std::pow(2.0f, 10.0f * (p - 1.0f)); 142 | } 143 | 144 | constexpr static float EaseOutElastic(float p) { 145 | return (std::sin(-13.0f * fM_PI_2 * (p + 1.0f)) * std::pow(2.0f, -10.0f * p)) + 1.0f; 146 | } 147 | 148 | constexpr static float EaseInOutElastic(float p) { 149 | if (p < 0.5f) { 150 | return 0.5f * std::sin(13.0f * fM_PI_2 * (2.0f * p)) * std::pow(2.0f, 10.0f * ((2.0f * p) - 1.0f)); 151 | } else { 152 | return 0.5f * ((std::sin(-13.0f * fM_PI_2 * (2.0f * p)) * std::pow(2.0f, -10.0f * ((2.0f * p) - 1.0f))) + 2.0f); 153 | } 154 | } 155 | 156 | constexpr static float EaseInBack(float p) { 157 | return (p * p * p) - (p * std::sin(p * fM_PI)); 158 | } 159 | 160 | constexpr static float EaseOutBack(float p) { 161 | float f = 1 - p; 162 | return 1 - ((f * f * f) - (f * std::sin(f * fM_PI))); 163 | } 164 | 165 | constexpr static float EaseInOutBack(float p) { 166 | if (p < 0.5f) { 167 | float f = 2 * p; 168 | return 0.5f * ((f * f * f) - (f * std::sin(f * fM_PI))); 169 | } else { 170 | float f = 1 - ((2 * p) - 1); 171 | return (0.5f * (1 - ((f * f * f) - (f * std::sin(f * fM_PI))))) + 0.5f; 172 | } 173 | } 174 | 175 | constexpr static float EaseOutBounce(float p) { 176 | if (p < 4 / 11.0f) { 177 | return 121 * p * p / 16.0f; 178 | } else if (p < 8 / 11.0f) { 179 | return (363 / 40.0f * p * p) - (99 / 10.0f * p) + (17 / 5.0f); 180 | } else if (p < 9 / 10.0f) { 181 | return (4356 / 361.0f * p * p) - (35442 / 1805.0f * p) + (16061 / 1805.0f); 182 | } else { 183 | return (54 / 5.0f * p * p) - (513 / 25.0f * p) + (268 / 25.0f); 184 | } 185 | } 186 | 187 | constexpr static float EaseInBounce(float p) { 188 | return 1.0f - EaseOutBounce(1.0f - p); 189 | } 190 | 191 | constexpr static float EaseInOutBounce(float p) { 192 | if (p < 0.5f) { 193 | return 0.5f * EaseInBounce(p * 2.0f); 194 | } else { 195 | return (0.5f * EaseOutBounce((p * 2.0f) - 1.0f)) + 0.5f; 196 | } 197 | } 198 | 199 | constexpr static float Interpolate(float p, Functions function) { 200 | // Short circuit math 201 | if (p >= 1) return 1; 202 | if (p <= 0) return 0; 203 | 204 | switch (function) { 205 | default: 206 | case Functions::EaseLinear: 207 | return EaseLinear(p); 208 | case Functions::EaseStep: 209 | return EaseStep(p); 210 | case Functions::EaseOutQuad: 211 | return EaseOutQuad(p); 212 | case Functions::EaseInQuad: 213 | return EaseInQuad(p); 214 | case Functions::EaseInOutQuad: 215 | return EaseInOutQuad(p); 216 | case Functions::EaseInCubic: 217 | return EaseInCubic(p); 218 | case Functions::EaseOutCubic: 219 | return EaseOutCubic(p); 220 | case Functions::EaseInOutCubic: 221 | return EaseInOutCubic(p); 222 | case Functions::EaseInQuart: 223 | return EaseInQuart(p); 224 | case Functions::EaseOutQuart: 225 | return EaseOutQuart(p); 226 | case Functions::EaseInOutQuart: 227 | return EaseInOutQuart(p); 228 | case Functions::EaseInQuint: 229 | return EaseInQuint(p); 230 | case Functions::EaseOutQuint: 231 | return EaseOutQuint(p); 232 | case Functions::EaseInOutQuint: 233 | return EaseInOutQuint(p); 234 | case Functions::EaseInSine: 235 | return EaseInSine(p); 236 | case Functions::EaseOutSine: 237 | return EaseOutSine(p); 238 | case Functions::EaseInOutSine: 239 | return EaseInOutSine(p); 240 | case Functions::EaseInCirc: 241 | return EaseInCirc(p); 242 | case Functions::EaseOutCirc: 243 | return EaseOutCirc(p); 244 | case Functions::EaseInOutCirc: 245 | return EaseInOutCirc(p); 246 | case Functions::EaseInExpo: 247 | return EaseInExpo(p); 248 | case Functions::EaseOutExpo: 249 | return EaseOutExpo(p); 250 | case Functions::EaseInOutExpo: 251 | return EaseInOutExpo(p); 252 | case Functions::EaseInElastic: 253 | return EaseInElastic(p); 254 | case Functions::EaseOutElastic: 255 | return EaseOutElastic(p); 256 | case Functions::EaseInOutElastic: 257 | return EaseInOutElastic(p); 258 | case Functions::EaseInBack: 259 | return EaseInBack(p); 260 | case Functions::EaseOutBack: 261 | return EaseOutBack(p); 262 | case Functions::EaseInOutBack: 263 | return EaseInOutBack(p); 264 | case Functions::EaseInBounce: 265 | return EaseInBounce(p); 266 | case Functions::EaseOutBounce: 267 | return EaseOutBounce(p); 268 | case Functions::EaseInOutBounce: 269 | return EaseInOutBounce(p); 270 | } 271 | } 272 | } // namespace Easings 273 | 274 | Functions FunctionFromStr(std::string_view str); 275 | -------------------------------------------------------------------------------- /shared/Animation/GameObjectTrackController.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "UnityEngine/GameObject.hpp" 4 | #include "UnityEngine/MonoBehaviour.hpp" 5 | #include "UnityEngine/Transform.hpp" 6 | 7 | #include "Track.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "custom-types/shared/types.hpp" 14 | #include "custom-types/shared/macros.hpp" 15 | 16 | namespace Tracks { 17 | struct GameObjectTrackControllerData { 18 | GameObjectTrackControllerData() = delete; 19 | GameObjectTrackControllerData(GameObjectTrackControllerData const&) = delete; 20 | GameObjectTrackControllerData(std::span track, bool v2) : _track(track.begin(), track.end()), v2(v2) {} 21 | 22 | std::vector const _track; 23 | bool const v2; 24 | 25 | UnorderedEventCallback<> PositionUpdate; 26 | UnorderedEventCallback<> ScaleUpdate; 27 | UnorderedEventCallback<> RotationUpdate; 28 | }; 29 | } // namespace Tracks 30 | 31 | DECLARE_CLASS_CODEGEN(Tracks, GameObjectTrackController, UnityEngine::MonoBehaviour) { 32 | DECLARE_INSTANCE_FIELD(UnityEngine::Transform*, parent); 33 | DECLARE_INSTANCE_FIELD(UnityEngine::Transform*, origin); 34 | GameObjectTrackControllerData* data; 35 | TimeUnit lastCheckedTime; 36 | int attemptedTries; 37 | 38 | public: 39 | static bool LeftHanded; 40 | 41 | static std::optional HandleTrackData(UnityEngine::GameObject * gameObject, 42 | std::span track, 43 | float noteLinesDistance, bool v2, bool overwrite); 44 | 45 | static void ClearData(); 46 | 47 | void UpdateData(bool force); 48 | GameObjectTrackControllerData& getTrackControllerData(); 49 | DECLARE_INSTANCE_METHOD(void, Awake); 50 | DECLARE_INSTANCE_METHOD(void, Start); 51 | DECLARE_INSTANCE_METHOD(void, OnDestroy); 52 | DECLARE_INSTANCE_METHOD(void, OnEnable); 53 | DECLARE_INSTANCE_METHOD(void, Update); 54 | DECLARE_INSTANCE_METHOD(void, OnTransformParentChanged); 55 | 56 | DECLARE_SIMPLE_DTOR(); 57 | DECLARE_DEFAULT_CTOR(); 58 | }; -------------------------------------------------------------------------------- /shared/Animation/PointDefinition.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../Vector.h" 7 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 8 | #include "Easings.h" 9 | #include "../Hash.h" 10 | #include "UnityEngine/Color.hpp" 11 | #include "beatsaber-hook/shared/rapidjson/include/rapidjson/document.h" 12 | #include "../bindings.h" 13 | 14 | extern Tracks::ffi::FFIJsonValue const* convert_rapidjson(rapidjson::Value const& value); 15 | 16 | class PointDefinitionW { 17 | public: 18 | explicit PointDefinitionW(rapidjson::Value const& value, Tracks::ffi::WrapBaseValueType type, 19 | Tracks::ffi::BaseProviderContext* internal_tracks_context) { 20 | auto* json = convert_rapidjson(value); 21 | 22 | internalPointDefinition = Tracks::ffi::tracks_make_base_point_definition(json, type, internal_tracks_context); 23 | this->internal_tracks_context = internal_tracks_context; 24 | } 25 | 26 | PointDefinitionW(Tracks::ffi::BasePointDefinition const* pointDefinition, Tracks::ffi::BaseProviderContext* context) 27 | : internalPointDefinition(pointDefinition), internal_tracks_context(context) {} 28 | 29 | PointDefinitionW(PointDefinitionW const& other) = default; 30 | explicit PointDefinitionW(std::nullptr_t) : internalPointDefinition(nullptr) {}; 31 | 32 | Tracks::ffi::WrapBaseValue Interpolate(float time) const { 33 | bool last; 34 | return Interpolate(time, last); 35 | } 36 | 37 | Tracks::ffi::WrapBaseValue Interpolate(float time, bool& last) const { 38 | auto result = Tracks::ffi::tracks_interpolate_base_point_definition(internalPointDefinition, time, &last, 39 | internal_tracks_context); 40 | 41 | return result; 42 | } 43 | NEVector::Vector3 InterpolateVec3(float time, bool& last) const { 44 | auto result = Interpolate(time, last); 45 | return { result.value.vec3.x, result.value.vec3.y, result.value.vec3.z }; 46 | } 47 | 48 | NEVector::Quaternion InterpolateQuaternion(float time, bool& last) const { 49 | auto result = Interpolate(time, last); 50 | return { result.value.quat.x, result.value.quat.y, result.value.quat.z, result.value.quat.w }; 51 | } 52 | 53 | float InterpolateLinear(float time, bool& last) const { 54 | auto result = Interpolate(time, last); 55 | return result.value.float_v; 56 | } 57 | 58 | NEVector::Vector4 InterpolateVector4(float time, bool& last) const { 59 | auto result = Interpolate(time, last); 60 | return { result.value.vec4.x, result.value.vec4.y, result.value.vec4.z, result.value.vec4.w }; 61 | } 62 | 63 | NEVector::Vector3 InterpolateVec3(float time) const { 64 | bool last; 65 | return InterpolateVec3(time, last); 66 | } 67 | 68 | NEVector::Quaternion InterpolateQuaternion(float time) const { 69 | bool last; 70 | return InterpolateQuaternion(time, last); 71 | } 72 | 73 | float InterpolateLinear(float time) const { 74 | bool last; 75 | return InterpolateLinear(time, last); 76 | } 77 | 78 | NEVector::Vector4 InterpolateVector4(float time) const { 79 | bool last; 80 | return InterpolateVector4(time, last); 81 | } 82 | 83 | uintptr_t count() const { 84 | return Tracks::ffi::tracks_base_point_definition_count(internalPointDefinition); 85 | } 86 | 87 | bool hasBaseProvider() const { 88 | return Tracks::ffi::tracks_base_point_definition_has_base_provider(internalPointDefinition); 89 | } 90 | 91 | operator Tracks::ffi::BasePointDefinition const*() const { 92 | return internalPointDefinition; 93 | } 94 | 95 | // operator Tracks::ffi::BasePointDefinition*() { 96 | // return internalPointDefinition; 97 | // } 98 | 99 | private: 100 | constexpr PointDefinitionW() = default; 101 | 102 | Tracks::ffi::BasePointDefinition const* internalPointDefinition; 103 | Tracks::ffi::BaseProviderContext* internal_tracks_context; 104 | }; 105 | 106 | class PointDefinitionManager { 107 | public: 108 | std::unordered_map pointData; 109 | 110 | void AddPoint(std::string const& pointDataName, rapidjson::Value const& pointData); 111 | }; -------------------------------------------------------------------------------- /shared/Animation/Track.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "../Vector.h" 7 | #include "../sv/small_vector.h" 8 | #include "PointDefinition.h" 9 | #include "UnityEngine/GameObject.hpp" 10 | 11 | #include "beatsaber-hook/shared/utils/typedefs-wrappers.hpp" 12 | 13 | #include 14 | 15 | #include "../bindings.h" 16 | 17 | namespace Events { 18 | struct AnimateTrackContext; 19 | } 20 | 21 | struct PropertyW; 22 | struct PathPropertyW; 23 | 24 | using PropertyNames = Tracks::ffi::PropertyNames; 25 | 26 | struct TimeUnit { 27 | Tracks::ffi::CTimeUnit time; 28 | 29 | constexpr TimeUnit(Tracks::ffi::CTimeUnit time) : time(time) {} 30 | constexpr TimeUnit() = default; 31 | constexpr TimeUnit(TimeUnit const&) = default; 32 | 33 | [[nodiscard]] constexpr operator Tracks::ffi::CTimeUnit() const { 34 | return time; 35 | } 36 | 37 | // get seconds 38 | constexpr uint64_t get_seconds() const { 39 | return time._0; 40 | } 41 | 42 | // get nanoseconds 43 | constexpr uint64_t get_nanoseconds() const { 44 | return time._1; 45 | } 46 | 47 | constexpr bool operator<(TimeUnit o) const { 48 | return get_seconds() < o.get_seconds() || 49 | (o.get_seconds() == get_seconds() && get_nanoseconds() < o.get_nanoseconds()); 50 | } 51 | 52 | constexpr bool operator>(TimeUnit o) const { 53 | return get_seconds() > o.get_seconds() || 54 | (o.get_seconds() == get_seconds() && get_nanoseconds() > o.get_nanoseconds()); 55 | } 56 | 57 | constexpr bool operator==(TimeUnit o) const { 58 | return get_seconds() == o.get_seconds() && get_nanoseconds() == o.get_nanoseconds(); 59 | } 60 | 61 | constexpr bool operator!=(TimeUnit o) const { 62 | return get_seconds() != o.get_seconds() || get_nanoseconds() != o.get_nanoseconds(); 63 | } 64 | 65 | constexpr bool operator<=(TimeUnit o) const { 66 | return o == *this || *this < o; 67 | } 68 | 69 | constexpr bool operator>=(TimeUnit o) const { 70 | return o == *this || *this > o; 71 | } 72 | }; 73 | 74 | struct PropertyW { 75 | Tracks::ffi::ValueProperty const* property; 76 | 77 | constexpr PropertyW() = default; 78 | constexpr PropertyW(Tracks::ffi::ValueProperty const* property) : property(property) {} 79 | 80 | operator Tracks::ffi::ValueProperty const*() const { 81 | return property; 82 | } 83 | operator bool() const { 84 | return property != nullptr; 85 | } 86 | 87 | [[nodiscard]] Tracks::ffi::WrapBaseValueType GetType() const { 88 | return Tracks::ffi::property_get_type(property); 89 | } 90 | [[nodiscard]] Tracks::ffi::CValueProperty GetValue() const { 91 | return Tracks::ffi::property_get_value(property); 92 | } 93 | 94 | [[nodiscard]] TimeUnit GetTime() const { 95 | return Tracks::ffi::property_get_last_updated(property); 96 | } 97 | 98 | [[nodiscard]] std::optional GetQuat(TimeUnit lastCheckedTime = {}) const { 99 | auto value = GetValue(); 100 | if (!value.value.has_value) return std::nullopt; 101 | if (TimeUnit(value.last_updated) <= lastCheckedTime) return std::nullopt; 102 | if (value.value.value.ty != Tracks::ffi::WrapBaseValueType::Quat) return std::nullopt; 103 | 104 | auto v = value.value.value.value; 105 | return NEVector::Quaternion{ v.quat.x, v.quat.y, v.quat.z, v.quat.w }; 106 | } 107 | [[nodiscard]] std::optional GetVec3(TimeUnit lastCheckedTime = {}) const { 108 | auto value = GetValue(); 109 | if (!value.value.has_value) return std::nullopt; 110 | if (TimeUnit(value.last_updated) <= lastCheckedTime) return std::nullopt; 111 | if (value.value.value.ty != Tracks::ffi::WrapBaseValueType::Vec3) return std::nullopt; 112 | 113 | auto v = value.value.value.value; 114 | return NEVector::Vector3{ v.vec3.x, v.vec3.y, v.vec3.z }; 115 | } 116 | [[nodiscard]] std::optional GetVec4(TimeUnit lastCheckedTime = {}) const { 117 | auto value = GetValue(); 118 | if (!value.value.has_value) return std::nullopt; 119 | if (TimeUnit(value.last_updated) <= lastCheckedTime) return std::nullopt; 120 | if (value.value.value.ty != Tracks::ffi::WrapBaseValueType::Vec4) return std::nullopt; 121 | auto v = value.value.value.value; 122 | 123 | return NEVector::Vector4{ v.vec4.x, v.vec4.y, v.vec4.z, v.vec4.w }; 124 | } 125 | [[nodiscard]] std::optional GetFloat(TimeUnit lastCheckedTime = {}) const { 126 | auto value = GetValue(); 127 | if (!value.value.has_value) return std::nullopt; 128 | if (TimeUnit(value.last_updated) <= lastCheckedTime) return std::nullopt; 129 | if (value.value.value.ty != Tracks::ffi::WrapBaseValueType::Float) return std::nullopt; 130 | 131 | return value.value.value.value.float_v; 132 | } 133 | }; 134 | 135 | struct PathPropertyW { 136 | Tracks::ffi::PathProperty* property; 137 | 138 | constexpr PathPropertyW() = default; 139 | constexpr PathPropertyW(Tracks::ffi::PathProperty* property) : property(property) {} 140 | operator Tracks::ffi::PathProperty*() const { 141 | return property; 142 | } 143 | operator Tracks::ffi::PathProperty const*() const { 144 | return property; 145 | } 146 | operator bool() const { 147 | return property != nullptr; 148 | } 149 | 150 | Tracks::ffi::WrapBaseValue Interpolate(float time, bool& last, Tracks::ffi::BaseProviderContext* context) const { 151 | auto result = Tracks::ffi::path_property_interpolate(property, time, context); 152 | last = result.has_value; 153 | return result.value; 154 | } 155 | NEVector::Vector3 InterpolateVec3(float time, bool& last, Tracks::ffi::BaseProviderContext* context) const { 156 | auto result = Interpolate(time, last, context); 157 | return { result.value.vec3.x, result.value.vec3.y, result.value.vec3.z }; 158 | } 159 | NEVector::Vector4 InterpolateVec4(float time, bool& last, Tracks::ffi::BaseProviderContext* context) const { 160 | auto result = Interpolate(time, last, context); 161 | return { result.value.vec4.x, result.value.vec4.y, result.value.vec4.z, result.value.vec4.w }; 162 | } 163 | NEVector::Quaternion InterpolateQuat(float time, bool& last, Tracks::ffi::BaseProviderContext* context) const { 164 | auto result = Interpolate(time, last, context); 165 | return { result.value.quat.x, result.value.quat.y, result.value.quat.z, result.value.quat.w }; 166 | } 167 | 168 | float InterpolateLinear(float time, bool& last, Tracks::ffi::BaseProviderContext* context) const { 169 | auto result = Interpolate(time, last, context); 170 | return result.value.float_v; 171 | } 172 | 173 | [[nodiscard]] Tracks::ffi::WrapBaseValueType GetType() const { 174 | return Tracks::ffi::path_property_get_type(property); 175 | } 176 | 177 | [[nodiscard]] float GetTime() const { 178 | return Tracks::ffi::path_property_get_time(property); 179 | } 180 | 181 | void SetTime(float time) const { 182 | Tracks::ffi::path_property_set_time(property, time); 183 | } 184 | 185 | void Finish() const { 186 | Tracks::ffi::path_property_finish(property); 187 | } 188 | 189 | void Init(std::optional newPointData) const { 190 | Tracks::ffi::path_property_init(property, newPointData.value_or(PointDefinitionW(nullptr))); 191 | } 192 | }; 193 | 194 | struct TrackW { 195 | Tracks::ffi::Track* track; 196 | bool v2; 197 | 198 | constexpr TrackW() = default; 199 | constexpr TrackW(Tracks::ffi::Track* track, bool v2) : track(track), v2(v2) {} 200 | 201 | operator Tracks::ffi::Track*() const { 202 | return track; 203 | } 204 | 205 | operator bool() const { 206 | return track != nullptr; 207 | } 208 | 209 | [[nodiscard]] PropertyW GetProperty(std::string_view name) const { 210 | auto prop = Tracks::ffi::track_get_property(track, name.data()); 211 | return PropertyW(prop); 212 | } 213 | [[nodiscard]] PropertyW GetPropertyNamed(Tracks::ffi::PropertyNames name) const { 214 | auto prop = Tracks::ffi::track_get_property_by_name(track, name); 215 | return PropertyW(prop); 216 | } 217 | 218 | [[nodiscard]] PathPropertyW GetPathProperty(std::string_view name) const { 219 | auto prop = Tracks::ffi::track_get_path_property(track, name.data()); 220 | return PathPropertyW(prop); 221 | } 222 | [[nodiscard]] PathPropertyW GetPathPropertyNamed(Tracks::ffi::PropertyNames name) const { 223 | auto prop = Tracks::ffi::track_get_path_property_by_name(track, name); 224 | return PathPropertyW(prop); 225 | } 226 | 227 | void RegisterGameObject(UnityEngine::GameObject* gameObject) const { 228 | Tracks::ffi::track_register_game_object(track, Tracks::ffi::GameObject{ .ptr = gameObject }); 229 | } 230 | 231 | void RegisterProperty(std::string_view id, PropertyW property) { 232 | Tracks::ffi::track_register_property(track, id.data(), const_cast(property.property)); 233 | } 234 | void RegisterPathProperty(std::string_view id, PathPropertyW property) const { 235 | Tracks::ffi::track_register_path_property(track, id.data(), property); 236 | } 237 | 238 | [[nodiscard]] Tracks::ffi::CPropertiesMap GetPropertiesMap() const { 239 | return Tracks::ffi::track_get_properties_map(track); 240 | } 241 | 242 | [[nodiscard]] Tracks::ffi::CPathPropertiesMap GetPathPropertiesMap() const { 243 | return Tracks::ffi::track_get_path_properties_map(track); 244 | } 245 | 246 | [[nodiscard]] std::string GetName() const { 247 | return Tracks::ffi::track_get_name(track); 248 | } 249 | 250 | [[nodiscard]] std::span GetGameObjects() const { 251 | static_assert(sizeof(UnityEngine::GameObject*) == sizeof(Tracks::ffi::GameObject), 252 | "Tracks wrapper and GameObject pointer do not match size!"); 253 | std::size_t count = 0; 254 | auto const* ptr = Tracks::ffi::track_get_game_objects(track, &count); 255 | const auto *castedPtr = reinterpret_cast(ptr); 256 | 257 | return std::span(castedPtr, count); 258 | } 259 | }; 260 | -------------------------------------------------------------------------------- /shared/AssociatedData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Animation/Easings.h" 4 | #include "Animation/Track.h" 5 | #include "Animation/PointDefinition.h" 6 | #include "Animation/Animation.h" 7 | #include "Hash.h" 8 | #include "Vector.h" 9 | #include "custom-json-data/shared/CustomBeatmapData.h" 10 | #include "custom-json-data/shared/CustomEventData.h" 11 | #include 12 | #include 13 | 14 | namespace UnityEngine { 15 | class Renderer; 16 | } 17 | 18 | namespace CustomJSONData { 19 | class CustomBeatmapData; 20 | class JSONWrapper; 21 | } // namespace CustomJSONData 22 | 23 | namespace TracksAD { 24 | namespace Constants { 25 | inline static constexpr std::string_view const V2_DURATION = "_duration"; 26 | inline static constexpr std::string_view const V2_EASING = "_easing"; 27 | inline static constexpr std::string_view const V2_NAME = "_name"; 28 | inline static constexpr std::string_view const V2_POINT_DEFINITIONS = "_pointDefinitions"; 29 | inline static constexpr std::string_view const V2_POINTS = "_points"; 30 | inline static constexpr std::string_view const V2_TRACK = "_track"; 31 | inline static constexpr std::string_view const V2_ANIMATION = "_animation"; 32 | inline static constexpr std::string_view const V2_LOCAL_ROTATION = "_localRotation"; 33 | inline static constexpr std::string_view const V2_POSITION = "_position"; 34 | inline static constexpr std::string_view const V2_LOCAL_POSITION = "_localPosition"; 35 | inline static constexpr std::string_view const V2_ROTATION = "_rotation"; 36 | inline static constexpr std::string_view const V2_SCALE = "_scale"; 37 | inline static constexpr std::string_view const V2_DEFINITE_POSITION = "_definitePosition"; 38 | inline static constexpr std::string_view const V2_DISSOLVE = "_dissolve"; 39 | inline static constexpr std::string_view const V2_DISSOLVE_ARROW = "_dissolveArrow"; 40 | inline static constexpr std::string_view const V2_CUTTABLE = "_interactable"; 41 | inline static constexpr std::string_view const V2_COLOR = "_color"; 42 | inline static constexpr std::string_view const V2_TIME = "_time"; 43 | inline static constexpr std::string_view const V2_ATTENUATION = "_attenuation"; 44 | inline static constexpr std::string_view const V2_OFFSET = "_offset"; 45 | inline static constexpr std::string_view const V2_HEIGHT_FOG_STARTY = "_startY"; 46 | inline static constexpr std::string_view const V2_HEIGHT_FOG_HEIGHT = "_height"; 47 | 48 | inline static constexpr std::string_view const INTERACTABLE = "interactable"; 49 | inline static constexpr std::string_view const DURATION = "duration"; 50 | inline static constexpr std::string_view const EASING = "easing"; 51 | inline static constexpr std::string_view const NAME = "name"; 52 | inline static constexpr std::string_view const POINT_DEFINITIONS = "pointDefinitions"; 53 | inline static constexpr std::string_view const POINTS = "points"; 54 | inline static constexpr std::string_view const TRACK = "track"; 55 | inline static constexpr std::string_view const ANIMATION = "animation"; 56 | inline static constexpr std::string_view const POSITION = "position"; 57 | inline static constexpr std::string_view const OFFSET_POSITION = "offsetPosition"; 58 | inline static constexpr std::string_view const OFFSET_ROTATION = "offsetWorldRotation"; 59 | inline static constexpr std::string_view const LOCAL_POSITION = "localPosition"; 60 | inline static constexpr std::string_view const ROTATION = "rotation"; 61 | inline static constexpr std::string_view const LOCAL_ROTATION = "localRotation"; 62 | inline static constexpr std::string_view const SCALE = "scale"; 63 | inline static constexpr std::string_view const DEFINITE_POSITION = "definitePosition"; 64 | inline static constexpr std::string_view const EVENT = "event"; 65 | inline static constexpr std::string_view const REPEAT = "repeat"; 66 | inline static constexpr std::string_view const TYPE = "type"; 67 | inline static constexpr std::string_view const EVENT_DEFINITIONS = "eventDefinitions"; 68 | inline static constexpr std::string_view const ATTENUATION = "attenuation"; 69 | inline static constexpr std::string_view const OFFSET = "offset"; 70 | inline static constexpr std::string_view const HEIGHT_FOG_STARTY = "startY"; 71 | inline static constexpr std::string_view const HEIGHT_FOG_HEIGHT = "height"; 72 | inline static constexpr std::string_view const DISSOLVE = "dissolve"; 73 | inline static constexpr std::string_view const DISSOLVE_ARROW = "dissolveArrow"; 74 | inline static constexpr std::string_view const CUTTABLE = "interactable"; 75 | inline static constexpr std::string_view const COLOR = "color"; 76 | inline static constexpr std::string_view const TIME = "time"; 77 | 78 | inline static constexpr std::string_view const LEFT_HANDED_ID = "leftHanded"; 79 | } // namespace Constants 80 | 81 | using TracksVector = sbo::small_vector; 82 | 83 | enum class EventType { animateTrack, assignPathAnimation, unknown }; 84 | 85 | class TracksContext { 86 | public: 87 | Tracks::ffi::TracksContext* internal_tracks_context = nullptr; 88 | 89 | TracksContext() { 90 | internal_tracks_context = Tracks::ffi::tracks_context_create(); 91 | } 92 | TracksContext(TracksContext const&) = delete; 93 | 94 | TracksContext(TracksContext&& o) noexcept : internal_tracks_context(o.internal_tracks_context) { 95 | o.internal_tracks_context = nullptr; 96 | } 97 | ~TracksContext() { 98 | if (!internal_tracks_context) { 99 | return; 100 | } 101 | Tracks::ffi::tracks_context_destroy(internal_tracks_context); 102 | } 103 | 104 | operator Tracks::ffi::TracksContext const*() const { 105 | return internal_tracks_context; 106 | } 107 | operator Tracks::ffi::TracksContext*() { 108 | return internal_tracks_context; 109 | } 110 | 111 | [[nodiscard]] Tracks::ffi::CoroutineManager* GetCoroutineManager() const { 112 | if (!internal_tracks_context) { 113 | throw std::runtime_error("TracksContext is null"); 114 | } 115 | return Tracks::ffi::tracks_context_get_coroutine_manager(internal_tracks_context); 116 | } 117 | 118 | [[nodiscard]] Tracks::ffi::BaseProviderContext* GetBaseProviderContext() const { 119 | if (!internal_tracks_context) { 120 | throw std::runtime_error("TracksContext is null"); 121 | } 122 | return Tracks::ffi::tracks_context_get_base_provider_context(internal_tracks_context); 123 | } 124 | 125 | PointDefinitionW AddPointDefinition(std::optional id, 126 | Tracks::ffi::BasePointDefinition* pointDefinition) const { 127 | if (!internal_tracks_context) { 128 | throw std::runtime_error("TracksContext is null"); 129 | } 130 | auto ptr = Tracks::ffi::tracks_context_add_point_definition(internal_tracks_context, id.value_or("").data(), 131 | pointDefinition); 132 | 133 | return { ptr, GetBaseProviderContext() }; 134 | } 135 | 136 | [[nodiscard]] std::optional GetPointDefinition(std::string_view name, 137 | Tracks::ffi::WrapBaseValueType ty) const { 138 | if (!internal_tracks_context) { 139 | throw std::runtime_error("TracksContext is null"); 140 | } 141 | auto pointDefinition = Tracks::ffi::tracks_context_get_point_definition(internal_tracks_context, name.data(), ty); 142 | if (!pointDefinition) { 143 | return std::nullopt; 144 | } 145 | 146 | return PointDefinitionW(pointDefinition, GetBaseProviderContext()); 147 | } 148 | 149 | TrackW AddTrack(TrackW track) const { 150 | if (!internal_tracks_context) { 151 | throw std::runtime_error("TracksContext is null"); 152 | } 153 | auto ptr = Tracks::ffi::tracks_context_add_track(internal_tracks_context, track); 154 | 155 | return TrackW(const_cast(ptr), track.v2); 156 | } 157 | }; 158 | 159 | class BeatmapAssociatedData { 160 | public: 161 | BeatmapAssociatedData() { 162 | internal_tracks_context = std::make_shared(); 163 | } 164 | ~BeatmapAssociatedData() = default; 165 | 166 | [[deprecated("Don't copy this!")]] BeatmapAssociatedData(BeatmapAssociatedData const&) = default; 167 | 168 | bool valid = false; 169 | bool leftHanded = false; 170 | bool v2; 171 | std::unordered_map tracks; 172 | std::unordered_map pointDefinitionsRaw; 173 | 174 | // TODO: Use this to cache instead of Animation::TryGetPointData 175 | std::unordered_map pointDefinitions; 176 | 177 | std::shared_ptr internal_tracks_context; 178 | 179 | inline PointDefinitionW getPointDefinition(rapidjson::Value const& val, std::string_view key, 180 | Tracks::ffi::WrapBaseValueType type) { 181 | PointDefinitionW pointData = Animation::TryGetPointData(*this, val, key, type); 182 | 183 | return pointData; 184 | } 185 | 186 | // get or create 187 | TrackW getTrack(std::string_view name) { 188 | auto it = tracks.find(name); 189 | if (it != tracks.end()) { 190 | return it->second; 191 | } 192 | 193 | auto freeTrack = Tracks::ffi::track_create(); 194 | auto ownedTrack = internal_tracks_context->AddTrack(TrackW(freeTrack, v2)); 195 | tracks.emplace(name, ownedTrack); 196 | 197 | return ownedTrack; 198 | } 199 | }; 200 | 201 | struct BeatmapObjectAssociatedData { 202 | // Should this be an optional? - Fern 203 | TracksVector tracks; 204 | }; 205 | 206 | struct AnimateTrackData { 207 | sbo::small_vector> properties; 208 | 209 | AnimateTrackData(BeatmapAssociatedData& beatmapAD, rapidjson::Value const& customData, TrackW trackProperties); 210 | }; 211 | 212 | struct AssignPathAnimationData { 213 | sbo::small_vector> pathProperties; 214 | 215 | AssignPathAnimationData(BeatmapAssociatedData& beatmapAD, rapidjson::Value const& customData, 216 | TrackW trackPathProperties); 217 | }; 218 | 219 | struct CustomEventAssociatedData { 220 | // This can probably be omitted or a set 221 | TracksVector tracks; 222 | float duration; 223 | Functions easing; 224 | EventType type; 225 | uint32_t repeat; 226 | 227 | // probably not a set, this might be ordered. Oh how much I hate tracks 228 | sbo::small_vector animateTrackData; 229 | sbo::small_vector assignPathAnimation; 230 | 231 | bool parsed = false; 232 | }; 233 | 234 | void LoadTrackEvent(CustomJSONData::CustomEventData const* customEventData, TracksAD::BeatmapAssociatedData& beatmapAD, 235 | bool v2); 236 | void readBeatmapDataAD(CustomJSONData::CustomBeatmapData* beatmapData); 237 | BeatmapAssociatedData& getBeatmapAD(CustomJSONData::JSONWrapper* customData); 238 | BeatmapObjectAssociatedData& getAD(CustomJSONData::JSONWrapper* customData); 239 | CustomEventAssociatedData& getEventAD(CustomJSONData::CustomEventData const* customEventData); 240 | 241 | void clearEventADs(); 242 | } // namespace TracksAD 243 | 244 | namespace NEJSON { 245 | static std::optional ReadOptionalTracks(rapidjson::Value const& object, 246 | std::string_view const key, 247 | TracksAD::BeatmapAssociatedData& beatmapAD) { 248 | auto tracksIt = object.FindMember(key.data()); 249 | if (tracksIt != object.MemberEnd()) { 250 | TracksAD::TracksVector tracks; 251 | 252 | auto size = tracksIt->value.IsString() ? 1 : tracksIt->value.Size(); 253 | tracks.reserve(size); 254 | 255 | if (tracksIt->value.IsString()) { 256 | tracks.emplace_back(beatmapAD.getTrack(tracksIt->value.GetString())); 257 | } else if (tracksIt->value.IsArray()) { 258 | for (auto const& it : tracksIt->value.GetArray()) { 259 | tracks.emplace_back(beatmapAD.getTrack(it.GetString())); 260 | } 261 | } 262 | 263 | return tracks; 264 | } 265 | return std::nullopt; 266 | } 267 | } // namespace NEJSON -------------------------------------------------------------------------------- /shared/Hash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace TracksAD { 7 | struct string_equal { 8 | using is_transparent = std::true_type; 9 | 10 | bool operator()(std::string_view l, std::string_view r) const noexcept { 11 | return l == r; 12 | } 13 | }; 14 | 15 | struct string_hash { 16 | using is_transparent = std::true_type; 17 | 18 | auto operator()(std::string_view str) const noexcept { 19 | return std::hash()(str); 20 | } 21 | }; 22 | } // namespace TracksAD 23 | -------------------------------------------------------------------------------- /shared/Json.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "custom-json-data/shared/JsonUtils.h" 3 | 4 | // #pragma once 5 | 6 | // #include 7 | // #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 8 | // #include "Vector.h" 9 | // #include "TLogger.h" 10 | 11 | // namespace NEJSON { 12 | 13 | // static std::optional ReadOptionalBool(rapidjson::Value const& object, std::string_view const key) { 14 | // auto itr = object.FindMember(key.data()); 15 | // if (itr != object.MemberEnd()) { 16 | // if (itr->value.IsString()) { 17 | // std::string boolS = itr->value.GetString(); 18 | 19 | // if (boolS == "true") { 20 | // return true; 21 | // } 22 | 23 | // TLogger::GetLogger().error("ReadOptionalBool: THE VALUE IS A STRING WHY! value: \"%s\"", boolS.c_str()); 24 | // return false; 25 | // } 26 | 27 | // return itr->value.GetBool(); 28 | // } 29 | // return std::nullopt; 30 | // } 31 | 32 | // static std::optional ReadOptionalString(rapidjson::Value const& object, std::string_view const key) { 33 | // auto itr = object.FindMember(key.data()); 34 | // if (itr != object.MemberEnd() && itr->value.IsString()) { 35 | // return itr->value.GetString(); 36 | // } 37 | // return std::nullopt; 38 | // } 39 | 40 | // static std::optional ReadOptionalFloat(rapidjson::Value const& object, std::string_view const key) { 41 | // auto itr = object.FindMember(key.data()); 42 | // if (itr != object.MemberEnd()) { 43 | // return itr->value.GetFloat(); 44 | // } 45 | // return std::nullopt; 46 | // } 47 | 48 | // static std::optional ReadOptionalInt(rapidjson::Value const& object, std::string_view const key) { 49 | // auto itr = object.FindMember(key.data()); 50 | // if (itr != object.MemberEnd()) { 51 | // return itr->value.GetInt(); 52 | // } 53 | // return std::nullopt; 54 | // } 55 | 56 | // static std::optional ReadOptionalVector2(rapidjson::Value const& object, 57 | // std::string_view const key) { 58 | // auto itr = object.FindMember(key.data()); 59 | // if (itr != object.MemberEnd() && itr->value.Size() >= 2) { 60 | // float x = itr->value[0].GetFloat(); 61 | // float y = itr->value[1].GetFloat(); 62 | // return NEVector::Vector2(x, y); 63 | // } 64 | // return std::nullopt; 65 | // } 66 | 67 | // // Used for note flip 68 | // static std::optional ReadOptionalVector2_emptyY(rapidjson::Value const& object, 69 | // std::string_view const key) { 70 | // auto itr = object.FindMember(key.data()); 71 | 72 | // if (itr != object.MemberEnd() && itr->value.Size() >= 1) { 73 | // float x = itr->value[0].GetFloat(); 74 | // float y = 0; 75 | 76 | // if (itr->value.Size() > 1) { 77 | // y = itr->value[1].GetFloat(); 78 | // } 79 | // return NEVector::Vector2(x, y); 80 | // } 81 | // return std::nullopt; 82 | // } 83 | 84 | // using OptPair = std::pair, std::optional>; 85 | 86 | // static OptPair ReadOptionalPair(rapidjson::Value const& object, std::string_view const key) { 87 | // auto itr = object.FindMember(key.data()); 88 | 89 | // if (itr != object.MemberEnd() && itr->value.Size() >= 1) { 90 | // float x = itr->value[0].GetFloat(); 91 | // float y = 0; 92 | 93 | // if (itr->value.Size() >= 2) { 94 | // y = itr->value[1].GetFloat(); 95 | // return OptPair(std::optional(x), std::optional(y)); 96 | // } 97 | // return OptPair(std::optional(x), std::nullopt); 98 | // } 99 | // return OptPair(std::nullopt, std::nullopt); 100 | // } 101 | 102 | // static std::optional ReadOptionalRotation(rapidjson::Value const& object, 103 | // std::string_view const key) { 104 | // auto itr = object.FindMember(key.data()); 105 | // if (itr != object.MemberEnd()) { 106 | // NEVector::Vector3 rot; 107 | // if (itr->value.IsArray() && itr->value.Size() >= 3) { 108 | // float x = itr->value[0].GetFloat(); 109 | // float y = itr->value[1].GetFloat(); 110 | // float z = itr->value[2].GetFloat(); 111 | // rot = NEVector::Vector3(x, y, z); 112 | // } else if (itr->value.IsNumber()) { 113 | // rot = NEVector::Vector3(0, itr->value.GetFloat(), 0); 114 | // } 115 | 116 | // return NEVector::Quaternion::Euler(rot); 117 | // } 118 | // return std::nullopt; 119 | // } 120 | 121 | // static std::optional ReadOptionalVector3(rapidjson::Value const& object, 122 | // std::string_view const key) { 123 | // auto itr = object.FindMember(key.data()); 124 | // if (itr != object.MemberEnd() && itr->value.Size() >= 3) { 125 | // float x = itr->value[0].GetFloat(); 126 | // float y = itr->value[1].GetFloat(); 127 | // float z = itr->value[2].GetFloat(); 128 | // return NEVector::Vector3(x, y, z); 129 | // } 130 | // return std::nullopt; 131 | // } 132 | 133 | // static std::optional, 3>> ReadOptionalScale(rapidjson::Value const& object, 134 | // std::string_view const key) { 135 | // auto itr = object.FindMember(key.data()); 136 | // if (itr != object.MemberEnd() && itr->value.IsArray()) { 137 | // rapidjson::SizeType size = itr->value.Size(); 138 | // std::optional x = size >= 1 ? std::optional{ itr->value[0].GetFloat() } : std::nullopt; 139 | // std::optional y = size >= 2 ? std::optional{ itr->value[1].GetFloat() } : std::nullopt; 140 | // std::optional z = size >= 3 ? std::optional{ itr->value[2].GetFloat() } : std::nullopt; 141 | // return { { x, y, z } }; 142 | // } 143 | // return std::nullopt; 144 | // } 145 | 146 | // } // namespace NEJSON -------------------------------------------------------------------------------- /shared/StaticHolders.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GlobalNamespace/BpmController.hpp" 4 | 5 | struct __attribute__((visibility("default"))) TracksStatic { 6 | static SafePtr bpmController; 7 | }; -------------------------------------------------------------------------------- /shared/TLogger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "beatsaber-hook/shared/utils/logging.hpp" 3 | #include "custom-json-data/shared/CJDLogger.h" 4 | 5 | class TLogger { 6 | public: 7 | static constexpr auto Logger = Paper::ConstLoggerContext("Tracks"); 8 | }; 9 | -------------------------------------------------------------------------------- /shared/TimeSourceHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GlobalNamespace/IAudioTimeSource.hpp" 4 | 5 | namespace TimeSourceHelper { 6 | 7 | float getSongTime(GlobalNamespace::IAudioTimeSource* timeSource); 8 | 9 | } // end namespace TimeSourceHelper -------------------------------------------------------------------------------- /shared/Vector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "UnityEngine/Quaternion.hpp" 4 | #include "UnityEngine/Vector2.hpp" 5 | #include "UnityEngine/Vector3.hpp" 6 | #include "UnityEngine/Vector4.hpp" 7 | 8 | #include "sombrero/shared/Vector2Utils.hpp" 9 | #include "sombrero/shared/Vector3Utils.hpp" 10 | #include "sombrero/shared/QuaternionUtils.hpp" 11 | 12 | namespace NEVector { 13 | 14 | using Vector2 = Sombrero::FastVector2; 15 | using Vector3 = Sombrero::FastVector3; 16 | using Quaternion = Sombrero::FastQuaternion; 17 | 18 | struct Vector4 : public UnityEngine::Vector4 { 19 | constexpr Vector4(float x = 0, float y = 0, float z = 0, float w = 0) : UnityEngine::Vector4(x, y, z, w) {} 20 | constexpr Vector4(UnityEngine::Vector4 const& other) : UnityEngine::Vector4(other) {} 21 | 22 | static Vector4 LerpUnclamped(Vector4 a, Vector4 b, float t) { 23 | return { a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t }; 24 | } 25 | 26 | constexpr operator Vector3() const { 27 | return Vector3(x, y, z); 28 | } 29 | bool operator==(const Vector4& other) const { 30 | return x == other.x && y == other.y && z == other.z && w == other.w; 31 | } 32 | 33 | constexpr Vector4 operator +(const Vector4& other) const { 34 | return Vector4(x + other.x, y + other.y, z + other.z, w + other.w); 35 | } 36 | constexpr Vector4 operator *(const Vector4& other) const { 37 | return Vector4(x * other.x, y * other.y, z * other.z, w * other.w); 38 | } 39 | constexpr Vector4 operator *(float other) const { 40 | return Vector4(x * other, y * other, z * other, w * other); 41 | } 42 | }; 43 | 44 | struct Vector5 { 45 | float x, y, z, w, v; 46 | 47 | constexpr Vector5(float x = 0, float y = 0, float z = 0, float w = 0, float v = 0) 48 | : x{ x }, y{ y }, z{ z }, w{ w }, v{ v } {}; 49 | 50 | constexpr operator Vector4() const { 51 | return Vector4(x, y, z, w); 52 | } 53 | }; 54 | 55 | } // end namespace NEVector -------------------------------------------------------------------------------- /shared/bindings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | namespace Tracks { 10 | namespace ffi { 11 | #endif // __cplusplus 12 | 13 | enum CEventTypeEnum 14 | #ifdef __cplusplus 15 | : uint32_t 16 | #endif // __cplusplus 17 | { 18 | AnimateTrack = 0, 19 | AssignPathAnimation = 1, 20 | }; 21 | #ifndef __cplusplus 22 | typedef uint32_t CEventTypeEnum; 23 | #endif // __cplusplus 24 | 25 | typedef enum Functions { 26 | EaseLinear, 27 | EaseStep, 28 | EaseInQuad, 29 | EaseOutQuad, 30 | EaseInOutQuad, 31 | EaseInCubic, 32 | EaseOutCubic, 33 | EaseInOutCubic, 34 | EaseInQuart, 35 | EaseOutQuart, 36 | EaseInOutQuart, 37 | EaseInQuint, 38 | EaseOutQuint, 39 | EaseInOutQuint, 40 | EaseInSine, 41 | EaseOutSine, 42 | EaseInOutSine, 43 | EaseInCirc, 44 | EaseOutCirc, 45 | EaseInOutCirc, 46 | EaseInExpo, 47 | EaseOutExpo, 48 | EaseInOutExpo, 49 | EaseInElastic, 50 | EaseOutElastic, 51 | EaseInOutElastic, 52 | EaseInBack, 53 | EaseOutBack, 54 | EaseInOutBack, 55 | EaseInBounce, 56 | EaseOutBounce, 57 | EaseInOutBounce, 58 | } Functions; 59 | 60 | /** 61 | * JSON FFI 62 | */ 63 | typedef enum JsonValueType { 64 | Number, 65 | Null, 66 | String, 67 | Array, 68 | } JsonValueType; 69 | 70 | enum PropertyNames 71 | #ifdef __cplusplus 72 | : uint32_t 73 | #endif // __cplusplus 74 | { 75 | Position, 76 | Rotation, 77 | Scale, 78 | LocalRotation, 79 | LocalPosition, 80 | DefinitePosition, 81 | Dissolve, 82 | DissolveArrow, 83 | Time, 84 | Cuttable, 85 | Color, 86 | Attentuation, 87 | FogOffset, 88 | HeightFogStartY, 89 | HeightFogHeight, 90 | }; 91 | #ifndef __cplusplus 92 | typedef uint32_t PropertyNames; 93 | #endif // __cplusplus 94 | 95 | typedef enum WrapBaseValueType { 96 | Vec3 = 0, 97 | Quat = 1, 98 | Vec4 = 2, 99 | Float = 3, 100 | } WrapBaseValueType; 101 | 102 | typedef struct BaseFFIProviderValues BaseFFIProviderValues; 103 | 104 | typedef struct BasePointDefinition BasePointDefinition; 105 | 106 | typedef struct BaseProviderContext BaseProviderContext; 107 | 108 | typedef struct CoroutineManager CoroutineManager; 109 | 110 | typedef struct EventData EventData; 111 | 112 | typedef struct FloatPointDefinition FloatPointDefinition; 113 | 114 | typedef struct PointDefinitionInterpolation PointDefinitionInterpolation; 115 | 116 | typedef struct QuaternionPointDefinition QuaternionPointDefinition; 117 | 118 | typedef struct Track Track; 119 | 120 | typedef struct TracksContext TracksContext; 121 | 122 | typedef struct ValueProperty ValueProperty; 123 | 124 | typedef struct Vector3PointDefinition Vector3PointDefinition; 125 | 126 | typedef struct Vector4PointDefinition Vector4PointDefinition; 127 | 128 | typedef struct PointDefinitionInterpolation PathProperty; 129 | 130 | typedef union CEventTypeData { 131 | /** 132 | * AnimateTrack(ValueProperty) 133 | */ 134 | const struct ValueProperty *property; 135 | /** 136 | * AssignPathAnimation(PathProperty) 137 | */ 138 | const PathProperty *path_property; 139 | } CEventTypeData; 140 | 141 | typedef struct CEventType { 142 | CEventTypeEnum ty; 143 | union CEventTypeData data; 144 | } CEventType; 145 | 146 | typedef struct CEventData { 147 | float raw_duration; 148 | enum Functions easing; 149 | uint32_t repeat; 150 | float start_time; 151 | struct CEventType event_type; 152 | struct Track *track_ptr; 153 | const struct BasePointDefinition *point_data_ptr; 154 | } CEventData; 155 | 156 | typedef struct JsonArray { 157 | const struct FFIJsonValue *elements; 158 | uintptr_t length; 159 | } JsonArray; 160 | 161 | typedef union JsonValueData { 162 | double number_value; 163 | const char *string_value; 164 | const struct JsonArray *array; 165 | } JsonValueData; 166 | 167 | typedef struct FFIJsonValue { 168 | enum JsonValueType value_type; 169 | union JsonValueData data; 170 | } FFIJsonValue; 171 | 172 | typedef struct WrappedValues { 173 | const float *values; 174 | uintptr_t length; 175 | } WrappedValues; 176 | 177 | typedef struct WrappedValues (*BaseFFIProvider)(const struct BaseProviderContext*, void*); 178 | 179 | typedef struct FloatInterpolationResult { 180 | float value; 181 | bool is_last; 182 | } FloatInterpolationResult; 183 | 184 | typedef struct WrapVec3 { 185 | float x; 186 | float y; 187 | float z; 188 | } WrapVec3; 189 | 190 | typedef struct WrapQuat { 191 | float x; 192 | float y; 193 | float z; 194 | float w; 195 | } WrapQuat; 196 | 197 | typedef struct WrapVec4 { 198 | float x; 199 | float y; 200 | float z; 201 | float w; 202 | } WrapVec4; 203 | 204 | typedef union WrapBaseValueUnion { 205 | struct WrapVec3 vec3; 206 | struct WrapQuat quat; 207 | struct WrapVec4 vec4; 208 | float float_v; 209 | } WrapBaseValueUnion; 210 | 211 | typedef struct WrapBaseValue { 212 | enum WrapBaseValueType ty; 213 | union WrapBaseValueUnion value; 214 | } WrapBaseValue; 215 | 216 | typedef struct Vector3InterpolationResult { 217 | struct WrapVec3 value; 218 | bool is_last; 219 | } Vector3InterpolationResult; 220 | 221 | typedef struct Vector4InterpolationResult { 222 | struct WrapVec4 value; 223 | bool is_last; 224 | } Vector4InterpolationResult; 225 | 226 | typedef struct QuaternionInterpolationResult { 227 | struct WrapQuat value; 228 | bool is_last; 229 | } QuaternionInterpolationResult; 230 | 231 | typedef struct CValueNullable { 232 | bool has_value; 233 | struct WrapBaseValue value; 234 | } CValueNullable; 235 | 236 | typedef struct CTimeUnit { 237 | uint64_t _0; 238 | uint32_t _1; 239 | } CTimeUnit; 240 | 241 | typedef struct CValueProperty { 242 | struct CValueNullable value; 243 | struct CTimeUnit last_updated; 244 | } CValueProperty; 245 | 246 | typedef struct GameObject { 247 | const void *ptr; 248 | } GameObject; 249 | 250 | typedef struct CPropertiesMap { 251 | const struct ValueProperty *position; 252 | const struct ValueProperty *rotation; 253 | const struct ValueProperty *scale; 254 | const struct ValueProperty *local_rotation; 255 | const struct ValueProperty *local_position; 256 | const struct ValueProperty *dissolve; 257 | const struct ValueProperty *dissolve_arrow; 258 | const struct ValueProperty *time; 259 | const struct ValueProperty *cuttable; 260 | const struct ValueProperty *color; 261 | const struct ValueProperty *attentuation; 262 | const struct ValueProperty *fog_offset; 263 | const struct ValueProperty *height_fog_start_y; 264 | const struct ValueProperty *height_fog_height; 265 | } CPropertiesMap; 266 | 267 | typedef struct CPathPropertiesMap { 268 | PathProperty *position; 269 | PathProperty *rotation; 270 | PathProperty *scale; 271 | PathProperty *local_rotation; 272 | PathProperty *local_position; 273 | PathProperty *definite_position; 274 | PathProperty *dissolve; 275 | PathProperty *dissolve_arrow; 276 | PathProperty *cuttable; 277 | PathProperty *color; 278 | } CPathPropertiesMap; 279 | 280 | 281 | 282 | #ifdef __cplusplus 283 | extern "C" { 284 | #endif // __cplusplus 285 | 286 | struct TracksContext *tracks_context_create(void); 287 | 288 | /** 289 | * Consumes the context and frees its memory. 290 | */ 291 | void tracks_context_destroy(struct TracksContext *context); 292 | 293 | /** 294 | * Consumes the track and moves 295 | * it into the context. Returns a const pointer to the track. 296 | */ 297 | const struct Track *tracks_context_add_track(struct TracksContext *context, struct Track *track); 298 | 299 | /** 300 | * Consumes the point definition and moves it into the context. 301 | * Returns a const pointer to the point definition. 302 | * 303 | * If id is null/empty, generates a uuid for the point definition. 304 | */ 305 | const struct BasePointDefinition *tracks_context_add_point_definition(struct TracksContext *context, 306 | const char *id, 307 | struct BasePointDefinition *point_def); 308 | 309 | const struct BasePointDefinition *tracks_context_get_point_definition(struct TracksContext *context, 310 | const char *name, 311 | enum WrapBaseValueType ty); 312 | 313 | struct Track *tracks_context_get_track_by_name(struct TracksContext *context, const char *name); 314 | 315 | struct Track *tracks_context_get_track(struct TracksContext *context, uintptr_t index); 316 | 317 | struct CoroutineManager *tracks_context_get_coroutine_manager(struct TracksContext *context); 318 | 319 | struct BaseProviderContext *tracks_context_get_base_provider_context(struct TracksContext *context); 320 | 321 | /** 322 | * Creates a new CoroutineManager instance and returns a raw pointer to it. 323 | * The caller is responsible for freeing the memory using destroy_coroutine_manager. 324 | */ 325 | struct CoroutineManager *create_coroutine_manager(void); 326 | 327 | /** 328 | * Destroys a CoroutineManager instance, freeing its memory. 329 | */ 330 | void destroy_coroutine_manager(struct CoroutineManager *manager); 331 | 332 | /** 333 | * Starts an event coroutine in the manager. Consumes event_data 334 | */ 335 | void start_event_coroutine(struct CoroutineManager *manager, 336 | float bpm, 337 | float song_time, 338 | const struct BaseProviderContext *context, 339 | struct EventData *event_data); 340 | 341 | /** 342 | * Polls all events in the manager, updating their state based on the current song time. 343 | */ 344 | void poll_events(struct CoroutineManager *manager, 345 | float song_time, 346 | const struct BaseProviderContext *context); 347 | 348 | /** 349 | * C-compatible wrapper for easing functions 350 | */ 351 | float interpolate_easing(enum Functions easing_function, float t); 352 | 353 | /** 354 | * Gets an easing function by index (useful for FFI where enums might be troublesome) 355 | * Returns Functions::EaseLinear if the index is out of bounds 356 | */ 357 | enum Functions get_easing_function_by_index(int32_t index); 358 | 359 | /** 360 | * Gets the total number of available easing functions 361 | */ 362 | int32_t get_easing_function_count(void); 363 | 364 | /** 365 | * Converts a CEventData into a Rust EventData 366 | * Does not consume the CEventData 367 | * Returns a raw pointer to the Rust EventData 368 | */ 369 | struct EventData *event_data_to_rust(const struct CEventData *c_event_data); 370 | 371 | void event_data_dispose(struct EventData *event_data); 372 | 373 | struct FFIJsonValue tracks_create_json_number(double value); 374 | 375 | struct FFIJsonValue tracks_create_json_string(const char *value); 376 | 377 | struct FFIJsonValue tracks_create_json_array(const struct FFIJsonValue *elements, uintptr_t length); 378 | 379 | void tracks_free_json_value(struct FFIJsonValue *json_value); 380 | 381 | struct BaseFFIProviderValues *tracks_make_base_ffi_provider(const BaseFFIProvider *func, 382 | void *user_value); 383 | 384 | /** 385 | * Dispose the base provider. Consumes 386 | */ 387 | void tracks_dipose_base_ffi_provider(struct BaseFFIProviderValues *func); 388 | 389 | /** 390 | * CONTEXT 391 | */ 392 | struct BaseProviderContext *tracks_make_base_provider_context(void); 393 | 394 | void tracks_set_base_provider(struct BaseProviderContext *context, 395 | const char *base, 396 | float *values, 397 | uintptr_t count, 398 | bool quat); 399 | 400 | /** 401 | *FLOAT POINT DEFINITION 402 | */ 403 | const struct FloatPointDefinition *tracks_make_float_point_definition(const struct FFIJsonValue *json, 404 | struct BaseProviderContext *context); 405 | 406 | struct FloatInterpolationResult tracks_interpolate_float(const struct FloatPointDefinition *point_definition, 407 | float time, 408 | struct BaseProviderContext *context); 409 | 410 | uintptr_t tracks_float_count(const struct FloatPointDefinition *point_definition); 411 | 412 | bool tracks_float_has_base_provider(const struct FloatPointDefinition *point_definition); 413 | 414 | /** 415 | *BASE POINT DEFINITION 416 | */ 417 | struct BasePointDefinition *tracks_make_base_point_definition(const struct FFIJsonValue *json, 418 | enum WrapBaseValueType ty, 419 | struct BaseProviderContext *context); 420 | 421 | struct WrapBaseValue tracks_interpolate_base_point_definition(const struct BasePointDefinition *point_definition, 422 | float time, 423 | bool *is_last_out, 424 | struct BaseProviderContext *context); 425 | 426 | uintptr_t tracks_base_point_definition_count(const struct BasePointDefinition *point_definition); 427 | 428 | bool tracks_base_point_definition_has_base_provider(const struct BasePointDefinition *point_definition); 429 | 430 | /** 431 | *VECTOR3 POINT DEFINITION 432 | */ 433 | const struct Vector3PointDefinition *tracks_make_vector3_point_definition(const struct FFIJsonValue *json, 434 | struct BaseProviderContext *context); 435 | 436 | struct Vector3InterpolationResult tracks_interpolate_vector3(const struct Vector3PointDefinition *point_definition, 437 | float time, 438 | struct BaseProviderContext *context); 439 | 440 | uintptr_t tracks_vector3_count(const struct Vector3PointDefinition *point_definition); 441 | 442 | bool tracks_vector3_has_base_provider(const struct Vector3PointDefinition *point_definition); 443 | 444 | /** 445 | *VECTOR4 POINT DEFINITION 446 | */ 447 | const struct Vector4PointDefinition *tracks_make_vector4_point_definition(const struct FFIJsonValue *json, 448 | struct BaseProviderContext *context); 449 | 450 | struct Vector4InterpolationResult tracks_interpolate_vector4(const struct Vector4PointDefinition *point_definition, 451 | float time, 452 | struct BaseProviderContext *context); 453 | 454 | uintptr_t tracks_vector4_count(const struct Vector4PointDefinition *point_definition); 455 | 456 | bool tracks_vector4_has_base_provider(const struct Vector4PointDefinition *point_definition); 457 | 458 | /** 459 | *QUATERNION POINT DEFINITION 460 | */ 461 | const struct QuaternionPointDefinition *tracks_make_quat_point_definition(const struct FFIJsonValue *json, 462 | struct BaseProviderContext *context); 463 | 464 | struct QuaternionInterpolationResult tracks_interpolate_quat(const struct QuaternionPointDefinition *point_definition, 465 | float time, 466 | struct BaseProviderContext *context); 467 | 468 | uintptr_t tracks_quat_count(const struct QuaternionPointDefinition *point_definition); 469 | 470 | bool tracks_quat_has_base_provider(const struct QuaternionPointDefinition *point_definition); 471 | 472 | PathProperty *path_property_create(void); 473 | 474 | void path_property_finish(PathProperty *ptr); 475 | 476 | void path_property_init(PathProperty *ptr, const struct BasePointDefinition *new_point_data); 477 | 478 | /** 479 | * Consumes the path property and frees its memory. 480 | */ 481 | void path_property_free(PathProperty *ptr); 482 | 483 | float path_property_get_time(const PathProperty *ptr); 484 | 485 | void path_property_set_time(PathProperty *ptr, float time); 486 | 487 | struct CValueNullable path_property_interpolate(PathProperty *ptr, 488 | float time, 489 | struct BaseProviderContext *context); 490 | 491 | enum WrapBaseValueType path_property_get_type(const PathProperty *ptr); 492 | 493 | enum WrapBaseValueType property_get_type(const struct ValueProperty *ptr); 494 | 495 | struct CValueProperty property_get_value(const struct ValueProperty *ptr); 496 | 497 | struct CTimeUnit property_get_last_updated(const struct ValueProperty *ptr); 498 | 499 | struct Track *track_create(void); 500 | 501 | /** 502 | * Consumes the track and frees its memory. 503 | */ 504 | void track_destroy(struct Track *track); 505 | 506 | void track_set_name(struct Track *track, const char *name); 507 | 508 | const char *track_get_name(const struct Track *track); 509 | 510 | void track_register_game_object(struct Track *track, struct GameObject game_object); 511 | 512 | void track_unregister_game_object(struct Track *track, struct GameObject game_object); 513 | 514 | const struct GameObject *track_get_game_objects(const struct Track *track, uintptr_t *size); 515 | 516 | void track_register_property(struct Track *track, const char *id, struct ValueProperty *property); 517 | 518 | const struct ValueProperty *track_get_property(const struct Track *track, const char *id); 519 | 520 | const struct ValueProperty *track_get_property_by_name(const struct Track *track, PropertyNames id); 521 | 522 | PathProperty *track_get_path_property_by_name(struct Track *track, PropertyNames id); 523 | 524 | void track_register_path_property(struct Track *track, const char *id, PathProperty *property); 525 | 526 | PathProperty *track_get_path_property(struct Track *track, const char *id); 527 | 528 | struct CPropertiesMap track_get_properties_map(const struct Track *track); 529 | 530 | struct CPathPropertiesMap track_get_path_properties_map(struct Track *track); 531 | 532 | struct CTimeUnit get_time(void); 533 | 534 | #ifdef __cplusplus 535 | } // extern "C" 536 | #endif // __cplusplus 537 | 538 | #ifdef __cplusplus 539 | } // namespace ffi 540 | } // namespace Tracks 541 | #endif // __cplusplus 542 | -------------------------------------------------------------------------------- /shared/sv/small_vector.h: -------------------------------------------------------------------------------- 1 | // taken from https://github.com/KonanM/small_vector/blob/master/include/small_vector/small_vector.h 2 | 3 | // Licensed under the Unlicense 4 | // SPDX-License-Identifier: Unlicense 5 | #pragma once 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace sbo { 13 | 14 | template struct small_buffer_vector_allocator { 15 | alignas(alignof(T)) std::byte m_smallBuffer[MaxSize * sizeof(T)]; 16 | std::allocator m_alloc{}; 17 | bool m_smallBufferUsed = false; 18 | 19 | using value_type = T; 20 | // we have to set this three values, as they are responsible for the correct handling of the move assignment operator 21 | using propagate_on_container_move_assignment = std::false_type; 22 | using propagate_on_container_swap = std::false_type; 23 | using is_always_equal = std::false_type; 24 | 25 | constexpr small_buffer_vector_allocator() noexcept = default; 26 | template 27 | constexpr small_buffer_vector_allocator(small_buffer_vector_allocator const&) noexcept {} 28 | 29 | template struct rebind { typedef small_buffer_vector_allocator other; }; 30 | // don't copy the small buffer for the copy/move constructors, as the copying is done through the vector 31 | constexpr small_buffer_vector_allocator(small_buffer_vector_allocator const& other) noexcept 32 | : m_smallBufferUsed(other.m_smallBufferUsed) {} 33 | constexpr small_buffer_vector_allocator& operator=(small_buffer_vector_allocator const& other) noexcept { 34 | m_smallBufferUsed = other.m_smallBufferUsed; 35 | return *this; 36 | } 37 | constexpr small_buffer_vector_allocator(small_buffer_vector_allocator&&) noexcept {} 38 | constexpr small_buffer_vector_allocator& operator=(small_buffer_vector_allocator const&&) noexcept { 39 | return *this; 40 | } 41 | 42 | [[nodiscard]] constexpr T* allocate(const size_t n) { 43 | // when the allocator was rebound we don't want to use the small buffer 44 | if constexpr (std::is_same_v) { 45 | if (n <= MaxSize) { 46 | m_smallBufferUsed = true; 47 | // as long as we use less memory than the small buffer, we return a pointer to it 48 | return reinterpret_cast(&m_smallBuffer); 49 | } 50 | } 51 | m_smallBufferUsed = false; 52 | // otherwise use the default allocator 53 | return m_alloc.allocate(n); 54 | } 55 | constexpr void deallocate(void* p, const size_t n) { 56 | // we don't deallocate anything if the memory was allocated in small buffer 57 | if (&m_smallBuffer != p) m_alloc.deallocate(static_cast(p), n); 58 | m_smallBufferUsed = false; 59 | } 60 | // according to the C++ standard when propagate_on_container_move_assignment is set to false, the comparision 61 | // operators are used to check if two allocators are equal. When they are not, an element wise move is done instead of 62 | // just taking over the memory. For our implementation this means the comparision has to return false, when the small 63 | // buffer is active 64 | friend constexpr bool operator==(small_buffer_vector_allocator const& lhs, small_buffer_vector_allocator const& rhs) { 65 | return !lhs.m_smallBufferUsed && !rhs.m_smallBufferUsed; 66 | } 67 | friend constexpr bool operator!=(small_buffer_vector_allocator const& lhs, small_buffer_vector_allocator const& rhs) { 68 | return !(lhs == rhs); 69 | } 70 | }; 71 | 72 | template class small_vector : public std::vector> { 73 | public: 74 | using vectorT = std::vector>; 75 | // default initialize with the small buffer size 76 | constexpr small_vector() noexcept { 77 | vectorT::reserve(N); 78 | } 79 | small_vector(small_vector const&) = default; 80 | small_vector& operator=(small_vector const&) = default; 81 | small_vector(small_vector&& other) noexcept(std::is_nothrow_move_constructible_v) { 82 | if (other.size() <= N) vectorT::reserve(N); 83 | vectorT::operator=(std::move(other)); 84 | } 85 | small_vector& operator=(small_vector&& other) noexcept(std::is_nothrow_move_constructible_v) { 86 | if (other.size() <= N) vectorT::reserve(N); 87 | vectorT::operator=(std::move(other)); 88 | return *this; 89 | } 90 | // use the default constructor first to reserve then construct the values 91 | explicit small_vector(size_t count) : small_vector() { 92 | vectorT::resize(count); 93 | } 94 | small_vector(size_t count, const T& value) : small_vector() { 95 | vectorT::assign(count, value); 96 | } 97 | template small_vector(InputIt first, InputIt last) : small_vector() { 98 | vectorT::insert(vectorT::begin(), first, last); 99 | } 100 | small_vector(std::initializer_list init) : small_vector() { 101 | vectorT::insert(vectorT::begin(), init); 102 | } 103 | friend void swap(small_vector& a, small_vector& b) noexcept { 104 | using std::swap; 105 | swap(static_cast(a), static_cast(b)); 106 | } 107 | 108 | // Fern 109 | operator vectorT const&() const { 110 | return static_cast(*this); 111 | }; 112 | 113 | operator vectorT&() { 114 | return static_cast(*this); 115 | }; 116 | 117 | operator std::span() const { 118 | return std::span(this->data(), this->size()); 119 | }; 120 | 121 | operator std::span() const { 122 | return std::span(const_cast(this->data()), this->size()); 123 | }; 124 | 125 | operator std::span() { 126 | return std::span(this->data(), this->size()); 127 | }; 128 | // 129 | }; 130 | } // namespace sbo -------------------------------------------------------------------------------- /src/Animation/Animation.cpp: -------------------------------------------------------------------------------- 1 | #include "Animation/Animation.h" 2 | #include "Animation/PointDefinition.h" 3 | #include "AssociatedData.h" 4 | #include "TLogger.h" 5 | 6 | using namespace TracksAD; 7 | 8 | namespace Animation { 9 | 10 | PointDefinitionW TryGetPointData(BeatmapAssociatedData& beatmapAD, rapidjson::Value const& customData, 11 | std::string_view pointName, Tracks::ffi::WrapBaseValueType type) { 12 | PointDefinitionW pointData = PointDefinitionW(nullptr); 13 | 14 | auto customDataItr = customData.FindMember(pointName.data()); 15 | if (customDataItr == customData.MemberEnd()) { 16 | return pointData; 17 | } 18 | rapidjson::Value const& pointString = customDataItr->value; 19 | 20 | switch (pointString.GetType()) { 21 | case rapidjson::kNullType: 22 | return pointData; 23 | case rapidjson::kStringType: { 24 | 25 | auto id = pointString.GetString(); 26 | auto itr = beatmapAD.pointDefinitionsRaw.find(id); 27 | if (itr == beatmapAD.pointDefinitionsRaw.end()) { 28 | TLogger::Logger.warn("Could not find point definition {}", pointString.GetString()); 29 | } else { 30 | auto json = convert_rapidjson(*itr->second); 31 | auto tracksContext = beatmapAD.internal_tracks_context; 32 | auto baseProviderContext = tracksContext->GetBaseProviderContext(); 33 | auto pointDataAnon = Tracks::ffi::tracks_make_base_point_definition(json, type, baseProviderContext); 34 | pointData = tracksContext->AddPointDefinition(id, pointDataAnon); 35 | } 36 | 37 | break; 38 | } 39 | default: 40 | auto json = convert_rapidjson(pointString); 41 | auto tracksContext = beatmapAD.internal_tracks_context; 42 | auto baseProviderContext = tracksContext->GetBaseProviderContext(); 43 | auto pointDataAnon = Tracks::ffi::tracks_make_base_point_definition(json, type, baseProviderContext); 44 | pointData = tracksContext->AddPointDefinition("", pointDataAnon); 45 | } 46 | 47 | return pointData; 48 | } 49 | 50 | } // namespace Animation -------------------------------------------------------------------------------- /src/Animation/Easings.cpp: -------------------------------------------------------------------------------- 1 | #include "Animation/Easings.h" 2 | #include "TLogger.h" 3 | 4 | Functions FunctionFromStr(std::string_view str) { 5 | static std::unordered_map const functions = { 6 | { "easeLinear", Functions::EaseLinear }, { "easeStep", Functions::EaseStep }, 7 | { "easeInQuad", Functions::EaseInQuad }, { "easeOutQuad", Functions::EaseOutQuad }, 8 | { "easeInOutQuad", Functions::EaseInOutQuad }, { "easeInCubic", Functions::EaseInCubic }, 9 | { "easeOutCubic", Functions::EaseOutCubic }, { "easeInOutCubic", Functions::EaseInOutCubic }, 10 | { "easeInQuart", Functions::EaseInQuart }, { "easeOutQuart", Functions::EaseOutQuart }, 11 | { "easeInOutQuart", Functions::EaseInOutQuart }, { "easeInQuint", Functions::EaseInQuint }, 12 | { "easeOutQuint", Functions::EaseOutQuint }, { "easeInOutQuint", Functions::EaseInOutQuint }, 13 | { "easeInSine", Functions::EaseInSine }, { "easeOutSine", Functions::EaseOutSine }, 14 | { "easeInOutSine", Functions::EaseInOutSine }, { "easeInCirc", Functions::EaseInCirc }, 15 | { "easeOutCirc", Functions::EaseOutCirc }, { "easeInOutCirc", Functions::EaseInOutCirc }, 16 | { "easeInExpo", Functions::EaseInExpo }, { "easeOutExpo", Functions::EaseOutExpo }, 17 | { "easeInOutExpo", Functions::EaseInOutExpo }, { "easeInElastic", Functions::EaseInElastic }, 18 | { "easeOutElastic", Functions::EaseOutElastic }, { "easeInOutElastic", Functions::EaseInOutElastic }, 19 | { "easeInBack", Functions::EaseInBack }, { "easeOutBack", Functions::EaseOutBack }, 20 | { "easeInOutBack", Functions::EaseInOutBack }, { "easeInBounce", Functions::EaseInBounce }, 21 | { "easeOutBounce", Functions::EaseOutBounce }, { "easeInOutBounce", Functions::EaseInOutBounce } 22 | }; 23 | 24 | auto itr = functions.find(str); 25 | if (itr != functions.end()) { 26 | return itr->second; 27 | } else { 28 | TLogger::Logger.error("Invalid function with name {}", str.data()); 29 | // Use linear by default 30 | return Functions::EaseLinear; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Animation/Events.cpp: -------------------------------------------------------------------------------- 1 | #include "beatsaber-hook/shared/utils/hooking.hpp" 2 | 3 | #include "GlobalNamespace/BeatmapCallbacksController.hpp" 4 | #include "GlobalNamespace/BeatmapData.hpp" 5 | #include "GlobalNamespace/BeatmapObjectSpawnController.hpp" 6 | #include "GlobalNamespace/BpmController.hpp" 7 | 8 | #include "UnityEngine/Resources.hpp" 9 | 10 | #include "beatsaber-hook/shared/utils/hooking.hpp" 11 | #include "custom-json-data/shared/CustomEventData.h" 12 | #include "custom-json-data/shared/CustomBeatmapData.h" 13 | #include "custom-types/shared/register.hpp" 14 | 15 | #include "Animation/Easings.h" 16 | #include "Animation/Events.h" 17 | #include "Animation/Easings.h" 18 | #include "Animation/Track.h" 19 | #include "Animation/Animation.h" 20 | #include "TimeSourceHelper.h" 21 | #include "AssociatedData.h" 22 | #include "TLogger.h" 23 | #include "Vector.h" 24 | #include "StaticHolders.hpp" 25 | 26 | using namespace Events; 27 | using namespace GlobalNamespace; 28 | using namespace NEVector; 29 | using namespace TracksAD; 30 | 31 | BeatmapObjectSpawnController* spawnController; 32 | // BeatmapObjectSpawnController.cpp 33 | 34 | MAKE_HOOK_MATCH(BeatmapObjectSpawnController_Start, &BeatmapObjectSpawnController::Start, void, 35 | BeatmapObjectSpawnController* self) { 36 | spawnController = self; 37 | BeatmapObjectSpawnController_Start(self); 38 | } 39 | 40 | void Events::UpdateCoroutines(BeatmapCallbacksController* callbackController) { 41 | auto songTime = callbackController->songTime; 42 | auto* customBeatmapData = (CustomJSONData::CustomBeatmapData*)callbackController->_beatmapData; 43 | auto& beatmapAD = getBeatmapAD(customBeatmapData->customData); 44 | 45 | auto tracksContext = beatmapAD.internal_tracks_context; 46 | 47 | auto coroutine = tracksContext->GetCoroutineManager(); 48 | auto baseManager = tracksContext->GetBaseProviderContext(); 49 | Tracks::ffi::poll_events(coroutine, songTime, baseManager); 50 | } 51 | 52 | [[nodiscard]] 53 | Tracks::ffi::EventData* makeEvent(float eventTime, CustomEventAssociatedData const& eventAD, 54 | TrackW track, 55 | PathPropertyW pathProperty, PointDefinitionW pointData) { 56 | auto eventType = Tracks::ffi::CEventType { 57 | .ty = Tracks::ffi::CEventTypeEnum::AssignPathAnimation, 58 | .data = { 59 | .path_property = pathProperty, 60 | }, 61 | }; 62 | 63 | Tracks::ffi::CEventData cEventData = { 64 | .raw_duration = eventAD.duration, 65 | .easing = eventAD.easing, 66 | .repeat = eventAD.repeat, 67 | .start_time = eventTime, 68 | .event_type = eventType, 69 | .track_ptr = track, 70 | .point_data_ptr = pointData, 71 | }; 72 | auto eventData = Tracks::ffi::event_data_to_rust(&cEventData); 73 | 74 | return eventData; 75 | } 76 | [[nodiscard]] 77 | Tracks::ffi::EventData* makeEvent(float eventTime, CustomEventAssociatedData const& eventAD, 78 | TrackW const& track, PropertyW const& property, PointDefinitionW const& pointData) { 79 | auto eventType = Tracks::ffi::CEventType { 80 | .ty = Tracks::ffi::CEventTypeEnum::AnimateTrack, 81 | .data = { 82 | .property = property, 83 | }, 84 | }; 85 | 86 | Tracks::ffi::CEventData cEventData = { 87 | .raw_duration = eventAD.duration, 88 | .easing = eventAD.easing, 89 | .repeat = eventAD.repeat, 90 | .start_time = eventTime, 91 | .event_type = eventType, 92 | .track_ptr = track, 93 | .point_data_ptr = pointData, 94 | }; 95 | 96 | auto eventData = Tracks::ffi::event_data_to_rust(&cEventData); 97 | 98 | return eventData; 99 | } 100 | void CustomEventCallback(BeatmapCallbacksController* callbackController, 101 | CustomJSONData::CustomEventData* customEventData) { 102 | PAPER_IL2CPP_CATCH_HANDLER( 103 | bool isType = false; 104 | 105 | auto typeHash = customEventData->typeHash; 106 | 107 | #define TYPE_GET(jsonName, varName) \ 108 | static auto jsonNameHash_##varName = std::hash()(jsonName); \ 109 | if (!isType && typeHash == (jsonNameHash_##varName)) isType = true; 110 | 111 | TYPE_GET("AnimateTrack", AnimateTrack) TYPE_GET("AssignPathAnimation", AssignPathAnimation) 112 | 113 | if (!isType) { return; } 114 | 115 | CustomEventAssociatedData const& eventAD = getEventAD(customEventData); 116 | 117 | auto* customBeatmapData = (CustomJSONData::CustomBeatmapData*)callbackController->_beatmapData; 118 | TracksAD::BeatmapAssociatedData& beatmapAD = TracksAD::getBeatmapAD(customBeatmapData->customData); 119 | 120 | // fail safe, idek why this needs to be done smh 121 | // CJD you bugger 122 | if (!eventAD.parsed) { 123 | TLogger::Logger.debug("callbackController {}", fmt::ptr(callbackController)); 124 | TLogger::Logger.debug("_beatmapData {}", fmt::ptr(callbackController->_beatmapData)); 125 | TLogger::Logger.debug("customBeatmapData {}", fmt::ptr(customBeatmapData)); 126 | 127 | if (!beatmapAD.valid) { 128 | TLogger::Logger.debug("Beatmap wasn't parsed when event is invoked, what?"); 129 | TracksAD::readBeatmapDataAD(customBeatmapData); 130 | } 131 | 132 | LoadTrackEvent(customEventData, beatmapAD, customBeatmapData->v2orEarlier); 133 | } 134 | 135 | auto duration = eventAD.duration; 136 | 137 | if (!TracksStatic::bpmController) { 138 | CJDLogger::Logger.fmtLog("BPM CONTROLLER NOT INITIALIZED"); 139 | } 140 | 141 | auto bpm = TracksStatic::bpmController->currentBpm; // spawnController->get_currentBpm() 142 | 143 | duration = 60.0f * duration / bpm; 144 | 145 | auto easing = eventAD.easing; auto repeat = eventAD.repeat; 146 | 147 | bool noDuration = duration == 0 || customEventData->time + (duration * (repeat + 1)) < 148 | TracksStatic::bpmController->_beatmapCallbacksController->songTime; 149 | 150 | auto tracksContext = beatmapAD.internal_tracks_context; 151 | 152 | auto coroutineManager = tracksContext->GetCoroutineManager(); 153 | auto baseManager = tracksContext->GetBaseProviderContext(); 154 | auto eventTime = customEventData->time; 155 | auto songTime = callbackController->_songTime; 156 | 157 | for (auto const& track 158 | : eventAD.tracks) { 159 | switch (eventAD.type) { 160 | case EventType::animateTrack: { 161 | for (auto const& animateTrackData : eventAD.animateTrackData) { 162 | for (auto const& [property, pointData] : animateTrackData.properties) { 163 | 164 | auto event = makeEvent(eventTime, eventAD, track, property, pointData); 165 | Tracks::ffi::start_event_coroutine(coroutineManager, bpm, songTime, baseManager, event); 166 | } 167 | } 168 | break; 169 | } 170 | case EventType::assignPathAnimation: { 171 | for (auto const& assignPathAnimationData : eventAD.assignPathAnimation) { 172 | for (auto const& [pathProperty, pointData] : assignPathAnimationData.pathProperties) { 173 | auto event = makeEvent(eventTime, eventAD, track, pathProperty, pointData); 174 | Tracks::ffi::start_event_coroutine(coroutineManager, bpm, songTime, baseManager, event); 175 | } 176 | } 177 | break; 178 | } 179 | default: 180 | break; 181 | } 182 | } 183 | 184 | ) 185 | } 186 | 187 | void Events::AddEventCallbacks() { 188 | auto logger = Paper::ConstLoggerContext("Tracks | AddEventCallbacks"); 189 | CustomJSONData::CustomEventCallbacks::AddCustomEventCallback(&CustomEventCallback); 190 | 191 | INSTALL_HOOK(logger, BeatmapObjectSpawnController_Start); 192 | } 193 | -------------------------------------------------------------------------------- /src/Animation/GameObjectTrackController.cpp: -------------------------------------------------------------------------------- 1 | #include "Animation/GameObjectTrackController.hpp" 2 | 3 | #include "Animation/Animation.h" 4 | #include "TLogger.h" 5 | 6 | #include "sombrero/shared/Vector3Utils.hpp" 7 | #include "sombrero/shared/QuaternionUtils.hpp" 8 | 9 | using namespace Tracks; 10 | 11 | bool GameObjectTrackController::LeftHanded = false; 12 | 13 | DEFINE_TYPE(Tracks, GameObjectTrackController) 14 | 15 | using namespace Tracks; 16 | 17 | // static NEVector::Quaternion QuatInverse(const NEVector::Quaternion &a) { 18 | // NEVector::Quaternion conj = {-a.x, -a.y, -a.z, a.w}; 19 | // float norm2 = NEVector::Quaternion::Dot(a, a); 20 | // return {conj.x / norm2, conj.y / norm2, conj.z / norm2, 21 | // conj.w / norm2}; 22 | // } 23 | GameObjectTrackControllerData& GameObjectTrackController::getTrackControllerData() { 24 | return *data; 25 | } 26 | 27 | void GameObjectTrackController::ClearData() { 28 | CJDLogger::Logger.fmtLog("Clearing track game objects"); 29 | } 30 | 31 | void GameObjectTrackController::Awake() { 32 | attemptedTries = 0; 33 | 34 | // OnTransformParentChanged(); 35 | } 36 | 37 | void GameObjectTrackController::Start() { 38 | CJDLogger::Logger.fmtLog("Checking data {}", fmt::ptr(data)); 39 | // CRASH_UNLESS(data); 40 | } 41 | 42 | void GameObjectTrackController::OnDestroy() { 43 | if (data == nullptr) { 44 | return; 45 | } 46 | 47 | delete data; 48 | data = nullptr; 49 | } 50 | 51 | void GameObjectTrackController::OnEnable() { 52 | OnTransformParentChanged(); 53 | } 54 | 55 | void GameObjectTrackController::OnTransformParentChanged() { 56 | origin = get_transform(); 57 | parent = origin->get_parent(); 58 | CJDLogger::Logger.fmtLog("Parent changed {}", static_cast(this->get_name())); 59 | UpdateData(true); 60 | } 61 | 62 | void GameObjectTrackController::Update() { 63 | UpdateData(false); 64 | } 65 | 66 | void GameObjectTrackController::UpdateData(bool force) { 67 | if (!data) { 68 | 69 | // Wait once just in case 70 | if (attemptedTries > 1 && attemptedTries < 10) { 71 | CJDLogger::Logger.fmtLog( 72 | "Data is null! Should remove component or just early return? {} {}", fmt::ptr(this), 73 | static_cast(get_gameObject()->get_name())); 74 | } 75 | 76 | // Destroy the object if the data is never found 77 | if (attemptedTries > 100) { 78 | CJDLogger::Logger.fmtLog("Destroying object", fmt::ptr(this), 79 | static_cast(get_gameObject()->get_name())); 80 | Destroy(this); 81 | CJDLogger::Logger.Backtrace(10); 82 | } else { 83 | attemptedTries++; 84 | } 85 | return; 86 | } 87 | 88 | auto const _noteLinesDistance = 0.6f; // StaticBeatmapObjectSpawnMovementData.kNoteLinesDistance 89 | auto const& tracks = data->_track; 90 | 91 | if (tracks.empty()) { 92 | CJDLogger::Logger.fmtLog("Track is null! Should remove component or just early return? {} {}", 93 | fmt::ptr(this), 94 | static_cast(get_gameObject()->get_name())); 95 | Destroy(this); 96 | return; 97 | } 98 | if (force) { 99 | lastCheckedTime = TimeUnit(); 100 | } 101 | 102 | std::optional rotation; 103 | std::optional localRotation; 104 | std::optional position; 105 | std::optional localPosition; 106 | std::optional scale; 107 | 108 | if (tracks.size() == 1) { 109 | auto track = tracks.front(); 110 | 111 | // after 112 | rotation = track.GetPropertyNamed(PropertyNames::Rotation).GetQuat(lastCheckedTime); 113 | rotation = track.GetPropertyNamed(PropertyNames::LocalRotation).GetQuat(lastCheckedTime); 114 | position = track.GetPropertyNamed(PropertyNames::Position).GetVec3(lastCheckedTime); 115 | localPosition = track.GetPropertyNamed(PropertyNames::LocalPosition).GetVec3(lastCheckedTime); 116 | scale = track.GetPropertyNamed(PropertyNames::Scale).GetVec3(lastCheckedTime); 117 | 118 | } else { 119 | 120 | // now 121 | auto localRotations = Animation::getPropertiesQuat(tracks, PropertyNames::LocalRotation, lastCheckedTime); 122 | auto rotations = Animation::getPropertiesQuat(tracks, PropertyNames::Rotation, lastCheckedTime); 123 | auto positions = Animation::getPropertiesVec3(tracks, PropertyNames::Position, lastCheckedTime); 124 | auto localPositions = Animation::getPropertiesVec3(tracks, PropertyNames::LocalPosition, lastCheckedTime); 125 | auto scales = Animation::getPropertiesVec3(tracks, PropertyNames::Scale, lastCheckedTime); 126 | 127 | TRACKS_LIST_OPERATE_MULTIPLE(localRotation, localRotations, *); 128 | TRACKS_LIST_OPERATE_MULTIPLE(rotation, rotations, *); 129 | TRACKS_LIST_OPERATE_MULTIPLE(scale, scales, *); 130 | TRACKS_LIST_OPERATE_MULTIPLE(position, positions, +); 131 | TRACKS_LIST_OPERATE_MULTIPLE(localPosition, localPositions, +); 132 | } 133 | 134 | if (GameObjectTrackController::LeftHanded) { 135 | localPosition = Animation::MirrorVectorNullable(localPosition); 136 | position = Animation::MirrorVectorNullable(position); 137 | 138 | rotation = Animation::MirrorQuaternionNullable(rotation); 139 | localRotation = Animation::MirrorQuaternionNullable(localRotation); 140 | } 141 | 142 | auto transform = origin; 143 | 144 | static auto Transform_Position = 145 | il2cpp_utils::il2cpp_type_check::FPtrWrapper<&UnityEngine::Transform::set_position>::get(); 146 | static auto Transform_LocalPosition = 147 | il2cpp_utils::il2cpp_type_check::FPtrWrapper<&UnityEngine::Transform::set_localPosition>::get(); 148 | static auto Transform_Rotation = 149 | il2cpp_utils::il2cpp_type_check::FPtrWrapper<&UnityEngine::Transform::set_rotation>::get(); 150 | static auto Transform_LocalRotation = 151 | il2cpp_utils::il2cpp_type_check::FPtrWrapper<&UnityEngine::Transform::set_localRotation>::get(); 152 | static auto Transform_Scale = 153 | il2cpp_utils::il2cpp_type_check::FPtrWrapper<&UnityEngine::Transform::set_localScale>::get(); 154 | if (localRotation) { 155 | 156 | Transform_LocalRotation(transform, localRotation.value()); 157 | data->RotationUpdate.invoke(); 158 | 159 | } else if (rotation) { 160 | Transform_Rotation(transform, rotation.value()); 161 | data->RotationUpdate.invoke(); 162 | } 163 | 164 | if (localPosition) { 165 | if (data->v2) { 166 | *localPosition *= _noteLinesDistance; 167 | } 168 | Transform_LocalPosition(transform, *localPosition); 169 | data->PositionUpdate.invoke(); 170 | } else if (position) { 171 | if (data->v2) { 172 | *position *= _noteLinesDistance; 173 | } 174 | 175 | Transform_Position(transform, *position); 176 | data->PositionUpdate.invoke(); 177 | } 178 | 179 | if (scale) { 180 | Transform_Scale(transform, scale.value()); 181 | data->ScaleUpdate.invoke(); 182 | } 183 | 184 | lastCheckedTime = Animation::getCurrentTime(); 185 | } 186 | 187 | std::optional 188 | GameObjectTrackController::HandleTrackData(UnityEngine::GameObject* gameObject, std::span track, 189 | float noteLinesDistance, bool v2, bool overwrite) { 190 | auto* existingTrackController = gameObject->GetComponent(); 191 | 192 | if (existingTrackController != nullptr) { 193 | if (overwrite) { 194 | CJDLogger::Logger.fmtLog("Overwriting existing TransformController on {}...", 195 | std::string(gameObject->get_name())); 196 | UnityEngine::Object::Destroy(existingTrackController); 197 | } else { 198 | CJDLogger::Logger.fmtLog("Could not create TransformController, {} already has one.", 199 | std::string(gameObject->get_name())); 200 | return existingTrackController; 201 | } 202 | } 203 | 204 | if (track.empty()) { 205 | return std::nullopt; 206 | } 207 | 208 | auto* trackController = gameObject->AddComponent(); 209 | CRASH_UNLESS(!track.empty()); 210 | CJDLogger::Logger.fmtLog("Created track game object with ID {}", 211 | static_cast(gameObject->get_name())); 212 | // cleaned up on OnDestroy 213 | trackController->data = new GameObjectTrackControllerData(track, v2); 214 | 215 | for (auto t : track) { 216 | t.RegisterGameObject(gameObject); 217 | } 218 | 219 | return trackController; 220 | } 221 | -------------------------------------------------------------------------------- /src/Animation/PointDefinition.cpp: -------------------------------------------------------------------------------- 1 | #include "Animation/PointDefinition.h" 2 | 3 | #include 4 | #include 5 | #include "Animation/Track.h" 6 | #include "Animation/Easings.h" 7 | #include "TLogger.h" 8 | #include "custom-json-data/shared/CJDLogger.h" 9 | #include "sombrero/shared/HSBColor.hpp" 10 | #include "bindings.h" 11 | 12 | using namespace NEVector; 13 | 14 | 15 | 16 | // TODO: Make a recursive cleanup method 17 | const Tracks::ffi::FFIJsonValue* convert_rapidjson(rapidjson::Value const& value) { 18 | // Handle different types of rapidjson values 19 | if (value.IsNumber()) { 20 | return new Tracks::ffi::FFIJsonValue{Tracks::ffi::JsonValueType::Number, {.number_value = value.GetDouble()}}; 21 | } else if (value.IsNull()) { 22 | return new Tracks::ffi::FFIJsonValue{Tracks::ffi::JsonValueType::Null, {}}; 23 | } else if (value.IsString()) { 24 | return new Tracks::ffi::FFIJsonValue{Tracks::ffi::JsonValueType::String, {.string_value = value.GetString()}}; 25 | } else if (value.IsArray()) { 26 | // Create array of FFIJsonValue for each element in the array 27 | auto size = value.Size(); 28 | auto *elements = new Tracks::ffi::FFIJsonValue[size]; 29 | 30 | for (size_t i = 0; i < size; i++) { 31 | elements[i] = *convert_rapidjson(value[i]); 32 | } 33 | 34 | auto jsonArray = new Tracks::ffi::JsonArray{elements, size}; 35 | return new Tracks::ffi::FFIJsonValue{Tracks::ffi::JsonValueType::Array, {.array = jsonArray}}; 36 | } else { 37 | TLogger::Logger.error("Unsupported JSON value type in conversion"); 38 | // Return null as fallback 39 | return new Tracks::ffi::FFIJsonValue{Tracks::ffi::JsonValueType::Null, {}}; 40 | } 41 | } 42 | 43 | void PointDefinitionManager::AddPoint(std::string const& pointDataName, const rapidjson::Value& pointData) { 44 | if (this->pointData.contains(pointDataName)) { 45 | TLogger::Logger.error("Duplicate point definition name, {} could not be registered!", pointDataName.data()); 46 | } else { 47 | this->pointData.try_emplace(pointDataName, &pointData); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Animation/Track.cpp: -------------------------------------------------------------------------------- 1 | #include "AssociatedData.h" 2 | #include "Animation/Track.h" 3 | #include 4 | 5 | using namespace TracksAD::Constants; 6 | 7 | -------------------------------------------------------------------------------- /src/AssociatedData.cpp: -------------------------------------------------------------------------------- 1 | #include "AssociatedData.h" 2 | #include "Animation/Animation.h" 3 | #include "Animation/PointDefinition.h" 4 | #include "Animation/Track.h" 5 | #include "custom-json-data/shared/CustomBeatmapData.h" 6 | #include "TLogger.h" 7 | 8 | using namespace TracksAD; 9 | 10 | namespace TracksAD { 11 | 12 | BeatmapObjectAssociatedData& getAD(CustomJSONData::JSONWrapper* customData) { 13 | std::any& ad = customData->associatedData['T']; 14 | if (!ad.has_value()) ad = std::make_any(); 15 | return std::any_cast(ad); 16 | } 17 | 18 | BeatmapAssociatedData& getBeatmapAD(CustomJSONData::JSONWrapper* customData) { 19 | std::any& ad = customData->associatedData['T']; 20 | if (!ad.has_value()) ad = std::make_any(); 21 | return std::any_cast(ad); 22 | } 23 | 24 | static std::unordered_map eventDataMap; 25 | 26 | ::CustomEventAssociatedData& getEventAD(CustomJSONData::CustomEventData const* customData) { 27 | return eventDataMap[customData]; 28 | } 29 | 30 | void clearEventADs() { 31 | eventDataMap.clear(); 32 | } 33 | 34 | inline bool IsStringProperties(std::string_view n) { 35 | using namespace TracksAD::Constants; 36 | return n != V2_TRACK && n != V2_DURATION && n != V2_EASING && n != TRACK && n != DURATION && n != EASING && 37 | n != REPEAT; 38 | } 39 | 40 | AnimateTrackData::AnimateTrackData(BeatmapAssociatedData& beatmapAD, rapidjson::Value const& customData, TrackW track) { 41 | for (auto const& member : customData.GetObject()) { 42 | char const* name = member.name.GetString(); 43 | if (!IsStringProperties(name)) { 44 | continue; 45 | } 46 | auto property = track.GetProperty(name); 47 | if (property) { 48 | auto type = property.GetType(); 49 | 50 | auto pointData = Animation::TryGetPointData(beatmapAD, customData, name, type); 51 | 52 | this->properties.emplace_back(property, pointData); 53 | } else { 54 | TLogger::Logger.warn("Could not find track property with name {}", name); 55 | } 56 | } 57 | } 58 | 59 | AssignPathAnimationData::AssignPathAnimationData(BeatmapAssociatedData& beatmapAD, rapidjson::Value const& customData, 60 | TrackW track) { 61 | for (auto const& member : customData.GetObject()) { 62 | char const* name = member.name.GetString(); 63 | if (IsStringProperties(name)) { 64 | auto property = track.GetPathProperty(name); 65 | if (property) { 66 | auto type = property.GetType(); 67 | 68 | auto pointData = Animation::TryGetPointData(beatmapAD, customData, name, type); 69 | 70 | pathProperties.emplace_back(property, pointData); 71 | } else { 72 | TLogger::Logger.warn("Could not find track path property with name {}", name); 73 | } 74 | } 75 | } 76 | } 77 | 78 | constexpr static float getFloat(rapidjson::Value const& value) { 79 | switch (value.GetType()) { 80 | case rapidjson::kStringType: 81 | return std::stof(value.GetString()); 82 | case rapidjson::kNumberType: 83 | return value.GetFloat(); 84 | default: 85 | throw std::runtime_error(&"Not valid type in JSON doc "[value.GetType()]); 86 | } 87 | } 88 | 89 | void LoadTrackEvent(CustomJSONData::CustomEventData const* customEventData, TracksAD::BeatmapAssociatedData& beatmapAD, 90 | bool v2) { 91 | auto typeHash = customEventData->typeHash; 92 | 93 | #define TYPE_GET(jsonName, varName) static auto jsonNameHash_##varName = std::hash()(jsonName); 94 | 95 | TYPE_GET("AnimateTrack", AnimateTrack) 96 | TYPE_GET("AssignPathAnimation", AssignPathAnimation) 97 | 98 | EventType type; 99 | if (typeHash == jsonNameHash_AnimateTrack) { 100 | type = EventType::animateTrack; 101 | } else if (typeHash == jsonNameHash_AssignPathAnimation) { 102 | type = EventType::assignPathAnimation; 103 | } else { 104 | return; 105 | } 106 | 107 | auto& eventAD = getEventAD(customEventData); 108 | 109 | if (eventAD.parsed) return; 110 | 111 | eventAD.parsed = true; 112 | 113 | rapidjson::Value const& eventData = *customEventData->data; 114 | 115 | eventAD.type = type; 116 | auto const& trackJSON = eventData[(v2 ? TracksAD::Constants::V2_TRACK : TracksAD::Constants::TRACK).data()]; 117 | unsigned int trackSize = trackJSON.IsArray() ? trackJSON.Size() : 1; 118 | 119 | sbo::small_vector tracks; 120 | tracks.reserve(trackSize); 121 | 122 | if (trackJSON.IsArray()) { 123 | for (auto const& track : trackJSON.GetArray()) { 124 | if (!track.IsString()) { 125 | TLogger::Logger.debug("Track in array is not a string, why?"); 126 | continue; 127 | } 128 | 129 | tracks.emplace_back(beatmapAD.getTrack(track.GetString())); 130 | } 131 | } else if (trackJSON.IsString()) { 132 | tracks.emplace_back(beatmapAD.getTrack(trackJSON.GetString())); 133 | } else { 134 | TLogger::Logger.debug("Track object is not a string or array, why?"); 135 | eventAD.type = EventType::unknown; 136 | return; 137 | } 138 | 139 | eventAD.tracks = std::move(tracks); 140 | auto durationIt = 141 | eventData.FindMember((v2 ? TracksAD::Constants::V2_DURATION : TracksAD::Constants::DURATION).data()); 142 | auto easingIt = eventData.FindMember((v2 ? TracksAD::Constants::V2_EASING : TracksAD::Constants::EASING).data()); 143 | auto repeatIt = eventData.FindMember(TracksAD::Constants::REPEAT.data()); 144 | 145 | eventAD.duration = durationIt != eventData.MemberEnd() ? getFloat(durationIt->value) : 0; 146 | eventAD.repeat = eventAD.duration > 0 && repeatIt != eventData.MemberEnd() ? repeatIt->value.GetInt() : 0; 147 | eventAD.easing = 148 | easingIt != eventData.MemberEnd() ? FunctionFromStr(easingIt->value.GetString()) : Functions::EaseLinear; 149 | 150 | for (auto const& track : eventAD.tracks) { 151 | 152 | switch (eventAD.type) { 153 | case EventType::animateTrack: { 154 | eventAD.animateTrackData.emplace_back(beatmapAD, eventData, track); 155 | break; 156 | } 157 | case EventType::assignPathAnimation: { 158 | eventAD.assignPathAnimation.emplace_back(beatmapAD, eventData, track); 159 | break; 160 | } 161 | default: 162 | break; 163 | } 164 | } 165 | } 166 | 167 | void readBeatmapDataAD(CustomJSONData::CustomBeatmapData* beatmapData) { 168 | static auto* customObstacleDataClass = classof(CustomJSONData::CustomObstacleData*); 169 | static auto* customNoteDataClass = classof(CustomJSONData::CustomNoteData*); 170 | static auto* customSliderDataClass = classof(CustomJSONData::CustomSliderData*); 171 | 172 | BeatmapAssociatedData& beatmapAD = getBeatmapAD(beatmapData->customData); 173 | bool v2 = beatmapData->v2orEarlier; 174 | 175 | CJDLogger::Logger.fmtLog("Reading beatmap ad"); 176 | Paper::Logger::Backtrace(CJDLogger::Logger.tag, 20); 177 | 178 | if (beatmapAD.valid) { 179 | return; 180 | } 181 | 182 | beatmapAD.v2 = v2; 183 | 184 | if (beatmapData->customData->value) { 185 | rapidjson::Value const& customData = *beatmapData->customData->value; 186 | 187 | PointDefinitionManager pointDataManager; 188 | auto pointDefinitionsIt = 189 | customData.FindMember(v2 ? Constants::V2_POINT_DEFINITIONS.data() : Constants::POINT_DEFINITIONS.data()); 190 | 191 | if (pointDefinitionsIt != customData.MemberEnd()) { 192 | rapidjson::Value const& pointDefinitions = pointDefinitionsIt->value; 193 | for (rapidjson::Value::ConstValueIterator itr = pointDefinitions.Begin(); itr != pointDefinitions.End(); itr++) { 194 | if (v2) { 195 | std::string pointName = (*itr)[Constants::V2_NAME.data()].GetString(); 196 | CJDLogger::Logger.fmtLog("Added point {}", pointName); 197 | pointDataManager.AddPoint(pointName, (*itr)[Constants::V2_POINTS.data()]); 198 | } else { 199 | for (auto const& [name, pointDataVal] : pointDefinitionsIt->value.GetObject()) { 200 | CJDLogger::Logger.fmtLog("Added point {}", name.GetString()); 201 | pointDataManager.AddPoint(name.GetString(), pointDataVal); 202 | } 203 | } 204 | } 205 | } 206 | TLogger::Logger.debug("Setting point definitions"); 207 | beatmapAD.pointDefinitionsRaw = pointDataManager.pointData; 208 | } 209 | 210 | for (auto* beatmapObjectData : beatmapData->beatmapObjectDatas) { 211 | if (!beatmapObjectData) continue; 212 | 213 | CustomJSONData::JSONWrapper* customDataWrapper; 214 | if (beatmapObjectData->klass == customObstacleDataClass) { 215 | auto obstacleData = (CustomJSONData::CustomObstacleData*)beatmapObjectData; 216 | customDataWrapper = obstacleData->customData; 217 | } else if (beatmapObjectData->klass == customNoteDataClass) { 218 | auto noteData = (CustomJSONData::CustomNoteData*)beatmapObjectData; 219 | customDataWrapper = noteData->customData; 220 | } else if (beatmapObjectData->klass == customSliderDataClass) { 221 | auto sliderData = (CustomJSONData::CustomSliderData*)beatmapObjectData; 222 | customDataWrapper = sliderData->customData; 223 | } else { 224 | continue; 225 | } 226 | 227 | if (customDataWrapper->value) { 228 | rapidjson::Value const& customData = *customDataWrapper->value; 229 | BeatmapObjectAssociatedData& ad = getAD(customDataWrapper); 230 | TracksVector tracksAD; 231 | 232 | auto trackIt = customData.FindMember(v2 ? Constants::V2_TRACK.data() : Constants::TRACK.data()); 233 | if (trackIt != customData.MemberEnd()) { 234 | rapidjson::Value const& tracksObject = trackIt->value; 235 | 236 | switch (tracksObject.GetType()) { 237 | case rapidjson::Type::kArrayType: { 238 | if (tracksObject.Empty()) break; 239 | 240 | for (auto& trackElement : tracksObject.GetArray()) { 241 | tracksAD.emplace_back(beatmapAD.getTrack(trackElement.GetString())); 242 | } 243 | break; 244 | } 245 | case rapidjson::Type::kStringType: { 246 | tracksAD.emplace_back(beatmapAD.getTrack(tracksObject.GetString())); 247 | break; 248 | } 249 | 250 | default: { 251 | TLogger::Logger.error("Tracks object is not an array or a string, what? Why?"); 252 | break; 253 | } 254 | } 255 | } 256 | 257 | ad.tracks = tracksAD; 258 | } 259 | } 260 | 261 | for (auto const& customEventData : beatmapData->customEventDatas) { 262 | if (!customEventData) continue; 263 | LoadTrackEvent(customEventData, beatmapAD, beatmapData->v2orEarlier); 264 | } 265 | 266 | beatmapAD.valid = true; 267 | } 268 | 269 | } // namespace TracksAD -------------------------------------------------------------------------------- /src/Hooks/BaseProviderHooks.cpp: -------------------------------------------------------------------------------- 1 | #include "AssociatedData.h" 2 | #include "THooks.h" 3 | #include "TLogger.h" 4 | #include "beatsaber-hook/shared/utils/hooking.hpp" 5 | 6 | #include "Animation/GameObjectTrackController.hpp" 7 | 8 | #include "GlobalNamespace/GameplayCoreInstaller.hpp" 9 | #include "GlobalNamespace/GameplayCoreSceneSetupData.hpp" 10 | #include "GlobalNamespace/PlayerSpecificSettings.hpp" 11 | #include "GlobalNamespace/ColorScheme.hpp" 12 | #include "GlobalNamespace/PlayerTransforms.hpp" 13 | #include "UnityEngine/Transform.hpp" 14 | 15 | #include "Animation/PointDefinition.h" 16 | #include "bindings.h" 17 | 18 | using namespace CustomJSONData; 19 | using namespace GlobalNamespace; 20 | using namespace UnityEngine; 21 | 22 | // i hate this 23 | static SafePtr tempCustomBeatmap; 24 | 25 | MAKE_HOOK_MATCH(GameplayCoreInstaller_InstallBindings, &GlobalNamespace::GameplayCoreInstaller::InstallBindings, void, 26 | GlobalNamespace::GameplayCoreInstaller* self) { 27 | 28 | GameplayCoreInstaller_InstallBindings(self); 29 | auto colorScheme = self->_sceneSetupData->colorScheme; 30 | auto beatmap = self->_sceneSetupData->get_transformedBeatmapData(); 31 | auto customBeatmap = tempCustomBeatmap = il2cpp_utils::cast(beatmap); 32 | auto const& beatmapAD = TracksAD::getBeatmapAD(customBeatmap->customData); 33 | 34 | auto const& context = beatmapAD.internal_tracks_context; 35 | auto baseProviderContext = context->GetBaseProviderContext(); 36 | 37 | bool leftHanded = self->_sceneSetupData->playerSpecificSettings->leftHanded; 38 | 39 | auto baseEnvironmentColor0 = colorScheme->environmentColor0; 40 | auto baseEnvironmentColor0Boost = colorScheme->environmentColor0Boost; 41 | auto baseEnvironmentColor1 = colorScheme->environmentColor1; 42 | auto baseEnvironmentColor1Boost = colorScheme->environmentColor1Boost; 43 | auto baseEnvironmentColorW = colorScheme->environmentColorW; 44 | auto baseEnvironmentColorWBoost = colorScheme->environmentColorWBoost; 45 | auto baseNoteColor1 = leftHanded ? colorScheme->saberAColor : colorScheme->saberBColor; 46 | auto baseNoteColor0 = leftHanded ? colorScheme->saberBColor : colorScheme->saberAColor; 47 | auto baseObstaclesColor = colorScheme->obstaclesColor; 48 | auto baseSaberAColor = colorScheme->saberAColor; 49 | auto baseSaberBColor = colorScheme->saberBColor; 50 | 51 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseEnvironmentColor0", 52 | new float[4]{ baseEnvironmentColor0.r, baseEnvironmentColor0.g, 53 | baseEnvironmentColor0.b, baseEnvironmentColor0.a }, 54 | 4, false); 55 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseEnvironmentColor0Boost", 56 | new float[4]{ baseEnvironmentColor0Boost.r, baseEnvironmentColor0Boost.g, 57 | baseEnvironmentColor0Boost.b, baseEnvironmentColor0Boost.a }, 58 | 4, false); 59 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseEnvironmentColor1", 60 | new float[4]{ baseEnvironmentColor1.r, baseEnvironmentColor1.g, 61 | baseEnvironmentColor1.b, baseEnvironmentColor1.a }, 62 | 4, false); 63 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseEnvironmentColor1Boost", 64 | new float[4]{ baseEnvironmentColor1Boost.r, baseEnvironmentColor1Boost.g, 65 | baseEnvironmentColor1Boost.b, baseEnvironmentColor1Boost.a }, 66 | 4, false); 67 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseEnvironmentColorW", 68 | new float[4]{ baseEnvironmentColorW.r, baseEnvironmentColorW.g, 69 | baseEnvironmentColorW.b, baseEnvironmentColorW.a }, 70 | 4, false); 71 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseEnvironmentColorWBoost", 72 | new float[4]{ baseEnvironmentColorWBoost.r, baseEnvironmentColorWBoost.g, 73 | baseEnvironmentColorWBoost.b, baseEnvironmentColorWBoost.a }, 74 | 4, false); 75 | Tracks::ffi::tracks_set_base_provider( 76 | baseProviderContext, "baseNote0Color", 77 | new float[4]{ baseNoteColor0.r, baseNoteColor0.g, baseNoteColor0.b, baseNoteColor0.a }, 4, false); 78 | Tracks::ffi::tracks_set_base_provider( 79 | baseProviderContext, "baseNote1Color", 80 | new float[4]{ baseNoteColor1.r, baseNoteColor1.g, baseNoteColor1.b, baseNoteColor1.a }, 4, false); 81 | Tracks::ffi::tracks_set_base_provider( 82 | baseProviderContext, "baseObstaclesColor", 83 | new float[4]{ baseObstaclesColor.r, baseObstaclesColor.g, baseObstaclesColor.b, baseObstaclesColor.a }, 4, false); 84 | Tracks::ffi::tracks_set_base_provider( 85 | baseProviderContext, "baseSaberAColor", 86 | new float[4]{ baseSaberAColor.r, baseSaberAColor.g, baseSaberAColor.b, baseSaberAColor.a }, 4, false); 87 | Tracks::ffi::tracks_set_base_provider( 88 | baseProviderContext, "baseSaberBColor", 89 | new float[4]{ baseSaberBColor.r, baseSaberBColor.g, baseSaberBColor.b, baseSaberBColor.a }, 4, false); 90 | } 91 | 92 | MAKE_HOOK_MATCH(PlayerTransforms_Update, &GlobalNamespace::PlayerTransforms::Update, void, 93 | GlobalNamespace::PlayerTransforms* self) { 94 | PlayerTransforms_Update(self); 95 | 96 | if (!tempCustomBeatmap) { 97 | return; 98 | } 99 | 100 | auto const& beatmapAD = TracksAD::getBeatmapAD(tempCustomBeatmap->customData); 101 | 102 | auto const& context = beatmapAD.internal_tracks_context; 103 | auto baseProviderContext = context->GetBaseProviderContext(); 104 | 105 | auto leftHand = self->_leftHandTransform; 106 | // leftHand = leftHand->parent == nullptr ? leftHand : leftHand->parent; 107 | auto rightHand = self->_rightHandTransform; 108 | // rightHand = rightHand->parent == nullptr ? rightHand : rightHand->parent; 109 | 110 | auto baseHeadLocalPosition = self->_headTransform->localPosition; 111 | auto baseHeadLocalRotation = self->_headTransform->localRotation; 112 | auto baseHeadLocalScale = self->_headTransform->localScale; 113 | auto baseHeadPosition = self->_headTransform->position; 114 | auto baseHeadRotation = self->_headTransform->rotation; 115 | auto baseLeftHandLocalPosition = leftHand->localPosition; 116 | auto baseLeftHandLocalRotation = leftHand->localRotation; 117 | auto baseLeftHandLocalScale = leftHand->localScale; 118 | auto baseLeftHandPosition = leftHand->position; 119 | auto baseLeftHandRotation = leftHand->rotation; 120 | 121 | auto baseRightHandLocalPosition = rightHand->localPosition; 122 | auto baseRightHandLocalRotation = rightHand->localRotation; 123 | auto baseRightHandLocalScale = rightHand->localScale; 124 | auto baseRightHandPosition = rightHand->position; 125 | auto baseRightHandRotation = rightHand->rotation; 126 | 127 | Tracks::ffi::tracks_set_base_provider( 128 | baseProviderContext, "baseHeadLocalPosition", 129 | new float[3]{ baseHeadLocalPosition.x, baseHeadLocalPosition.y, baseHeadLocalPosition.z }, 3, false); 130 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseHeadLocalRotation", 131 | new float[4]{ baseHeadLocalRotation.x, baseHeadLocalRotation.y, 132 | baseHeadLocalRotation.z, baseHeadLocalRotation.w }, 133 | 4, true); 134 | Tracks::ffi::tracks_set_base_provider( 135 | baseProviderContext, "baseHeadLocalScale", 136 | new float[3]{ baseHeadLocalScale.x, baseHeadLocalScale.y, baseHeadLocalScale.z }, 3, false); 137 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseHeadPosition", 138 | new float[3]{ baseHeadPosition.x, baseHeadPosition.y, baseHeadPosition.z }, 3, 139 | false); 140 | Tracks::ffi::tracks_set_base_provider( 141 | baseProviderContext, "baseHeadRotation", 142 | new float[4]{ baseHeadRotation.x, baseHeadRotation.y, baseHeadRotation.z, baseHeadRotation.w }, 4, true); 143 | Tracks::ffi::tracks_set_base_provider( 144 | baseProviderContext, "baseLeftHandLocalPosition", 145 | new float[3]{ baseLeftHandLocalPosition.x, baseLeftHandLocalPosition.y, baseLeftHandLocalPosition.z }, 3, false); 146 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseLeftHandLocalRotation", 147 | new float[4]{ baseLeftHandLocalRotation.x, baseLeftHandLocalRotation.y, 148 | baseLeftHandLocalRotation.z, baseLeftHandLocalRotation.w }, 149 | 4, true); 150 | Tracks::ffi::tracks_set_base_provider( 151 | baseProviderContext, "baseLeftHandLocalScale", 152 | new float[3]{ baseLeftHandLocalScale.x, baseLeftHandLocalScale.y, baseLeftHandLocalScale.z }, 3, false); 153 | Tracks::ffi::tracks_set_base_provider( 154 | baseProviderContext, "baseLeftHandPosition", 155 | new float[3]{ baseLeftHandPosition.x, baseLeftHandPosition.y, baseLeftHandPosition.z }, 3, false); 156 | Tracks::ffi::tracks_set_base_provider( 157 | baseProviderContext, "baseLeftHandRotation", 158 | new float[4]{ baseLeftHandRotation.x, baseLeftHandRotation.y, baseLeftHandRotation.z, baseLeftHandRotation.w }, 4, 159 | true); 160 | Tracks::ffi::tracks_set_base_provider( 161 | baseProviderContext, "baseRightHandLocalPosition", 162 | new float[3]{ baseRightHandLocalPosition.x, baseRightHandLocalPosition.y, baseRightHandLocalPosition.z }, 3, 163 | false); 164 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseRightHandLocalRotation", 165 | new float[4]{ baseRightHandLocalRotation.x, baseRightHandLocalRotation.y, 166 | baseRightHandLocalRotation.z, baseRightHandLocalRotation.w }, 167 | 4, true); 168 | Tracks::ffi::tracks_set_base_provider( 169 | baseProviderContext, "baseRightHandLocalScale", 170 | new float[3]{ baseRightHandLocalScale.x, baseRightHandLocalScale.y, baseRightHandLocalScale.z }, 3, false); 171 | Tracks::ffi::tracks_set_base_provider( 172 | baseProviderContext, "baseRightHandPosition", 173 | new float[3]{ baseRightHandPosition.x, baseRightHandPosition.y, baseRightHandPosition.z }, 3, false); 174 | Tracks::ffi::tracks_set_base_provider(baseProviderContext, "baseRightHandRotation", 175 | new float[4]{ baseRightHandRotation.x, baseRightHandRotation.y, 176 | baseRightHandRotation.z, baseRightHandRotation.w }, 177 | 4, true); 178 | } 179 | 180 | void InstallBaseProviderHooks() { 181 | auto logger = Paper::ConstLoggerContext("Tracks | InstallBaseProviderHooks"); 182 | INSTALL_HOOK(logger, GameplayCoreInstaller_InstallBindings); 183 | INSTALL_HOOK(logger, PlayerTransforms_Update); 184 | } 185 | 186 | TInstallHooks(InstallBaseProviderHooks) -------------------------------------------------------------------------------- /src/Hooks/BeatmapDataTransformHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "THooks.h" 2 | #include "beatsaber-hook/shared/utils/hooking.hpp" 3 | #include "Animation/Events.h" 4 | 5 | #include "AssociatedData.h" 6 | 7 | #include "GlobalNamespace/BeatmapDataTransformHelper.hpp" 8 | #include "GlobalNamespace/EnvironmentEffectsFilterPreset.hpp" 9 | #include "GlobalNamespace/GameplayModifiers.hpp" 10 | 11 | #include "custom-json-data/shared/CustomBeatmapData.h" 12 | 13 | using namespace TracksAD; 14 | using namespace GlobalNamespace; -------------------------------------------------------------------------------- /src/Hooks/BeatmapObjectCallbackController.cpp: -------------------------------------------------------------------------------- 1 | #include "AssociatedData.h" 2 | #include "THooks.h" 3 | #include "beatsaber-hook/shared/utils/hooking.hpp" 4 | #include "Animation/Events.h" 5 | 6 | #include "GlobalNamespace/BeatmapCallbacksController.hpp" 7 | #include "GlobalNamespace/BeatmapCallbacksUpdater.hpp" 8 | #include "GlobalNamespace/BpmController.hpp" 9 | #include "custom-types/shared/coroutine.hpp" 10 | #include "UnityEngine/Resources.hpp" 11 | #include "StaticHolders.hpp" 12 | 13 | #include "Animation/GameObjectTrackController.hpp" 14 | 15 | using namespace GlobalNamespace; 16 | 17 | // MAKE_HOOK_MATCH(BeatmapObjectCallbackController_LateUpdate, &BeatmapObjectCallbackController::LateUpdate, void, 18 | // BeatmapObjectCallbackController *self) { 19 | // Events::UpdateCoroutines(self); 20 | // BeatmapObjectCallbackController_LateUpdate(self); 21 | // } 22 | 23 | #pragma clang diagnostic push 24 | #pragma ide diagnostic ignored "EndlessLoop" 25 | custom_types::Helpers::Coroutine updateCoroutines(BeatmapCallbacksController* self) { 26 | IL2CPP_CATCH_HANDLER(while (true) { 27 | Events::UpdateCoroutines(self); 28 | co_yield nullptr; 29 | }) 30 | } 31 | #pragma clang diagnostic pop 32 | 33 | BeatmapCallbacksController* controller; 34 | SafePtr TracksStatic::bpmController; 35 | 36 | MAKE_HOOK_MATCH(BeatmapObjectCallbackController_Start, &BeatmapCallbacksController::ManualUpdate, void, 37 | BeatmapCallbacksController* self, float songTime) { 38 | BeatmapObjectCallbackController_Start(self, songTime); 39 | if (controller != self) { 40 | controller = self; 41 | 42 | if (auto customBeatmap = il2cpp_utils::try_cast(self->_beatmapData)) { 43 | if (customBeatmap.value()->customData) { 44 | auto& tracksBeatmapAD = TracksAD::getBeatmapAD(customBeatmap.value()->customData); 45 | Tracks::GameObjectTrackController::LeftHanded = tracksBeatmapAD.leftHanded; 46 | } 47 | } 48 | 49 | UnityEngine::Resources::FindObjectsOfTypeAll().get(0)->StartCoroutine( 50 | custom_types::Helpers::CoroutineHelper::New(updateCoroutines(self))); 51 | } 52 | } 53 | 54 | MAKE_HOOK_FIND_INSTANCE(BpmController_ctor, classof(BpmController*), ".ctor", void, BpmController* self, 55 | BpmController::InitData* initData, BeatmapCallbacksController* beatmapCallbacksController) { 56 | BpmController_ctor(self, initData, beatmapCallbacksController); 57 | TracksStatic::bpmController = self; 58 | } 59 | 60 | void InstallBeatmapObjectCallbackControllerHooks() { 61 | auto logger = Paper::ConstLoggerContext("Tracks | InstallBeatmapObjectCallbackControllerHooks"); 62 | INSTALL_HOOK(logger, BeatmapObjectCallbackController_Start); 63 | INSTALL_HOOK(logger, BpmController_ctor); 64 | } 65 | TInstallHooks(InstallBeatmapObjectCallbackControllerHooks) -------------------------------------------------------------------------------- /src/Hooks/UnitySceneChange.cpp: -------------------------------------------------------------------------------- 1 | #include "THooks.h" 2 | #include "beatsaber-hook/shared/utils/hooking.hpp" 3 | 4 | #include "Animation/GameObjectTrackController.hpp" 5 | 6 | #include "GlobalNamespace/GameScenesManager.hpp" 7 | #include "UnityEngine/SceneManagement/SceneManager.hpp" 8 | #include "UnityEngine/SceneManagement/Scene.hpp" 9 | #include "UnityEngine/SceneManagement/LoadSceneMode.hpp" 10 | #include "System/Action.hpp" 11 | #include "custom-json-data/shared/CustomBeatmapData.h" 12 | 13 | using namespace CustomJSONData; 14 | using namespace GlobalNamespace; 15 | using namespace UnityEngine; 16 | 17 | MAKE_HOOK_MATCH(SceneManager_Internal_SceneLoaded, &UnityEngine::SceneManagement::SceneManager::Internal_SceneLoaded, 18 | void, UnityEngine::SceneManagement::Scene scene, UnityEngine::SceneManagement::LoadSceneMode mode) { 19 | 20 | if (scene.IsValid() && scene.get_name() == "GameCore") { 21 | Tracks::GameObjectTrackController::ClearData(); 22 | } 23 | 24 | SceneManager_Internal_SceneLoaded(scene, mode); 25 | } 26 | 27 | void InstallSceneManagerHooks() { 28 | auto logger = Paper::ConstLoggerContext("Tracks | InstallBeatmapObjectCallbackControllerHooks"); 29 | INSTALL_HOOK(logger, SceneManager_Internal_SceneLoaded); 30 | } 31 | 32 | TInstallHooks(InstallSceneManagerHooks) -------------------------------------------------------------------------------- /src/TimeSourceHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "TimeSourceHelper.h" 2 | #include "GlobalNamespace/AudioTimeSyncController.hpp" 3 | 4 | using namespace GlobalNamespace; 5 | 6 | float TimeSourceHelper::getSongTime(GlobalNamespace::IAudioTimeSource* timeSource) { 7 | static auto* timeSyncControllerClass = classof(AudioTimeSyncController*); 8 | auto* timeSourceObject = reinterpret_cast(timeSource); 9 | if (timeSourceObject->klass == timeSyncControllerClass) { 10 | auto* timeSyncController = reinterpret_cast(timeSource); 11 | return timeSyncController->songTime; 12 | } else { 13 | return timeSource->get_songTime(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "TLogger.h" 2 | #include "THooks.h" 3 | #include "Animation/Events.h" 4 | 5 | extern "C" void setup(CModInfo* info) { 6 | info->id = "Tracks"; 7 | info->version = VERSION; 8 | info->version_long = 0; 9 | } 10 | 11 | extern "C" void late_load() { 12 | // Force load to ensure order 13 | auto cjdModInfo = CustomJSONData::modInfo.to_c(); 14 | modloader_require_mod(&cjdModInfo, CMatchType::MatchType_IdOnly); 15 | 16 | Hooks::InstallHooks(); 17 | Events::AddEventCallbacks(); 18 | } -------------------------------------------------------------------------------- /tracks_rs_link/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-linux-android] 2 | rustflags = ["-C", "target-feature=+neon"] -------------------------------------------------------------------------------- /tracks_rs_link/.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /tracks_rs_link/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "getrandom 0.2.15", 13 | "once_cell", 14 | "version_check", 15 | "zerocopy", 16 | ] 17 | 18 | [[package]] 19 | name = "aho-corasick" 20 | version = "1.1.3" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 23 | dependencies = [ 24 | "memchr", 25 | ] 26 | 27 | [[package]] 28 | name = "android_log-sys" 29 | version = "0.3.2" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" 32 | 33 | [[package]] 34 | name = "android_logger" 35 | version = "0.15.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "f6f39be698127218cca460cb624878c9aa4e2b47dba3b277963d2bf00bad263b" 38 | dependencies = [ 39 | "android_log-sys", 40 | "env_filter", 41 | "log", 42 | ] 43 | 44 | [[package]] 45 | name = "approx" 46 | version = "0.5.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" 49 | dependencies = [ 50 | "num-traits", 51 | ] 52 | 53 | [[package]] 54 | name = "autocfg" 55 | version = "1.4.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 58 | 59 | [[package]] 60 | name = "bitflags" 61 | version = "2.9.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 64 | 65 | [[package]] 66 | name = "by_address" 67 | version = "1.2.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" 70 | 71 | [[package]] 72 | name = "cbindgen" 73 | version = "0.28.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" 76 | dependencies = [ 77 | "heck", 78 | "indexmap", 79 | "log", 80 | "proc-macro2", 81 | "quote", 82 | "serde", 83 | "serde_json", 84 | "syn", 85 | "tempfile", 86 | "toml", 87 | ] 88 | 89 | [[package]] 90 | name = "cfg-if" 91 | version = "1.0.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 94 | 95 | [[package]] 96 | name = "ctor" 97 | version = "0.4.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "07e9666f4a9a948d4f1dff0c08a4512b0f7c86414b23960104c243c10d79f4c3" 100 | dependencies = [ 101 | "ctor-proc-macro", 102 | "dtor", 103 | ] 104 | 105 | [[package]] 106 | name = "ctor-proc-macro" 107 | version = "0.0.5" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" 110 | 111 | [[package]] 112 | name = "dtor" 113 | version = "0.0.5" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "222ef136a1c687d4aa0395c175f2c4586e379924c352fd02f7870cf7de783c23" 116 | dependencies = [ 117 | "dtor-proc-macro", 118 | ] 119 | 120 | [[package]] 121 | name = "dtor-proc-macro" 122 | version = "0.0.5" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" 125 | 126 | [[package]] 127 | name = "either" 128 | version = "1.15.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 131 | 132 | [[package]] 133 | name = "env_filter" 134 | version = "0.1.3" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 137 | dependencies = [ 138 | "log", 139 | "regex", 140 | ] 141 | 142 | [[package]] 143 | name = "equivalent" 144 | version = "1.0.2" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 147 | 148 | [[package]] 149 | name = "errno" 150 | version = "0.3.10" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 153 | dependencies = [ 154 | "libc", 155 | "windows-sys", 156 | ] 157 | 158 | [[package]] 159 | name = "fast-srgb8" 160 | version = "1.0.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" 163 | 164 | [[package]] 165 | name = "fastrand" 166 | version = "2.3.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 169 | 170 | [[package]] 171 | name = "getrandom" 172 | version = "0.2.15" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 175 | dependencies = [ 176 | "cfg-if", 177 | "libc", 178 | "wasi 0.11.0+wasi-snapshot-preview1", 179 | ] 180 | 181 | [[package]] 182 | name = "getrandom" 183 | version = "0.3.2" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 186 | dependencies = [ 187 | "cfg-if", 188 | "libc", 189 | "r-efi", 190 | "wasi 0.14.2+wasi-0.2.4", 191 | ] 192 | 193 | [[package]] 194 | name = "glam" 195 | version = "0.30.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "bf3aa70d918d2b234126ff4f850f628f172542bf0603ded26b8ee36e5e22d5f9" 198 | 199 | [[package]] 200 | name = "hashbrown" 201 | version = "0.15.2" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 204 | 205 | [[package]] 206 | name = "heck" 207 | version = "0.4.1" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 210 | 211 | [[package]] 212 | name = "indexmap" 213 | version = "2.8.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" 216 | dependencies = [ 217 | "equivalent", 218 | "hashbrown", 219 | ] 220 | 221 | [[package]] 222 | name = "itertools" 223 | version = "0.14.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 226 | dependencies = [ 227 | "either", 228 | ] 229 | 230 | [[package]] 231 | name = "itoa" 232 | version = "1.0.15" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 235 | 236 | [[package]] 237 | name = "libc" 238 | version = "0.2.171" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 241 | 242 | [[package]] 243 | name = "linux-raw-sys" 244 | version = "0.9.3" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" 247 | 248 | [[package]] 249 | name = "log" 250 | version = "0.4.27" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 253 | 254 | [[package]] 255 | name = "memchr" 256 | version = "2.7.4" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 259 | 260 | [[package]] 261 | name = "num-traits" 262 | version = "0.2.19" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 265 | dependencies = [ 266 | "autocfg", 267 | ] 268 | 269 | [[package]] 270 | name = "once_cell" 271 | version = "1.21.3" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 274 | 275 | [[package]] 276 | name = "palette" 277 | version = "0.7.6" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" 280 | dependencies = [ 281 | "approx", 282 | "fast-srgb8", 283 | "palette_derive", 284 | "phf", 285 | ] 286 | 287 | [[package]] 288 | name = "palette_derive" 289 | version = "0.7.6" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" 292 | dependencies = [ 293 | "by_address", 294 | "proc-macro2", 295 | "quote", 296 | "syn", 297 | ] 298 | 299 | [[package]] 300 | name = "phf" 301 | version = "0.11.3" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" 304 | dependencies = [ 305 | "phf_macros", 306 | "phf_shared", 307 | ] 308 | 309 | [[package]] 310 | name = "phf_generator" 311 | version = "0.11.3" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" 314 | dependencies = [ 315 | "phf_shared", 316 | "rand", 317 | ] 318 | 319 | [[package]] 320 | name = "phf_macros" 321 | version = "0.11.3" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" 324 | dependencies = [ 325 | "phf_generator", 326 | "phf_shared", 327 | "proc-macro2", 328 | "quote", 329 | "syn", 330 | ] 331 | 332 | [[package]] 333 | name = "phf_shared" 334 | version = "0.11.3" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 337 | dependencies = [ 338 | "siphasher", 339 | ] 340 | 341 | [[package]] 342 | name = "proc-macro2" 343 | version = "1.0.94" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 346 | dependencies = [ 347 | "unicode-ident", 348 | ] 349 | 350 | [[package]] 351 | name = "quote" 352 | version = "1.0.40" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 355 | dependencies = [ 356 | "proc-macro2", 357 | ] 358 | 359 | [[package]] 360 | name = "r-efi" 361 | version = "5.2.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 364 | 365 | [[package]] 366 | name = "rand" 367 | version = "0.8.5" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 370 | dependencies = [ 371 | "rand_core", 372 | ] 373 | 374 | [[package]] 375 | name = "rand_core" 376 | version = "0.6.4" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 379 | 380 | [[package]] 381 | name = "regex" 382 | version = "1.11.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 385 | dependencies = [ 386 | "aho-corasick", 387 | "memchr", 388 | "regex-automata", 389 | "regex-syntax", 390 | ] 391 | 392 | [[package]] 393 | name = "regex-automata" 394 | version = "0.4.9" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 397 | dependencies = [ 398 | "aho-corasick", 399 | "memchr", 400 | "regex-syntax", 401 | ] 402 | 403 | [[package]] 404 | name = "regex-syntax" 405 | version = "0.8.5" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 408 | 409 | [[package]] 410 | name = "rustix" 411 | version = "1.0.3" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" 414 | dependencies = [ 415 | "bitflags", 416 | "errno", 417 | "libc", 418 | "linux-raw-sys", 419 | "windows-sys", 420 | ] 421 | 422 | [[package]] 423 | name = "ryu" 424 | version = "1.0.20" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 427 | 428 | [[package]] 429 | name = "serde" 430 | version = "1.0.219" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 433 | dependencies = [ 434 | "serde_derive", 435 | ] 436 | 437 | [[package]] 438 | name = "serde_derive" 439 | version = "1.0.219" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 442 | dependencies = [ 443 | "proc-macro2", 444 | "quote", 445 | "syn", 446 | ] 447 | 448 | [[package]] 449 | name = "serde_json" 450 | version = "1.0.140" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 453 | dependencies = [ 454 | "itoa", 455 | "memchr", 456 | "ryu", 457 | "serde", 458 | ] 459 | 460 | [[package]] 461 | name = "serde_spanned" 462 | version = "0.6.8" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 465 | dependencies = [ 466 | "serde", 467 | ] 468 | 469 | [[package]] 470 | name = "siphasher" 471 | version = "1.0.1" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 474 | 475 | [[package]] 476 | name = "syn" 477 | version = "2.0.100" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 480 | dependencies = [ 481 | "proc-macro2", 482 | "quote", 483 | "unicode-ident", 484 | ] 485 | 486 | [[package]] 487 | name = "tempfile" 488 | version = "3.19.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 491 | dependencies = [ 492 | "fastrand", 493 | "getrandom 0.3.2", 494 | "once_cell", 495 | "rustix", 496 | "windows-sys", 497 | ] 498 | 499 | [[package]] 500 | name = "thiserror" 501 | version = "2.0.12" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 504 | dependencies = [ 505 | "thiserror-impl", 506 | ] 507 | 508 | [[package]] 509 | name = "thiserror-impl" 510 | version = "2.0.12" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 513 | dependencies = [ 514 | "proc-macro2", 515 | "quote", 516 | "syn", 517 | ] 518 | 519 | [[package]] 520 | name = "toml" 521 | version = "0.8.20" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" 524 | dependencies = [ 525 | "serde", 526 | "serde_spanned", 527 | "toml_datetime", 528 | "toml_edit", 529 | ] 530 | 531 | [[package]] 532 | name = "toml_datetime" 533 | version = "0.6.8" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 536 | dependencies = [ 537 | "serde", 538 | ] 539 | 540 | [[package]] 541 | name = "toml_edit" 542 | version = "0.22.24" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" 545 | dependencies = [ 546 | "indexmap", 547 | "serde", 548 | "serde_spanned", 549 | "toml_datetime", 550 | "winnow", 551 | ] 552 | 553 | [[package]] 554 | name = "tracks_rs" 555 | version = "0.2.0" 556 | source = "git+https://github.com/Futuremappermydud/tracks-rs.git?branch=tracks#f89a6efc76b48c9b287310f34b15832a68701b19" 557 | dependencies = [ 558 | "ahash", 559 | "cbindgen", 560 | "cfg-if", 561 | "glam", 562 | "itertools", 563 | "log", 564 | "palette", 565 | "serde_json", 566 | "thiserror", 567 | "uuid", 568 | ] 569 | 570 | [[package]] 571 | name = "tracks_rs_link" 572 | version = "0.1.0" 573 | dependencies = [ 574 | "android_logger", 575 | "cbindgen", 576 | "ctor", 577 | "log", 578 | "tracks_rs", 579 | ] 580 | 581 | [[package]] 582 | name = "unicode-ident" 583 | version = "1.0.18" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 586 | 587 | [[package]] 588 | name = "uuid" 589 | version = "1.16.0" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" 592 | dependencies = [ 593 | "getrandom 0.3.2", 594 | ] 595 | 596 | [[package]] 597 | name = "version_check" 598 | version = "0.9.5" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 601 | 602 | [[package]] 603 | name = "wasi" 604 | version = "0.11.0+wasi-snapshot-preview1" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 607 | 608 | [[package]] 609 | name = "wasi" 610 | version = "0.14.2+wasi-0.2.4" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 613 | dependencies = [ 614 | "wit-bindgen-rt", 615 | ] 616 | 617 | [[package]] 618 | name = "windows-sys" 619 | version = "0.59.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 622 | dependencies = [ 623 | "windows-targets", 624 | ] 625 | 626 | [[package]] 627 | name = "windows-targets" 628 | version = "0.52.6" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 631 | dependencies = [ 632 | "windows_aarch64_gnullvm", 633 | "windows_aarch64_msvc", 634 | "windows_i686_gnu", 635 | "windows_i686_gnullvm", 636 | "windows_i686_msvc", 637 | "windows_x86_64_gnu", 638 | "windows_x86_64_gnullvm", 639 | "windows_x86_64_msvc", 640 | ] 641 | 642 | [[package]] 643 | name = "windows_aarch64_gnullvm" 644 | version = "0.52.6" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 647 | 648 | [[package]] 649 | name = "windows_aarch64_msvc" 650 | version = "0.52.6" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 653 | 654 | [[package]] 655 | name = "windows_i686_gnu" 656 | version = "0.52.6" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 659 | 660 | [[package]] 661 | name = "windows_i686_gnullvm" 662 | version = "0.52.6" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 665 | 666 | [[package]] 667 | name = "windows_i686_msvc" 668 | version = "0.52.6" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 671 | 672 | [[package]] 673 | name = "windows_x86_64_gnu" 674 | version = "0.52.6" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 677 | 678 | [[package]] 679 | name = "windows_x86_64_gnullvm" 680 | version = "0.52.6" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 683 | 684 | [[package]] 685 | name = "windows_x86_64_msvc" 686 | version = "0.52.6" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 689 | 690 | [[package]] 691 | name = "winnow" 692 | version = "0.7.4" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" 695 | dependencies = [ 696 | "memchr", 697 | ] 698 | 699 | [[package]] 700 | name = "wit-bindgen-rt" 701 | version = "0.39.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 704 | dependencies = [ 705 | "bitflags", 706 | ] 707 | 708 | [[package]] 709 | name = "zerocopy" 710 | version = "0.7.35" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 713 | dependencies = [ 714 | "zerocopy-derive", 715 | ] 716 | 717 | [[package]] 718 | name = "zerocopy-derive" 719 | version = "0.7.35" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 722 | dependencies = [ 723 | "proc-macro2", 724 | "quote", 725 | "syn", 726 | ] 727 | -------------------------------------------------------------------------------- /tracks_rs_link/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tracks_rs_link" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | android_logger = "0.15.0" 8 | ctor = "0.4.1" 9 | log = "0.4.27" 10 | # todo: use version 11 | tracks_rs = { git = "https://github.com/Futuremappermydud/tracks-rs.git", branch = "tracks", features = [ 12 | "ffi", 13 | ] } 14 | 15 | [lib] 16 | crate-type = ["staticlib"] 17 | 18 | [build-dependencies] 19 | cbindgen = { version = "0.28.0", default-features = false } 20 | 21 | [profile.release] 22 | lto = "fat" 23 | opt-level = 3 24 | strip = "none" 25 | -------------------------------------------------------------------------------- /tracks_rs_link/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cbindgen; 2 | 3 | fn main() { 4 | { 5 | use std::env; 6 | let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 7 | 8 | cbindgen::Builder::new() 9 | .with_crate_and_name(crate_dir, "tracks_rs") 10 | // .with_parse_deps(true) 11 | .with_only_target_dependencies(true) 12 | .with_language(cbindgen::Language::C) 13 | .with_namespaces(&["Tracks", "ffi"]) 14 | .with_cpp_compat(true) 15 | .with_pragma_once(true) 16 | .generate() 17 | .expect("Unable to generate bindings") 18 | .write_to_file("../shared/bindings.h"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tracks_rs_link/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /tracks_rs_link/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub extern crate tracks_rs; 2 | 3 | use log::{error, info, LevelFilter}; 4 | use std::panic::PanicHookInfo; 5 | use android_logger::Config; 6 | use std::backtrace::Backtrace; 7 | 8 | #[ctor::ctor] 9 | fn main() { 10 | android_logger::init_once(Config::default().with_max_level(LevelFilter::Trace)); 11 | 12 | std::panic::set_hook(panic_hook(true, true)); 13 | } 14 | 15 | /// Returns a panic handler, optionally with backtrace and spantrace capture. 16 | pub fn panic_hook( 17 | backtrace: bool, 18 | spantrace: bool, 19 | ) -> Box { 20 | // Mostly taken from https://doc.rust-lang.org/src/std/panicking.rs.html 21 | Box::new(move |info| { 22 | let location = info.location().unwrap(); 23 | let msg = match info.payload().downcast_ref::<&'static str>() { 24 | Some(s) => *s, 25 | None => match info.payload().downcast_ref::() { 26 | Some(s) => &s[..], 27 | None => "Box", 28 | }, 29 | }; 30 | 31 | info!(target: "panic", "panicked at '{}', {}", msg, location); 32 | if backtrace { 33 | error!(target: "panic", "{:?}", Backtrace::force_capture()); 34 | } 35 | if spantrace { 36 | // error!(target: "panic", "{:?}", SpanTrace::capture()); 37 | } 38 | }) 39 | } 40 | --------------------------------------------------------------------------------