├── .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