├── .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 ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include ├── CustomJSONDataHooks.h └── HookUtils.hpp ├── mod.template.json ├── qpm.json ├── qpm.shared.json ├── scripts ├── build.ps1 ├── copy.ps1 ├── createqmod.ps1 ├── decompile.sh ├── ndk-stack.ps1 ├── pull-tombstone.ps1 ├── restart-game.ps1 ├── start-logging.ps1 └── validate-modjson.ps1 ├── shared ├── CJDLogger.h ├── CustomBeatmapData.h ├── CustomBeatmapSaveDatav2.h ├── CustomBeatmapSaveDatav3.h ├── CustomEventData.h ├── JSONWrapper.h ├── JsonUtils.h ├── LowLevelUtils.hpp ├── VList.h ├── _config.hpp └── misc │ ├── BeatmapDataLoaderUtils.hpp │ └── BeatmapFieldUtils.hpp └── src ├── CustomBeatmapData.cpp ├── CustomBeatmapSaveDatav2.cpp ├── CustomBeatmapSaveDatav3.cpp ├── CustomEventData.cpp ├── JSONWrapper.cpp ├── hooks ├── BeatmapHooks.cpp ├── CustomJSONDataHooks.cpp ├── V2Hooks.cpp └── V3Hooks.cpp └── main.cpp /.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: custom-json-data 5 | qmodName: custom-json-data 6 | 7 | on: 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | name: Checkout 20 | with: 21 | submodules: true 22 | lfs: true 23 | 24 | - uses: seanmiddleditch/gha-setup-ninja@v3 25 | 26 | 27 | 28 | # - name: Create ndkpath.txt 29 | # run: | 30 | # echo "$ANDROID_NDK_LATEST_HOME" > ${GITHUB_WORKSPACE}/ndkpath.txt 31 | # cat ${GITHUB_WORKSPACE}/ndkpath.txt 32 | 33 | - name: QPM Rust Action 34 | uses: Fernthedev/qpm-rust-action@v1 35 | with: 36 | #required 37 | workflow_token: ${{secrets.GITHUB_TOKEN}} 38 | 39 | restore: true # will run restore on download 40 | cache: true #will cache dependencies 41 | 42 | # Name of qmod in release asset. Assumes exists, same as prior 43 | qpm_qmod: ${{env.qmodName}}.qmod 44 | 45 | - name: QPM Collapse 46 | run: | 47 | qpm-rust collapse 48 | 49 | - name: Build 50 | run: | 51 | cd ${GITHUB_WORKSPACE} 52 | qpm-rust s build 53 | 54 | - name: Create Qmod 55 | run: | 56 | qpm-rust qmod zip 57 | 58 | - name: Get Library Name 59 | id: libname 60 | run: | 61 | cd ./build/ 62 | pattern="lib${module_id}*.so" 63 | files=( $pattern ) 64 | echo ::set-output name=NAME::"${files[0]}" 65 | - name: Upload non-debug artifact 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: ${{ steps.libname.outputs.NAME }} 69 | path: ./build/${{ steps.libname.outputs.NAME }} 70 | if-no-files-found: error 71 | 72 | - name: Upload qmod artifact 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: ${{env.qmodName}}.qmod 76 | path: ./${{ env.qmodName }}.qmod 77 | if-no-files-found: error -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish QPM Package 2 | 3 | env: 4 | module_id: custom-json-data 5 | qmodName: custom-json-data 6 | cache-name: custom-json-data_cache 7 | 8 | on: 9 | push: 10 | tags: 11 | - 'v*' 12 | 13 | permissions: 14 | pull-requests: write 15 | contents: write 16 | 17 | jobs: 18 | publish: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | name: Checkout 24 | with: 25 | submodules: true 26 | lfs: true 27 | 28 | - uses: seanmiddleditch/gha-setup-ninja@v3 29 | 30 | 31 | # - name: Create ndkpath.txt 32 | # run: | 33 | # echo "$ANDROID_NDK_LATEST_HOME" > ${GITHUB_WORKSPACE}/ndkpath.txt 34 | # cat ${GITHUB_WORKSPACE}/ndkpath.txt 35 | 36 | - name: Get Tag Version 37 | id: get_tag_version 38 | run: | 39 | echo ${GITHUB_REF#refs/tags/} 40 | echo ::set-output name=TAG::${GITHUB_REF#refs/tags/} 41 | echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} 42 | 43 | - name: QPM Rust Action 44 | uses: Fernthedev/qpm-rust-action@v1 45 | with: 46 | #required 47 | workflow_token: ${{secrets.GITHUB_TOKEN}} 48 | 49 | restore: true # will run restore on download 50 | cache: true #will cache dependencies 51 | 52 | publish: "late" 53 | publish_token: ${{secrets.QPM_TOKEN}} 54 | 55 | version: ${{ steps.get_tag_version.outputs.VERSION }} 56 | tag: ${{ steps.get_tag_version.outputs.TAG }} 57 | 58 | # set to true if applicable, ASSUMES the file is already a release asset 59 | qpm_release_bin: true 60 | qpm_debug_bin: true 61 | 62 | # Name of qmod in release asset. Assumes exists, same as prior 63 | qpm_qmod: ${{env.qmodName}}.qmod 64 | 65 | - name: Build 66 | run: | 67 | cd ${GITHUB_WORKSPACE} 68 | qpm s build 69 | 70 | - name: Create Qmod 71 | run: | 72 | qpm qmod zip 73 | 74 | - name: Get Library Name 75 | id: libname 76 | run: | 77 | cd ./build/ 78 | pattern="lib${module_id}*.so" 79 | files=( $pattern ) 80 | echo ::set-output name=NAME::"${files[0]}" 81 | 82 | - name: Rename debug 83 | run: | 84 | mv ./build/debug/${{ steps.libname.outputs.NAME }} ./build/debug/debug_${{ steps.libname.outputs.NAME }} 85 | 86 | 87 | - name: Create Qmod 88 | run: | 89 | pwsh -Command ./scripts/createqmod.ps1 ${{env.qmodName}} 90 | 91 | - name: Upload to Release 92 | id: upload_file_release 93 | uses: softprops/action-gh-release@v0.1.15 94 | with: 95 | name: ${{ github.event.inputs.release_msg }} 96 | tag_name: ${{ github.event.inputs.version }} 97 | files: | 98 | ./${{ env.qmodName }}.qmod 99 | ./build/${{ steps.libname.outputs.NAME }} 100 | ./build/debug/debug_${{ steps.libname.outputs.NAME }} 101 | env: 102 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 103 | 104 | - name: Make PR to QeatMods3 105 | id: qmod-release 106 | uses: QuestPackageManager/qmod-repo-publish-action@main 107 | # TODO: Make this work! 108 | continue-on-error: true 109 | with: 110 | token: ${{secrets.GITHUB_TOKEN}} 111 | # first asset URL 112 | qmod_url: ${{ fromJSON(steps.upload_file_release.outputs.assets)[0].browser_download_url }} 113 | qmod_repo_owner: 'dantheman827' 114 | qmod_repo_name: 'bsqmods' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | 3 | # Prerequisites 4 | *.d 5 | 6 | # Compiled Object files 7 | *.slo 8 | *.lo 9 | *.o 10 | *.obj 11 | 12 | # Precompiled Headers 13 | *.gch 14 | *.pch 15 | 16 | # Compiled Dynamic libraries 17 | *.so 18 | *.dylib 19 | *.dll 20 | 21 | # Fortran module files 22 | *.mod 23 | *.smod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | 36 | # VSCode config stuff 37 | !.vscode/c_cpp_properties.json 38 | !.vscode/tasks.json 39 | !.vscode/settings.json 40 | 41 | # Jetbrains IDEs 42 | .idea/ 43 | 44 | # NDK stuff 45 | out/ 46 | [Ll]ib/ 47 | [Ll]ibs/ 48 | [Oo]bj/ 49 | [Oo]bjs/ 50 | ndkpath.txt 51 | *.zip 52 | *.txt 53 | *.log 54 | Android.mk.backup 55 | 56 | # QPM stuff 57 | [Ee]xtern/ 58 | *.qmod 59 | mod.json 60 | qpm_defines.cmake 61 | ![Cc][Mm]ake[Ll]ists.txt 62 | 63 | # CMake stuff 64 | [Bb]uild/ 65 | cmake-build-*/ 66 | extern.cmake 67 | 68 | # QMOD Schema 69 | mod.json.schema -------------------------------------------------------------------------------- /.idea/cmake.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "defines": [ 5 | "MOD_ID=\"custom-json-data\"", 6 | "VERSION=\"0.1.0\"", 7 | "__GNUC__", 8 | "__aarch64__" 9 | ], 10 | "includePath": [ 11 | "${workspaceFolder}/extern/includes/libil2cpp/il2cpp/libil2cpp", 12 | "${workspaceFolder}/extern/includes/codegen/include", 13 | "${workspaceFolder}/extern/includes", 14 | "${workspaceFolder}/include", 15 | "${workspaceFolder}/shared", 16 | "${workspaceFolder}", 17 | 18 | "D:\DevTools\android-ndk-r27\android-ndk-r27-canary/**", 19 | "${default}" 20 | ], 21 | "name": "Quest", 22 | "cStandard": "c11", 23 | "cppStandard": "c++20", 24 | "intelliSenseMode": "clang-x64", 25 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 26 | } 27 | ], 28 | "version": 4 29 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Beat Saber", 6 | "type": "fb-lldb", 7 | "request": "launch", 8 | "preLaunchTask": "Powershell Build and Copy", 9 | "android": { 10 | "application": { 11 | "package": "com.beatgames.beatsaber", 12 | "activity": "com.unity3d.player.UnityPlayerActivity" 13 | }, 14 | "lldbConfig": { 15 | "sourceMaps": [], 16 | "librarySearchPaths": [ 17 | "${workspaceFolder}/build/debug/", 18 | "${workspaceFolder}/extern/libs/" 19 | ] 20 | } 21 | } 22 | }, 23 | { 24 | "name": "Attach to running Beat Saber Instance", 25 | "type": "fb-lldb", 26 | "request": "attach", 27 | "android": { 28 | "application": { 29 | "package": "com.beatgames.beatsaber", 30 | "activity": "com.unity3d.player.UnityPlayerActivity" 31 | }, 32 | "lldbConfig": { 33 | "sourceMaps": [], 34 | "librarySearchPaths": [ 35 | "${workspaceFolder}/build/debug/", 36 | "${workspaceFolder}/extern/libs/", 37 | ] 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.bsml": "xml", 4 | "iosfwd": "cpp", 5 | "__config": "cpp", 6 | "__nullptr": "cpp", 7 | "thread": "cpp", 8 | "any": "cpp", 9 | "deque": "cpp", 10 | "list": "cpp", 11 | "map": "cpp", 12 | "optional": "cpp", 13 | "queue": "cpp", 14 | "set": "cpp", 15 | "stack": "cpp", 16 | "unordered_map": "cpp", 17 | "unordered_set": "cpp", 18 | "variant": "cpp", 19 | "vector": "cpp", 20 | "__bit_reference": "cpp", 21 | "__debug": "cpp", 22 | "__errc": "cpp", 23 | "__functional_base": "cpp", 24 | "__hash_table": "cpp", 25 | "__locale": "cpp", 26 | "__mutex_base": "cpp", 27 | "__node_handle": "cpp", 28 | "__split_buffer": "cpp", 29 | "__string": "cpp", 30 | "__threading_support": "cpp", 31 | "__tree": "cpp", 32 | "__tuple": "cpp", 33 | "algorithm": "cpp", 34 | "array": "cpp", 35 | "atomic": "cpp", 36 | "bit": "cpp", 37 | "bitset": "cpp", 38 | "cctype": "cpp", 39 | "cfenv": "cpp", 40 | "charconv": "cpp", 41 | "chrono": "cpp", 42 | "cinttypes": "cpp", 43 | "clocale": "cpp", 44 | "cmath": "cpp", 45 | "codecvt": "cpp", 46 | "compare": "cpp", 47 | "complex": "cpp", 48 | "condition_variable": "cpp", 49 | "csetjmp": "cpp", 50 | "csignal": "cpp", 51 | "cstdarg": "cpp", 52 | "cstddef": "cpp", 53 | "cstdint": "cpp", 54 | "cstdio": "cpp", 55 | "cstdlib": "cpp", 56 | "cstring": "cpp", 57 | "ctime": "cpp", 58 | "cwchar": "cpp", 59 | "cwctype": "cpp", 60 | "exception": "cpp", 61 | "coroutine": "cpp", 62 | "propagate_const": "cpp", 63 | "forward_list": "cpp", 64 | "fstream": "cpp", 65 | "functional": "cpp", 66 | "future": "cpp", 67 | "initializer_list": "cpp", 68 | "iomanip": "cpp", 69 | "ios": "cpp", 70 | "iostream": "cpp", 71 | "istream": "cpp", 72 | "iterator": "cpp", 73 | "limits": "cpp", 74 | "locale": "cpp", 75 | "memory": "cpp", 76 | "mutex": "cpp", 77 | "new": "cpp", 78 | "numeric": "cpp", 79 | "ostream": "cpp", 80 | "random": "cpp", 81 | "ratio": "cpp", 82 | "regex": "cpp", 83 | "scoped_allocator": "cpp", 84 | "span": "cpp", 85 | "sstream": "cpp", 86 | "stdexcept": "cpp", 87 | "streambuf": "cpp", 88 | "string": "cpp", 89 | "string_view": "cpp", 90 | "strstream": "cpp", 91 | "system_error": "cpp", 92 | "tuple": "cpp", 93 | "type_traits": "cpp", 94 | "typeindex": "cpp", 95 | "typeinfo": "cpp", 96 | "utility": "cpp", 97 | "valarray": "cpp", 98 | "xstring": "cpp", 99 | "xlocale": "cpp", 100 | "xlocbuf": "cpp", 101 | "concepts": "cpp", 102 | "filesystem": "cpp", 103 | "shared_mutex": "cpp", 104 | "xfacet": "cpp", 105 | "xhash": "cpp", 106 | "xiosbase": "cpp", 107 | "xlocinfo": "cpp", 108 | "xlocmes": "cpp", 109 | "xlocmon": "cpp", 110 | "xlocnum": "cpp", 111 | "xloctime": "cpp", 112 | "xmemory": "cpp", 113 | "xstddef": "cpp", 114 | "xtr1common": "cpp", 115 | "xtree": "cpp", 116 | "xutility": "cpp", 117 | "format": "cpp", 118 | "ranges": "cpp", 119 | "stop_token": "cpp" 120 | } 121 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "NDK Build", 6 | "detail": "Builds the library using ndk-build.cmd", 7 | "type": "shell", 8 | "command": "ndk-build", 9 | "windows": { 10 | "command": "ndk-build.cmd" 11 | }, 12 | "args": ["NDK_PROJECT_PATH=.", "APP_BUILD_SCRIPT=./Android.mk", "NDK_APPLICATION_MK=./Application.mk"], 13 | "group": "build", 14 | "options": { 15 | "env": {} 16 | } 17 | }, 18 | { 19 | "label": "Powershell Build", 20 | "detail": "Builds the library using Powershell (recommended)", 21 | "type": "shell", 22 | "command": "./build.ps1", 23 | "windows": { 24 | "command": "./build.ps1" 25 | }, 26 | "group": { 27 | "kind": "build", 28 | "isDefault": true 29 | }, 30 | "options": { 31 | "env": {} 32 | } 33 | }, 34 | { 35 | "label": "Powershell Build and Copy", 36 | "detail": "Builds and copies the library to the Quest using ADB and force-quits Beat Saber", 37 | "type": "shell", 38 | "command": "./copy.ps1", 39 | "windows": { 40 | "command": "./copy.ps1" 41 | }, 42 | "group": "build", 43 | "options": { 44 | "env": {} 45 | } 46 | }, 47 | { 48 | "label": "QMOD Build", 49 | "detail": "Builds a .qmod to be installed into BMBF or QuestPatcher", 50 | "type": "shell", 51 | "command": "./buildQMOD.ps1", 52 | "windows": { 53 | "command": "./buildQMOD.ps1" 54 | }, 55 | "args": [], 56 | "group": "build", 57 | "options": { 58 | "env": {} 59 | } 60 | }, 61 | { 62 | "label": "Start logging", 63 | "detail": "Begin logging from the Quest to the console", 64 | "type": "shell", 65 | "command": "./start-logging.ps1", 66 | "windows": { 67 | "command": "./start-logging.ps1" 68 | } 69 | }, 70 | { 71 | "label": "Start logging to file", 72 | "detail": "Begin logging from the Quest to the console and saving output to a file 'logcat.log'", 73 | "type": "shell", 74 | "command": "./start-logging.ps1 --file", 75 | "windows": { 76 | "command": "./start-logging.ps1 --file" 77 | } 78 | }, 79 | { 80 | "label": "Restart Beat Saber", 81 | "detail": "Force-quits and restarts Beat Saber on the Quest", 82 | "type": "shell", 83 | "command": "./start-logging.ps1", 84 | "windows": { 85 | "command": "./start-logging.ps1" 86 | } 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # include some defines automatically made by qpm 2 | include(qpm_defines.cmake) 3 | 4 | # override mod id 5 | set(MOD_ID "custom-json-data") 6 | 7 | # Enable link time optimization 8 | # In my experience, this can be highly unstable but it nets a huge size optimization and likely performance 9 | # However, the instability was seen using Android.mk/ndk-build builds. With Ninja + CMake, this problem seems to have been solved. 10 | # As always, test thoroughly 11 | # - Fern 12 | # set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 13 | 14 | cmake_minimum_required(VERSION 3.21) 15 | project(${COMPILE_ID}) 16 | 17 | # export compile commands for significantly better intellisense 18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 19 | 20 | # c++ standard 21 | set(CMAKE_CXX_STANDARD 20) 22 | set(CMAKE_CXX_STANDARD_REQUIRED 20) 23 | 24 | # define that stores the actual source directory 25 | set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) 26 | set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) 27 | 28 | # compile options used 29 | add_compile_options(-frtti -fexceptions) 30 | add_compile_options(-O3) 31 | # compile definitions used 32 | add_compile_definitions(VERSION=\"${MOD_VERSION}\") 33 | add_compile_definitions(MOD_ID=\"${MOD_ID}\") 34 | 35 | # recursively get all src files 36 | RECURSE_FILES(cpp_file_list ${SOURCE_DIR}/*.cpp) 37 | RECURSE_FILES(c_file_list ${SOURCE_DIR}/*.c) 38 | 39 | RECURSE_FILES(inline_hook_c ${EXTERN_DIR}/includes/beatsaber-hook/shared/inline-hook/*.c) 40 | RECURSE_FILES(inline_hook_cpp ${EXTERN_DIR}/includes/beatsaber-hook/shared/inline-hook/*.cpp) 41 | 42 | # add all src files to compile 43 | add_library( 44 | ${COMPILE_ID} 45 | SHARED 46 | ${cpp_file_list} 47 | ${c_file_list} 48 | ${inline_hook_c} 49 | ${inline_hook_cpp} 50 | ) 51 | 52 | target_include_directories(${COMPILE_ID} PRIVATE .) 53 | 54 | # add src dir as include dir 55 | target_include_directories(${COMPILE_ID} PRIVATE ${SOURCE_DIR}) 56 | # add include dir as include dir 57 | target_include_directories(${COMPILE_ID} PRIVATE ${INCLUDE_DIR}) 58 | # add shared dir as include dir 59 | target_include_directories(${COMPILE_ID} PUBLIC ${SHARED_DIR}) 60 | # codegen includes 61 | target_include_directories(${COMPILE_ID} PRIVATE ${EXTERN_DIR}/includes/${CODEGEN_ID}/include) 62 | 63 | target_link_libraries(${COMPILE_ID} PRIVATE -llog) 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 "Make directory for 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 | # strip debug symbols from the .so and all dependencies 83 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 84 | COMMAND ${CMAKE_COMMAND} -E rename stripped_lib${COMPILE_ID}.so lib${COMPILE_ID}.so 85 | COMMENT "Rename the stripped lib to regular" 86 | ) 87 | foreach(so_file ${so_list}) 88 | cmake_path(GET so_file FILENAME file) 89 | 90 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 91 | COMMAND ${CMAKE_COMMAND} -E copy ${so_file} debug/${file} 92 | COMMENT "Copy so files for ndk stack" 93 | ) 94 | 95 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 96 | COMMAND ${CMAKE_STRIP} -g -S -d --strip-all ${so_file} -o ${file} 97 | COMMENT "Strip debug symbols from the dependencies") 98 | endforeach() 99 | 100 | foreach(a_file ${a_list}) 101 | cmake_path(GET a_file FILENAME file) 102 | 103 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 104 | COMMAND ${CMAKE_COMMAND} -E copy ${a_file} debug/${file} 105 | COMMENT "Copy a files for ndk stack") 106 | endforeach() 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 StackDoubleFlow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CustomJSONData 2 | 3 | Funny maps go brrr 4 | 5 | Use `qpm-rust s build` to build 6 | Same goes for `qpm-rust s copy` and `qpm-rust s qmod` 7 | 8 | ## Credits 9 | 10 | * [zoller27osu](https://github.com/zoller27osu), [Sc2ad](https://github.com/Sc2ad) and [jakibaki](https://github.com/jakibaki) - [beatsaber-hook](https://github.com/sc2ad/beatsaber-hook) 11 | * [raftario](https://github.com/raftario) 12 | * [Lauriethefish](https://github.com/Lauriethefish), [danrouse](https://github.com/danrouse) and [Bobby Shmurner](https://github.com/BobbyShmurner) for [this template](https://github.com/Lauriethefish/quest-mod-template) 13 | -------------------------------------------------------------------------------- /include/CustomJSONDataHooks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "beatsaber-hook/shared/utils/logging.hpp" 4 | #include "beatsaber-hook/shared/utils/hooking.hpp" 5 | #include "CJDLogger.h" 6 | 7 | namespace CustomJSONData { 8 | 9 | void InstallHooks(); 10 | 11 | void InstallBeatmapHooks(); 12 | 13 | namespace v2 { 14 | void InstallHooks(); 15 | } 16 | 17 | namespace v3 { 18 | void InstallHooks(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /include/HookUtils.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 6 | 7 | #include "misc/BeatmapDataLoaderUtils.hpp" 8 | #include "misc/BeatmapFieldUtils.hpp" 9 | 10 | // for rapidjson error parsing 11 | #include "beatsaber-hook/shared/rapidjson/include/rapidjson/error/en.h" 12 | 13 | namespace CustomJSONData { 14 | 15 | template constexpr void addAllToVector(std::vector& vec, auto const& listPtr) { 16 | ListW::value_type> vList(listPtr); 17 | 18 | std::copy(vList.begin(), vList.end(), std::back_inserter(vec)); 19 | }; 20 | 21 | template constexpr void sortInPlace(std::span vec) { 22 | std::stable_sort(vec.begin(), vec.end(), TimeCompare::const_reference>); 23 | }; 24 | 25 | template constexpr void cleanAndSort(std::vector& vec) { 26 | // remove nulls 27 | for (auto it = vec.begin(); it != vec.end();) { 28 | auto const& v = *it; 29 | if (!v) { 30 | it = vec.erase(it); 31 | continue; 32 | } 33 | 34 | it++; 35 | } 36 | 37 | sortInPlace({vec.begin(), vec.end()}); 38 | }; 39 | 40 | static std::optional> parseDocument(std::string_view stringData) { 41 | auto sharedDoc = std::make_shared(); 42 | rapidjson::Document& doc = *sharedDoc; 43 | rapidjson::ParseResult result = doc.Parse(stringData.data()); 44 | 45 | if (!result || doc.IsNull() || doc.HasParseError()) { 46 | std::string errorCodeStr(rapidjson::GetParseError_En(result.Code())); 47 | CJDLogger::Logger.fmtLog("Unable to parse json due to {}", errorCodeStr); 48 | return std::nullopt; 49 | } 50 | 51 | CJDLogger::Logger.fmtLog("Parsing json success"); 52 | 53 | return sharedDoc; 54 | } 55 | 56 | static std::string GetVersionFromPath(std::string_view path) { 57 | // SongCore has a fallback so i guess i do too 58 | static std::string_view const fallback = "2.0.0"; 59 | 60 | auto truncatedText = path.substr(0, 50); 61 | static std::regex const versionRegex(R"("_?version"\s*:\s*"[0-9]+\.[0-9]+\.?[0-9]?")"); 62 | std::match_results matches; 63 | if (std::regex_search(truncatedText.begin(), truncatedText.end(), matches, versionRegex)) { 64 | if (!matches.empty()) { 65 | auto version = matches[0].str(); 66 | version = version.substr(0, version.length() - 1); 67 | version = version.substr(version.find_last_of('\"') + 1, version.length()); 68 | 69 | return version; 70 | } 71 | } 72 | 73 | return std::string(fallback); 74 | } 75 | } // namespace CustomJSONData -------------------------------------------------------------------------------- /mod.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/Lauriethefish/QuestPatcher.QMod/main/QuestPatcher.QMod/Resources/qmod.schema.json", 3 | "_QPVersion": "0.1.2", 4 | "name": "${mod_name}", 5 | "id": "${mod_id}", 6 | "author": "StackDoubleFlow, Fernthedev", 7 | "version": "${version}", 8 | "packageId": "com.beatgames.beatsaber", 9 | "packageVersion": "1.40.4_5283", 10 | "description": "Funny maps go brrr", 11 | "dependencies": [], 12 | "modFiles": [], 13 | "lateModFiles": ["${binary}"], 14 | "libraryFiles": [], 15 | "fileCopies": [] 16 | } 17 | -------------------------------------------------------------------------------- /qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.4.0", 3 | "sharedDir": "shared", 4 | "dependenciesDir": "extern", 5 | "info": { 6 | "name": "CustomJSONData", 7 | "id": "custom-json-data", 8 | "version": "0.22.0", 9 | "url": "https://github.com/StackDoubleFlow/CustomJSONData", 10 | "additionalData": { 11 | "overrideSoName": "libcustom-json-data.so", 12 | "compileOptions": { 13 | "cppFlags": [ 14 | "-DRAPIDJSON_NEON" 15 | ] 16 | }, 17 | "cmake": true 18 | } 19 | }, 20 | "workspace": { 21 | "scripts": { 22 | "build": [ 23 | "pwsh ./scripts/build.ps1" 24 | ], 25 | "copy": [ 26 | "pwsh ./scripts/copy.ps1" 27 | ], 28 | "log": [ 29 | "pwsh ./scripts/copy.ps1 -log" 30 | ], 31 | "ndk-stack": [ 32 | "pwsh ./scripts/ndk-stack.ps1" 33 | ] 34 | }, 35 | "qmodIncludeDirs": [ 36 | "./build", 37 | "./extern/libs" 38 | ], 39 | "qmodIncludeFiles": [], 40 | "qmodOutput": "custom-json-data.qmod" 41 | }, 42 | "dependencies": [ 43 | { 44 | "id": "beatsaber-hook", 45 | "versionRange": "^6.1.7", 46 | "additionalData": {} 47 | }, 48 | { 49 | "id": "bs-cordl", 50 | "versionRange": "^4004.0.0", 51 | "additionalData": {} 52 | }, 53 | { 54 | "id": "custom-types", 55 | "versionRange": "^0.18.0", 56 | "additionalData": { 57 | "includeQmod": true 58 | } 59 | }, 60 | { 61 | "id": "songcore", 62 | "versionRange": "^1.1.20", 63 | "additionalData": { 64 | "includeQmod": true, 65 | "private": true 66 | } 67 | }, 68 | { 69 | "id": "scotland2", 70 | "versionRange": "^0.1.6", 71 | "additionalData": { 72 | "includeQmod": false 73 | } 74 | }, 75 | { 76 | "id": "paper2_scotland2", 77 | "versionRange": "^4.6.4", 78 | "additionalData": {} 79 | }, 80 | { 81 | "id": "cpp-semver", 82 | "versionRange": "^0.1.1", 83 | "additionalData": { 84 | "private": true 85 | } 86 | }, 87 | { 88 | "id": "sombrero", 89 | "versionRange": "^0.1.43", 90 | "additionalData": { 91 | "private": true 92 | } 93 | } 94 | ] 95 | } -------------------------------------------------------------------------------- /qpm.shared.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/QuestPackageManager/QPM.Package/refs/heads/main/qpm.shared.schema.json", 3 | "config": { 4 | "version": "0.4.0", 5 | "sharedDir": "shared", 6 | "dependenciesDir": "extern", 7 | "info": { 8 | "name": "CustomJSONData", 9 | "id": "custom-json-data", 10 | "version": "0.22.0", 11 | "url": "https://github.com/StackDoubleFlow/CustomJSONData", 12 | "additionalData": { 13 | "overrideSoName": "libcustom-json-data.so", 14 | "compileOptions": { 15 | "cppFlags": [ 16 | "-DRAPIDJSON_NEON" 17 | ] 18 | }, 19 | "cmake": true 20 | } 21 | }, 22 | "workspace": { 23 | "scripts": { 24 | "build": [ 25 | "pwsh ./scripts/build.ps1" 26 | ], 27 | "copy": [ 28 | "pwsh ./scripts/copy.ps1" 29 | ], 30 | "log": [ 31 | "pwsh ./scripts/copy.ps1 -log" 32 | ], 33 | "ndk-stack": [ 34 | "pwsh ./scripts/ndk-stack.ps1" 35 | ] 36 | }, 37 | "qmodIncludeDirs": [ 38 | "./build", 39 | "./extern/libs" 40 | ], 41 | "qmodIncludeFiles": [], 42 | "qmodOutput": "custom-json-data.qmod" 43 | }, 44 | "dependencies": [ 45 | { 46 | "id": "beatsaber-hook", 47 | "versionRange": "^6.1.7", 48 | "additionalData": {} 49 | }, 50 | { 51 | "id": "bs-cordl", 52 | "versionRange": "^4004.0.0", 53 | "additionalData": {} 54 | }, 55 | { 56 | "id": "custom-types", 57 | "versionRange": "^0.18.0", 58 | "additionalData": { 59 | "includeQmod": true 60 | } 61 | }, 62 | { 63 | "id": "songcore", 64 | "versionRange": "^1.1.20", 65 | "additionalData": { 66 | "includeQmod": true, 67 | "private": true 68 | } 69 | }, 70 | { 71 | "id": "scotland2", 72 | "versionRange": "^0.1.6", 73 | "additionalData": { 74 | "includeQmod": false 75 | } 76 | }, 77 | { 78 | "id": "paper2_scotland2", 79 | "versionRange": "^4.6.4", 80 | "additionalData": {} 81 | }, 82 | { 83 | "id": "cpp-semver", 84 | "versionRange": "^0.1.1", 85 | "additionalData": { 86 | "private": true 87 | } 88 | }, 89 | { 90 | "id": "sombrero", 91 | "versionRange": "^0.1.43", 92 | "additionalData": { 93 | "private": true 94 | } 95 | } 96 | ] 97 | }, 98 | "restoredDependencies": [ 99 | { 100 | "dependency": { 101 | "id": "custom-types", 102 | "versionRange": "=0.18.2", 103 | "additionalData": { 104 | "soLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/libcustom-types.so", 105 | "debugSoLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/debug_libcustom-types.so", 106 | "overrideSoName": "libcustom-types.so", 107 | "modLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/CustomTypes.qmod", 108 | "branchName": "version/v0_18_2", 109 | "compileOptions": { 110 | "cppFlags": [ 111 | "-Wno-invalid-offsetof" 112 | ] 113 | }, 114 | "cmake": true 115 | } 116 | }, 117 | "version": "0.18.2" 118 | }, 119 | { 120 | "dependency": { 121 | "id": "sombrero", 122 | "versionRange": "=0.1.43", 123 | "additionalData": { 124 | "headersOnly": true, 125 | "branchName": "version/v0_1_43" 126 | } 127 | }, 128 | "version": "0.1.43" 129 | }, 130 | { 131 | "dependency": { 132 | "id": "bs-cordl", 133 | "versionRange": "=4004.0.0", 134 | "additionalData": { 135 | "headersOnly": true, 136 | "branchName": "version/v4004_0_0", 137 | "compileOptions": { 138 | "includePaths": [ 139 | "include" 140 | ], 141 | "cppFeatures": [], 142 | "cppFlags": [ 143 | "-DNEED_UNSAFE_CSHARP", 144 | "-fdeclspec", 145 | "-DUNITY_2021", 146 | "-DHAS_CODEGEN", 147 | "-Wno-invalid-offsetof" 148 | ] 149 | } 150 | } 151 | }, 152 | "version": "4004.0.0" 153 | }, 154 | { 155 | "dependency": { 156 | "id": "paper2_scotland2", 157 | "versionRange": "=4.6.4", 158 | "additionalData": { 159 | "soLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.4/libpaper2_scotland2.so", 160 | "overrideSoName": "libpaper2_scotland2.so", 161 | "modLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.4/paper2_scotland2.qmod", 162 | "branchName": "version/v4_6_4", 163 | "compileOptions": { 164 | "systemIncludes": [ 165 | "shared/utfcpp/source" 166 | ] 167 | }, 168 | "cmake": false 169 | } 170 | }, 171 | "version": "4.6.4" 172 | }, 173 | { 174 | "dependency": { 175 | "id": "cpp-semver", 176 | "versionRange": "=0.1.2", 177 | "additionalData": { 178 | "headersOnly": true, 179 | "branchName": "version-v0.1.2" 180 | } 181 | }, 182 | "version": "0.1.2" 183 | }, 184 | { 185 | "dependency": { 186 | "id": "fmt", 187 | "versionRange": "=11.0.2", 188 | "additionalData": { 189 | "headersOnly": true, 190 | "branchName": "version/v11_0_2", 191 | "compileOptions": { 192 | "systemIncludes": [ 193 | "fmt/include/" 194 | ], 195 | "cppFlags": [ 196 | "-DFMT_HEADER_ONLY" 197 | ] 198 | } 199 | } 200 | }, 201 | "version": "11.0.2" 202 | }, 203 | { 204 | "dependency": { 205 | "id": "beatsaber-hook", 206 | "versionRange": "=6.4.1", 207 | "additionalData": { 208 | "soLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/libbeatsaber-hook.so", 209 | "debugSoLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/debug_libbeatsaber-hook.so", 210 | "overrideSoName": "libbeatsaber-hook.so", 211 | "modLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v6.4.1/beatsaber-hook.qmod", 212 | "branchName": "version/v6_4_1", 213 | "cmake": true 214 | } 215 | }, 216 | "version": "6.4.1" 217 | }, 218 | { 219 | "dependency": { 220 | "id": "songcore", 221 | "versionRange": "=1.1.20", 222 | "additionalData": { 223 | "soLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/libsongcore.so", 224 | "debugSoLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/debug_libsongcore.so", 225 | "overrideSoName": "libsongcore.so", 226 | "modLink": "https://github.com/raineaeternal/Quest-SongCore/releases/download/v1.1.20/SongCore.qmod", 227 | "branchName": "version/v1_1_20", 228 | "cmake": true 229 | } 230 | }, 231 | "version": "1.1.20" 232 | }, 233 | { 234 | "dependency": { 235 | "id": "scotland2", 236 | "versionRange": "=0.1.6", 237 | "additionalData": { 238 | "soLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.6/libsl2.so", 239 | "debugSoLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.6/debug_libsl2.so", 240 | "overrideSoName": "libsl2.so", 241 | "branchName": "version/v0_1_6" 242 | } 243 | }, 244 | "version": "0.1.6" 245 | }, 246 | { 247 | "dependency": { 248 | "id": "libil2cpp", 249 | "versionRange": "=0.4.0", 250 | "additionalData": { 251 | "headersOnly": true, 252 | "compileOptions": { 253 | "systemIncludes": [ 254 | "il2cpp/external/baselib/Include", 255 | "il2cpp/external/baselib/Platforms/Android/Include" 256 | ] 257 | } 258 | } 259 | }, 260 | "version": "0.4.0" 261 | } 262 | ] 263 | } -------------------------------------------------------------------------------- /scripts/build.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory=$false)] 3 | [Switch] $clean, 4 | 5 | [Parameter(Mandatory=$false)] 6 | [Switch] $help 7 | ) 8 | 9 | if ($help -eq $true) { 10 | Write-Output "`"Build`" - Copiles your mod into a `".so`" or a `".a`" library" 11 | Write-Output "`n-- Arguments --`n" 12 | 13 | Write-Output "-Clean `t`t Deletes the `"build`" folder, so that the entire library is rebuilt" 14 | 15 | exit 16 | } 17 | 18 | # if user specified clean, remove all build files 19 | if ($clean.IsPresent) { 20 | if (Test-Path -Path "build") { 21 | remove-item build -R 22 | } 23 | } 24 | 25 | 26 | if (($clean.IsPresent) -or (-not (Test-Path -Path "build"))) { 27 | new-item -Path build -ItemType Directory 28 | } 29 | 30 | & cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" -B build 31 | & cmake --build ./build 32 | -------------------------------------------------------------------------------- /scripts/copy.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory=$false)] 3 | [Switch] $clean, 4 | 5 | [Parameter(Mandatory=$false)] 6 | [Switch] $log, 7 | 8 | [Parameter(Mandatory=$false)] 9 | [Switch] $useDebug, 10 | 11 | [Parameter(Mandatory=$false)] 12 | [Switch] $self, 13 | 14 | [Parameter(Mandatory=$false)] 15 | [Switch] $all, 16 | 17 | [Parameter(Mandatory=$false)] 18 | [String] $custom="", 19 | 20 | [Parameter(Mandatory=$false)] 21 | [String] $file="", 22 | 23 | [Parameter(Mandatory=$false)] 24 | [Switch] $help 25 | ) 26 | 27 | if ($help -eq $true) { 28 | Write-Output "`"Copy`" - Builds and copies your mod to your quest, and also starts Beat Saber with optional logging" 29 | Write-Output "`n-- Arguments --`n" 30 | 31 | Write-Output "-Clean `t`t Performs a clean build (equvilant to running `"build -clean`")" 32 | Write-Output "-UseDebug `t Copies the debug version of the mod to your quest" 33 | Write-Output "-Log `t`t Logs Beat Saber using the `"Start-Logging`" command" 34 | 35 | Write-Output "`n-- Logging Arguments --`n" 36 | 37 | & $PSScriptRoot/start-logging.ps1 -help -excludeHeader 38 | 39 | exit 40 | } 41 | 42 | & $PSScriptRoot/build.ps1 -clean:$clean 43 | 44 | if ($LASTEXITCODE -ne 0) { 45 | Write-Output "Failed to build, exiting..." 46 | exit $LASTEXITCODE 47 | } 48 | 49 | & $PSScriptRoot/validate-modjson.ps1 50 | if ($LASTEXITCODE -ne 0) { 51 | exit $LASTEXITCODE 52 | } 53 | & qpm qmod manifest 54 | $modJson = Get-Content "./mod.json" -Raw | ConvertFrom-Json 55 | 56 | $modFiles = $modJson.lateModFiles 57 | foreach ($fileName in $modFiles) { 58 | if ($useDebug -eq $true) { 59 | & adb push build/debug/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/mods/$fileName 60 | } else { 61 | & adb push build/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/mods/$fileName 62 | } 63 | } 64 | 65 | & $PSScriptRoot/restart-game.ps1 66 | 67 | if ($log -eq $true) { 68 | & adb logcat -c 69 | & adb logcat > log.log 70 | # & $PSScriptRoot/start-logging.ps1 -self:$self -all:$all -custom:$custom -file:$file 71 | } 72 | -------------------------------------------------------------------------------- /scripts/createqmod.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory=$false)] 3 | [String] $qmodName="", 4 | 5 | [Parameter(Mandatory=$false)] 6 | [Switch] $help 7 | ) 8 | 9 | if ($help -eq $true) { 10 | Write-Output "`"createqmod`" - Creates a .qmod file with your compiled libraries and mod.json." 11 | Write-Output "`n-- Arguments --`n" 12 | 13 | Write-Output "-QmodName `t The file name of your qmod" 14 | 15 | exit 16 | } 17 | 18 | $mod = "./mod.json" 19 | 20 | & $PSScriptRoot/validate-modjson.ps1 21 | if ($LASTEXITCODE -ne 0) { 22 | exit $LASTEXITCODE 23 | } 24 | $modJson = Get-Content $mod -Raw | ConvertFrom-Json 25 | 26 | if ($qmodName -eq "") { 27 | $qmodName = $modJson.name 28 | } 29 | 30 | $filelist = @($mod) 31 | 32 | $cover = "./" + $modJson.coverImage 33 | if ((-not ($cover -eq "./")) -and (Test-Path $cover)) { 34 | $filelist += ,$cover 35 | } 36 | 37 | foreach ($mod in $modJson.modFiles) { 38 | $path = "./build/" + $mod 39 | if (-not (Test-Path $path)) { 40 | $path = "./extern/libs/" + $mod 41 | } 42 | if (-not (Test-Path $path)) { 43 | Write-Output "Error: could not find dependency: $path" 44 | exit 1 45 | } 46 | $filelist += $path 47 | } 48 | 49 | foreach ($lib in $modJson.libraryFiles) { 50 | $path = "./build/" + $lib 51 | if (-not (Test-Path $path)) { 52 | $path = "./extern/libs/" + $lib 53 | } 54 | if (-not (Test-Path $path)) { 55 | Write-Output "Error: could not find dependency: $path" 56 | exit 1 57 | } 58 | $filelist += $path 59 | } 60 | 61 | $zip = $qmodName + ".zip" 62 | $qmod = $qmodName + ".qmod" 63 | 64 | Compress-Archive -Path $filelist -DestinationPath $zip -Update 65 | Move-Item $zip $qmod -Force 66 | -------------------------------------------------------------------------------- /scripts/decompile.sh: -------------------------------------------------------------------------------- 1 | ilspycmd -r "/home/stack/.local/share/Steam/steamapps/common/Beat Saber/Beat Saber_Data/Managed" -t ${1} "/home/stack/.local/share/Steam/steamapps/common/Beat Saber/Beat Saber_Data/Managed/Main.dll" -------------------------------------------------------------------------------- /scripts/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 -------------------------------------------------------------------------------- /scripts/pull-tombstone.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory=$false)] 3 | [String] $fileName = "RecentCrash.log", 4 | 5 | [Parameter(Mandatory=$false)] 6 | [Switch] $analyze, 7 | 8 | [Parameter(Mandatory=$false)] 9 | [Switch] $help 10 | ) 11 | 12 | if ($help -eq $true) { 13 | Write-Output "`"Pull-Tombstone`" - Finds and pulls the most recent tombstone from your quest, optionally analyzing it with ndk-stack" 14 | Write-Output "`n-- Arguments --`n" 15 | 16 | Write-Output "-FileName `t The name for the output file, defaulting to RecentCrash.log" 17 | Write-Output "-Analyze `t Runs ndk-stack on the file after pulling" 18 | 19 | exit 20 | } 21 | 22 | $global:currentDate = get-date 23 | $global:recentDate = $Null 24 | $global:recentTombstone = $Null 25 | 26 | for ($i = 0; $i -lt 3; $i++) { 27 | $stats = & adb shell stat /sdcard/Android/data/com.beatgames.beatsaber/files/tombstone_0$i 28 | $date = (Select-String -Input $stats -Pattern "(?<=Modify: )\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?=.\d{9})").Matches.Value 29 | if([string]::IsNullOrEmpty($date)) { 30 | Write-Output "Failed to pull tombstone, exiting..." 31 | exit 1; 32 | } 33 | $dateObj = [datetime]::ParseExact($date, "yyyy-MM-dd HH:mm:ss", $Null) 34 | $difference = [math]::Round(($currentDate - $dateObj).TotalMinutes) 35 | if ($difference -eq 1) { 36 | Write-Output "Found tombstone_0$i $difference minute ago" 37 | } else { 38 | Write-Output "Found tombstone_0$i $difference minutes ago" 39 | } 40 | if (-not $recentDate -or $recentDate -lt $dateObj) { 41 | $recentDate = $dateObj 42 | $recentTombstone = $i 43 | } 44 | } 45 | 46 | Write-Output "Latest tombstone was tombstone_0$recentTombstone" 47 | 48 | & adb pull /sdcard/Android/data/com.beatgames.beatsaber/files/tombstone_0$recentTombstone $fileName 49 | 50 | if ($analyze) { 51 | & $PSScriptRoot/ndk-stack.ps1 -logName:$fileName 52 | } 53 | -------------------------------------------------------------------------------- /scripts/restart-game.ps1: -------------------------------------------------------------------------------- 1 | adb shell am force-stop com.beatgames.beatsaber 2 | adb shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity 3 | -------------------------------------------------------------------------------- /scripts/start-logging.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory=$false)] 3 | [Switch] $self, 4 | 5 | [Parameter(Mandatory=$false)] 6 | [Switch] $all, 7 | 8 | [Parameter(Mandatory=$false)] 9 | [String] $custom="", 10 | 11 | [Parameter(Mandatory=$false)] 12 | [String] $file="", 13 | 14 | [Parameter(Mandatory=$false)] 15 | [Switch] $help, 16 | 17 | [Parameter(Mandatory=$false)] 18 | [Switch] $excludeHeader 19 | ) 20 | 21 | if ($help -eq $true) { 22 | if ($excludeHeader -eq $false) { 23 | Write-Output "`"Start-Logging`" - Logs Beat Saber using `"adb logcat`"" 24 | Write-Output "`n-- Arguments --`n" 25 | } 26 | 27 | Write-Output "-Self `t`t Only Logs your mod and Crashes" 28 | Write-Output "-All `t`t Logs everything, including logs made by the Quest itself" 29 | Write-Output "-Custom `t Specify a specific logging pattern, e.g `"custom-types|questui`"" 30 | Write-Output "`t`t NOTE: The paterent `"AndriodRuntime|CRASH`" is always appended to a custom pattern" 31 | Write-Output "-File `t`t Saves the output of the log to the file name given" 32 | 33 | exit 34 | } 35 | 36 | $bspid = adb shell pidof com.beatgames.beatsaber 37 | $command = "adb logcat " 38 | 39 | if ($all -eq $false) { 40 | $loops = 0 41 | while ([string]::IsNullOrEmpty($bspid) -and $loops -lt 3) { 42 | Start-Sleep -Milliseconds 100 43 | $bspid = adb shell pidof com.beatgames.beatsaber 44 | $loops += 1 45 | } 46 | 47 | if ([string]::IsNullOrEmpty($bspid)) { 48 | Write-Output "Could not connect to adb, exiting..." 49 | exit 1 50 | } 51 | 52 | $command += "--pid $bspid" 53 | } 54 | 55 | if ($all -eq $false) { 56 | $pattern = "(" 57 | if ($self -eq $true) { 58 | $modID = (Get-Content "./mod.json" -Raw | ConvertFrom-Json).id 59 | $pattern += "$modID|" 60 | } 61 | if (![string]::IsNullOrEmpty($custom)) { 62 | $pattern += "$custom|" 63 | } 64 | if ($pattern -eq "(") { 65 | $pattern = "(QuestHook|modloader|" 66 | } 67 | $pattern += "AndroidRuntime|CRASH)" 68 | $command += " | Select-String -pattern `"$pattern`"" 69 | } 70 | 71 | if (![string]::IsNullOrEmpty($file)) { 72 | $command += " | Out-File -FilePath $PSScriptRoot\$file" 73 | } 74 | 75 | Write-Output "Logging using Command `"$command`"" 76 | Invoke-Expression $command 77 | -------------------------------------------------------------------------------- /scripts/validate-modjson.ps1: -------------------------------------------------------------------------------- 1 | return 2 | 3 | $mod = "./mod.json" 4 | 5 | if (-not (Test-Path -Path $mod)) { 6 | if (Test-Path -Path ".\mod.template.json") { 7 | & qpm-rust qmod build 8 | if ($LASTEXITCODE -ne 0) { 9 | exit $LASTEXITCODE 10 | } 11 | } 12 | else { 13 | Write-Output "Error: mod.json and mod.template.json were not present" 14 | exit 1 15 | } 16 | } 17 | 18 | Write-Output "Creating qmod from mod.json" 19 | 20 | $psVersion = $PSVersionTable.PSVersion.Major 21 | if ($psVersion -ge 6) { 22 | $schemaUrl = "https://raw.githubusercontent.com/Lauriethefish/QuestPatcher.QMod/main/QuestPatcher.QMod/Resources/qmod.schema.json" 23 | Invoke-WebRequest $schemaUrl -OutFile ./mod.schema.json 24 | 25 | $schema = "./mod.schema.json" 26 | $modJsonRaw = Get-Content $mod -Raw 27 | $modSchemaRaw = Get-Content $schema -Raw 28 | 29 | Remove-Item $schema 30 | 31 | Write-Output "Validating mod.json..." 32 | if (-not ($modJsonRaw | Test-Json -Schema $modSchemaRaw)) { 33 | Write-Output "Error: mod.json is not valid" 34 | exit 1 35 | } 36 | } 37 | else { 38 | Write-Output "Could not validate mod.json with schema: powershell version was too low (< 6)" 39 | } 40 | exit 41 | -------------------------------------------------------------------------------- /shared/CJDLogger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "beatsaber-hook/shared/utils/logging.hpp" 3 | #include "beatsaber-hook/shared/utils/il2cpp-utils.hpp" 4 | 5 | #include "scotland2/shared/loader.hpp" 6 | 7 | #include "paper2_scotland2/shared/logger.hpp" 8 | 9 | using LogLevel = Paper::LogLevel; 10 | 11 | namespace CustomJSONData { 12 | static modloader::ModInfo const modInfo{"CustomJSONData", VERSION, 1}; 13 | } 14 | 15 | class CJDLogger { 16 | public: 17 | // Register file log in main.cpp 18 | static constexpr auto Logger = 19 | Paper::ConstLoggerContext("CustomJSONData"); // Paper::Logger::WithContext<"CustomJSONData", false>(); 20 | }; 21 | 22 | // Implements a try-catch handler which will first attempt to run the provided body. 23 | // If there is an uncaught RunMethodException, it will first attempt to log the backtrace. 24 | // If it holds a valid C# exception, it will attempt to raise it, such that it is caught in the il2cpp domain. 25 | // If an exception is thrown that is otherwise what-able is caught, it will attempt to call the what() method 26 | // and then rethrow the exception to the il2cpp domain. 27 | // If an unknown exception is caught, it will terminate explicitly, as opposed to letting it be thrown across the il2cpp 28 | // domain. All logs that occur as a result of this function will be under the core beatsaber-hook global logger. 29 | #define PAPER_IL2CPP_CATCH_HANDLER(...) \ 30 | try { \ 31 | __VA_ARGS__ \ 32 | } catch (::il2cpp_utils::RunMethodException const& exc) { \ 33 | CJDLogger::Logger.fmtLog("Uncaught RunMethodException! what(): {}", exc.what()); \ 34 | Paper::Logger::WaitForFlush(); \ 35 | CJDLogger::Logger.error("Uncaught RunMethodException! what(): {}", exc.what()); \ 36 | exc.log_backtrace(); \ 37 | CJDLogger::Logger.Backtrace(100); \ 38 | if (exc.ex) { \ 39 | exc.rethrow(); \ 40 | } \ 41 | SAFE_ABORT(); \ 42 | } catch (::il2cpp_utils::exceptions::StackTraceException const& exc) { \ 43 | CJDLogger::Logger.error("Uncaught StackTraceException! what(): {}", exc.what()); \ 44 | CJDLogger::Logger.fmtLog("Uncaught StackTraceException! what(): {}", exc.what()); \ 45 | exc.log_backtrace(); \ 46 | CJDLogger::Logger.Backtrace(100); \ 47 | SAFE_ABORT(); \ 48 | } catch (::std::exception const& exc) { \ 49 | CJDLogger::Logger.error("Uncaught C++ exception! type name: {}, what(): {}", typeid(exc).name(), exc.what()); \ 50 | CJDLogger::Logger.fmtLog("Uncaught C++ exception! type name: {}, what(): {}", typeid(exc).name(), \ 51 | exc.what()); \ 52 | Paper::Logger::WaitForFlush(); \ 53 | CJDLogger::Logger.Backtrace(100); \ 54 | ::il2cpp_utils::raise(exc); \ 55 | } catch (...) { \ 56 | CJDLogger::Logger.fmtLog( \ 57 | "Uncaught, unknown C++ exception (not std::exception) with no known what() method!"); \ 58 | Paper::Logger::WaitForFlush(); \ 59 | CJDLogger::Logger.error("Uncaught, unknown C++ exception (not std::exception) with no known what() method!"); \ 60 | CJDLogger::Logger.Backtrace(100); \ 61 | SAFE_ABORT(); \ 62 | } 63 | 64 | template 65 | /// @brief Exposes a static wrapper method that forwards to the provided function pointer, wrapping it in 66 | /// IL2CPP_CATCH_HANDLER. 67 | struct PaperHookCatchWrapper; 68 | 69 | template struct PaperHookCatchWrapper { 70 | static R wrapper(TArgs... args) { 71 | PAPER_IL2CPP_CATCH_HANDLER(return Func(args...);) 72 | } 73 | }; 74 | 75 | // Make a hook that uses the provided method pointer in a match an ensures the signature matches. 76 | // This should be your go-to hook macro when hooking anything that has a codegen equivalent. 77 | // Also includes a catch handler. 78 | #define MAKE_PAPER_HOOK_MATCH(name_, mPtr, retval, ...) \ 79 | struct Hook_##name_ { \ 80 | using funcType = retval (*)(__VA_ARGS__); \ 81 | static_assert(std::is_same_v::funcType>, \ 82 | "Hook method signature does not match!"); \ 83 | constexpr static char const* name() { \ 84 | return #name_; \ 85 | } \ 86 | static MethodInfo const* getInfo() { \ 87 | return ::il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo(); \ 88 | } \ 89 | static funcType* trampoline() { \ 90 | return &name_; \ 91 | } \ 92 | static inline retval (*name_)(__VA_ARGS__) = nullptr; \ 93 | static funcType hook() { \ 94 | return &::PaperHookCatchWrapper<&hook_##name_, funcType>::wrapper; \ 95 | } \ 96 | static retval hook_##name_(__VA_ARGS__); \ 97 | }; \ 98 | retval Hook_##name_::hook_##name_(__VA_ARGS__) 99 | 100 | // Same as MAKE_PAPER_HOOK_MATCH, but for manual MethodInfo* usage. 101 | #define MAKE_PAPER_HOOK_FIND(name_, infoGet, retval, ...) \ 102 | struct Hook_##name_ { \ 103 | using funcType = retval (*)(__VA_ARGS__); \ 104 | constexpr static char const* name() { \ 105 | return #name_; \ 106 | } \ 107 | static MethodInfo const* getInfo() { \ 108 | return infoGet; \ 109 | } \ 110 | static funcType* trampoline() { \ 111 | return &name_; \ 112 | } \ 113 | static inline retval (*name_)(__VA_ARGS__) = nullptr; \ 114 | static funcType hook() { \ 115 | return &::PaperHookCatchWrapper<&hook_##name_, funcType>::wrapper; \ 116 | } \ 117 | static retval hook_##name_(__VA_ARGS__); \ 118 | }; \ 119 | retval Hook_##name_::hook_##name_(__VA_ARGS__) -------------------------------------------------------------------------------- /shared/CustomBeatmapData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "custom-types/shared/macros.hpp" 4 | 5 | #include "System/Func_2.hpp" 6 | 7 | #include "GlobalNamespace/BeatmapData.hpp" 8 | #include "GlobalNamespace/BeatmapEventData.hpp" 9 | #include "GlobalNamespace/ObstacleData.hpp" 10 | #include "GlobalNamespace/NoteData.hpp" 11 | #include "GlobalNamespace/WaypointData.hpp" 12 | #include "GlobalNamespace/BasicBeatmapEventData.hpp" 13 | #include "GlobalNamespace/SliderData.hpp" 14 | #include "GlobalNamespace/BeatmapDataSortedListForTypeAndIds_1.hpp" 15 | #include "GlobalNamespace/ISortedList_1.hpp" 16 | #include "System/Object.hpp" 17 | #include "System/Collections/IEnumerable.hpp" 18 | #include "System/Collections/IEnumerator.hpp" 19 | #include "System/Collections/Generic/IEnumerator_1.hpp" 20 | #include "System/Collections/Generic/IEnumerable_1.hpp" 21 | #include "System/Collections/Generic/LinkedList_1.hpp" 22 | #include "System/Collections/Generic/LinkedListNode_1.hpp" 23 | 24 | #include "LowLevelUtils.hpp" 25 | #include "CustomEventData.h" 26 | #include "JSONWrapper.h" 27 | #include "CJDLogger.h" 28 | 29 | namespace CustomJSONData { 30 | template 31 | static constexpr System::Collections::Generic::LinkedListNode_1* 32 | LinkedListNode_1_get_Next(System::Collections::Generic::LinkedListNode_1* self) { 33 | if (self->next != nullptr && self->next != self->list->head) { 34 | return self->next; 35 | } 36 | return nullptr; 37 | } 38 | } // namespace CustomJSONData 39 | 40 | DECLARE_CLASS_CODEGEN(CustomJSONData, CustomBeatmapData, GlobalNamespace::BeatmapData) { 41 | DECLARE_FASTER_CTOR(ctor, int numberOfLines); 42 | DECLARE_SIMPLE_DTOR(); 43 | 44 | DECLARE_INSTANCE_METHOD(CustomBeatmapData*, BaseCopy); 45 | 46 | public: 47 | void AddBeatmapObjectDataOverride(GlobalNamespace::BeatmapObjectData * beatmapObjectData); 48 | void AddBeatmapObjectDataInOrderOverride(GlobalNamespace::BeatmapObjectData * beatmapObjectData); 49 | void InsertBeatmapEventDataOverride(GlobalNamespace::BeatmapEventData * beatmapObjectData); 50 | void InsertBeatmapEventDataInOrderOverride(GlobalNamespace::BeatmapEventData * beatmapObjectData); 51 | 52 | inline CustomBeatmapData* GetCopyOverride() { 53 | return GetFilteredCopyOverride([](auto i) constexpr { return i; }); 54 | } 55 | 56 | template CustomBeatmapData* GetFilteredCopyOverride(F && filter) { 57 | _isCreatingFilteredCopy = true; 58 | 59 | CustomBeatmapData* copy = BaseCopy(); 60 | 61 | auto linkedList = _allBeatmapData->get_items(); 62 | 63 | for (auto node = linkedList->get_First(); node != nullptr; node = LinkedListNode_1_get_Next(node)) { 64 | auto beatmapDataItem = node->item; 65 | 66 | if (!beatmapDataItem) continue; 67 | 68 | beatmapDataItem = filter(beatmapDataItem->GetCopy()); 69 | 70 | if (!beatmapDataItem) continue; 71 | 72 | if (auto event = il2cpp_utils::try_cast(beatmapDataItem)) { 73 | copy->InsertBeatmapEventDataInOrder(*event); 74 | } 75 | 76 | if (auto object = il2cpp_utils::try_cast(beatmapDataItem)) { 77 | copy->AddBeatmapObjectDataInOrder(*object); 78 | } 79 | 80 | if (auto customEvent = il2cpp_utils::try_cast(beatmapDataItem)) { 81 | copy->InsertCustomEventDataInOrder(*customEvent); 82 | } 83 | } 84 | 85 | _isCreatingFilteredCopy = false; 86 | 87 | return copy; 88 | } 89 | 90 | static System::Type* GetCustomType(Il2CppObject * obj); 91 | static System::Type* GetCustomType(Il2CppClass * obj); 92 | 93 | void InsertCustomEventData(CustomEventData * customEventData); 94 | void InsertCustomEventDataInOrder(CustomEventData * customEventData); 95 | 96 | template std::vector GetBeatmapItemsCpp(GlobalNamespace::BeatmapDataItem::BeatmapDataItemType type) { 97 | auto* list = reinterpret_cast*>( 98 | _beatmapDataItemsPerTypeAndId->GetList(GetCustomType(classof(T)), type.value__)); 99 | 100 | if (!list) return {}; 101 | 102 | auto linkedItems = list->get_items(); 103 | 104 | std::vector items; 105 | items.reserve(linkedItems->get_Count()); 106 | 107 | for (auto node = linkedItems->get_First(); node != nullptr; node = LinkedListNode_1_get_Next(node)) { 108 | auto val = node->item; 109 | if (!val) continue; 110 | 111 | items.template emplace_back(reinterpret_cast(val)); 112 | } 113 | 114 | return items; 115 | } 116 | 117 | std::vector GetAllBeatmapItemsCpp() { 118 | if (!_allBeatmapData) return {}; 119 | 120 | auto linkedItems = _allBeatmapData->get_items(); 121 | 122 | std::vector items; 123 | items.reserve(linkedItems->get_Count()); 124 | 125 | for (auto node = linkedItems->get_First(); node != nullptr; node = LinkedListNode_1_get_Next(node)) { 126 | auto val = node->item; 127 | if (!val) continue; 128 | 129 | items.template emplace_back(val); 130 | } 131 | 132 | return items; 133 | } 134 | 135 | std::vector beatmapObjectDatas; 136 | // 137 | std::vector beatmapEventDatas; 138 | // 139 | std::vector customEventDatas; 140 | 141 | DECLARE_INSTANCE_FIELD(bool, v2orEarlier); 142 | // optional 143 | DECLARE_INSTANCE_FIELD(CustomJSONData::JSONWrapper*, customData); 144 | DECLARE_INSTANCE_FIELD(CustomJSONData::JSONWrapperUTF16*, beatmapCustomData); 145 | DECLARE_INSTANCE_FIELD(CustomJSONData::JSONWrapperUTF16*, levelCustomData); 146 | DECLARE_INSTANCE_FIELD(CustomJSONData::DocumentWrapper*, doc); 147 | }; 148 | 149 | DECLARE_CLASS_CODEGEN(CustomJSONData, CustomBeatmapEventData, GlobalNamespace::BasicBeatmapEventData) { 150 | DECLARE_FASTER_CTOR(ctor, float time, 151 | ::GlobalNamespace::BasicBeatmapEventType basicBeatmapEventType, int value, float floatValue); 152 | 153 | DECLARE_OVERRIDE_METHOD(CustomBeatmapEventData*, GetCopy, il2cpp_utils::FindMethod("", "BeatmapDataItem", "GetCopy")); 154 | 155 | // optional 156 | DECLARE_INSTANCE_FIELD(CustomJSONData::JSONWrapper*, customData); 157 | }; 158 | 159 | DECLARE_CLASS_CODEGEN(CustomJSONData, CustomObstacleData, GlobalNamespace::ObstacleData) { 160 | DECLARE_FASTER_CTOR(ctor, float time, float endBeat, float beat, int rotation, int lineIndex, 161 | ::GlobalNamespace::NoteLineLayer lineLayer, float duration, int width, int height); 162 | 163 | DECLARE_OVERRIDE_METHOD(CustomObstacleData*, GetCopy, il2cpp_utils::FindMethod("", "BeatmapDataItem", "GetCopy")); 164 | 165 | // optional 166 | DECLARE_INSTANCE_FIELD(CustomJSONData::JSONWrapper*, customData); 167 | // Used for Noodle Extensions 168 | DECLARE_INSTANCE_FIELD(float, bpm); 169 | DECLARE_INSTANCE_FIELD(float, aheadTimeNoodle); 170 | }; 171 | 172 | DECLARE_CLASS_CODEGEN(CustomJSONData, CustomNoteData, GlobalNamespace::NoteData) { 173 | DECLARE_FASTER_CTOR( 174 | ctor, float time, float beat, int rotation, int lineIndex, ::GlobalNamespace::NoteLineLayer noteLineLayer, 175 | ::GlobalNamespace::NoteLineLayer beforeJumpNoteLineLayer, ::GlobalNamespace::NoteData::GameplayType gameplayType, 176 | ::GlobalNamespace::NoteData::ScoringType scoringType, ::GlobalNamespace::ColorType colorType, 177 | ::GlobalNamespace::NoteCutDirection cutDirection, float timeToNextColorNote, float timeToPrevColorNote, 178 | int flipLineIndex, float flipYSide, float cutDirectionAngleOffset, float cutSfxVolumeMultiplier); 179 | 180 | DECLARE_OVERRIDE_METHOD(CustomNoteData*, GetCopy, il2cpp_utils::FindMethod("", "BeatmapDataItem", "GetCopy")); 181 | 182 | // optional 183 | DECLARE_INSTANCE_FIELD(CustomJSONData::JSONWrapper*, customData); 184 | // Used for Noodle Extensions 185 | DECLARE_INSTANCE_FIELD(float, bpm); 186 | DECLARE_INSTANCE_FIELD(float, aheadTimeNoodle); 187 | }; 188 | 189 | DECLARE_CLASS_CODEGEN(CustomJSONData, CustomSliderData, GlobalNamespace::SliderData) { 190 | DECLARE_FASTER_CTOR(ctor, GlobalNamespace::SliderData::Type sliderType, ::GlobalNamespace::ColorType colorType, 191 | bool hasHeadNote, float headTime, float headBeat, int headRotation, int headLineIndex, 192 | ::GlobalNamespace::NoteLineLayer headLineLayer, 193 | ::GlobalNamespace::NoteLineLayer headBeforeJumpLineLayer, float headControlPointLengthMultiplier, 194 | ::GlobalNamespace::NoteCutDirection headCutDirection, float headCutDirectionAngleOffset, 195 | bool hasTailNote, float tailTime, int tailRotation, int tailLineIndex, 196 | ::GlobalNamespace::NoteLineLayer tailLineLayer, 197 | ::GlobalNamespace::NoteLineLayer tailBeforeJumpLineLayer, float tailControlPointLengthMultiplier, 198 | ::GlobalNamespace::NoteCutDirection tailCutDirection, float tailCutDirectionAngleOffset, 199 | ::GlobalNamespace::SliderMidAnchorMode midAnchorMode, int sliceCount, float squishAmount); 200 | 201 | DECLARE_OVERRIDE_METHOD(CustomSliderData*, GetCopy, il2cpp_utils::FindMethod("", "BeatmapDataItem", "GetCopy")); 202 | 203 | // optional 204 | DECLARE_INSTANCE_FIELD(CustomJSONData::JSONWrapper*, customData); 205 | // Used for Noodle Extensions 206 | DECLARE_INSTANCE_FIELD(float, bpm); 207 | }; 208 | 209 | DECLARE_CLASS_CODEGEN(CustomJSONData, CustomWaypointData, GlobalNamespace::WaypointData) { 210 | DECLARE_FASTER_CTOR(ctor, float time, float beat, int rotation, int lineIndex, 211 | ::GlobalNamespace::NoteLineLayer lineLayer, ::GlobalNamespace::OffsetDirection offsetDirection); 212 | 213 | DECLARE_OVERRIDE_METHOD(CustomWaypointData*, GetCopy, il2cpp_utils::FindMethod("", "BeatmapDataItem", "GetCopy")); 214 | 215 | // Used for Noodle Extensions 216 | DECLARE_INSTANCE_FIELD(float, bpm); 217 | }; -------------------------------------------------------------------------------- /shared/CustomBeatmapSaveDatav2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "custom-types/shared/macros.hpp" 4 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 5 | 6 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/BeatmapSaveData.hpp" 7 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/SpecialEventKeywordFiltersData.hpp" 8 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/SpecialEventsForKeyword.hpp" 9 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/NoteData.hpp" 10 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/SliderData.hpp" 11 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/EventData.hpp" 12 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/ObstacleData.hpp" 13 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/WaypointData.hpp" 14 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/ObstacleType.hpp" 15 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/ColorType.hpp" 16 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/NoteType.hpp" 17 | 18 | #include "BeatmapSaveDataCommon/BeatmapEventType.hpp" 19 | #include "BeatmapSaveDataCommon/NoteCutDirection.hpp" 20 | #include "BeatmapSaveDataCommon/SliderMidAnchorMode.hpp" 21 | #include "BeatmapSaveDataCommon/NoteLineLayer.hpp" 22 | #include "BeatmapSaveDataCommon/OffsetDirection.hpp" 23 | #include "BeatmapSaveDataCommon/Axis.hpp" 24 | #include "BeatmapSaveDataCommon/BasicEventTypesWithKeywords.hpp" 25 | #include "BeatmapSaveDataCommon/ExecutionTime.hpp" 26 | #include "BeatmapSaveDataCommon/BasicEventTypesWithKeywords.hpp" 27 | 28 | #include "GlobalNamespace/StandardLevelInfoSaveData.hpp" 29 | 30 | #include "JSONWrapper.h" 31 | 32 | #include "LowLevelUtils.hpp" 33 | #include "CustomEventData.h" 34 | #include "CJDLogger.h" 35 | 36 | namespace CustomJSONData::v2 { 37 | using CustomDataOpt = std::optional>; 38 | using CustomDataOptUTF16 = std::optional>; 39 | 40 | } // namespace CustomJSONData::v2 41 | DECLARE_CLASS_CODEGEN(CustomJSONData::v2, CustomBeatmapSaveData, 42 | BeatmapSaveDataVersion2_6_0AndEarlier::BeatmapSaveData) { 43 | DECLARE_FASTER_CTOR( 44 | ctor, System::Collections::Generic::List_1 * events, 45 | System::Collections::Generic::List_1 * notes, 46 | System::Collections::Generic::List_1 * sliders, 47 | System::Collections::Generic::List_1 * waypoints, 48 | System::Collections::Generic::List_1 * obstacles, 49 | BeatmapSaveDataVersion2_6_0AndEarlier::SpecialEventKeywordFiltersData * specialEventsKeywordFilters); 50 | 51 | DECLARE_SIMPLE_DTOR(); 52 | 53 | public: 54 | static CustomBeatmapSaveData* Deserialize(std::shared_ptr const& sharedDoc); 55 | 56 | std::shared_ptr> customEventsData; 57 | std::shared_ptr doc; 58 | CustomDataOpt customData; 59 | CustomDataOptUTF16 beatmapCustomData; 60 | CustomDataOptUTF16 levelCustomData; 61 | }; 62 | 63 | DECLARE_CLASS_CODEGEN(CustomJSONData::v2, CustomBeatmapSaveData_NoteData, 64 | BeatmapSaveDataVersion2_6_0AndEarlier::NoteData) { 65 | DECLARE_FASTER_CTOR(ctor, float time, int lineIndex, BeatmapSaveDataCommon::NoteLineLayer lineLayer, 66 | BeatmapSaveDataVersion2_6_0AndEarlier::NoteType type, 67 | BeatmapSaveDataCommon::NoteCutDirection cutDirection); 68 | 69 | DECLARE_SIMPLE_DTOR(); 70 | 71 | public: 72 | CustomDataOpt customData; 73 | }; 74 | 75 | DECLARE_CLASS_CODEGEN(CustomJSONData::v2, CustomBeatmapSaveData_SliderData, 76 | BeatmapSaveDataVersion2_6_0AndEarlier::SliderData) { 77 | DECLARE_FASTER_CTOR(ctor, BeatmapSaveDataVersion2_6_0AndEarlier::ColorType colorType, float headTime, 78 | int headLineIndex, BeatmapSaveDataCommon::NoteLineLayer headLineLayer, 79 | float headControlPointLengthMultiplier, BeatmapSaveDataCommon::NoteCutDirection headCutDirection, 80 | float tailTime, int tailLineIndex, BeatmapSaveDataCommon::NoteLineLayer tailLineLayer, 81 | float tailControlPointLengthMultiplier, BeatmapSaveDataCommon::NoteCutDirection tailCutDirection, 82 | BeatmapSaveDataCommon::SliderMidAnchorMode sliderMidAnchorMode); 83 | 84 | DECLARE_SIMPLE_DTOR(); 85 | 86 | public: 87 | CustomDataOpt customData; 88 | }; 89 | 90 | DECLARE_CLASS_CODEGEN(CustomJSONData::v2, CustomBeatmapSaveData_ObstacleData, 91 | BeatmapSaveDataVersion2_6_0AndEarlier::ObstacleData) { 92 | DECLARE_FASTER_CTOR(ctor, float time, int lineIndex, BeatmapSaveDataVersion2_6_0AndEarlier::ObstacleType type, 93 | float duration, int width); 94 | 95 | DECLARE_SIMPLE_DTOR(); 96 | 97 | public: 98 | CustomDataOpt customData; 99 | }; 100 | 101 | DECLARE_CLASS_CODEGEN(CustomJSONData::v2, CustomBeatmapSaveData_EventData, 102 | BeatmapSaveDataVersion2_6_0AndEarlier::EventData) { 103 | DECLARE_FASTER_CTOR(ctor, float time, BeatmapSaveDataCommon::BeatmapEventType type, int value, float floatValue); 104 | 105 | DECLARE_SIMPLE_DTOR(); 106 | 107 | public: 108 | CustomDataOpt customData; 109 | }; -------------------------------------------------------------------------------- /shared/CustomBeatmapSaveDatav3.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "custom-types/shared/macros.hpp" 4 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 5 | 6 | #include "BeatmapSaveDataVersion3/ColorNoteData.hpp" 7 | #include "BeatmapSaveDataVersion3/BurstSliderData.hpp" 8 | #include "BeatmapSaveDataVersion3/BombNoteData.hpp" 9 | #include "BeatmapSaveDataVersion3/BurstSliderData.hpp" 10 | #include "BeatmapSaveDataVersion3/SliderData.hpp" 11 | #include "BeatmapSaveDataVersion3/SliderType.hpp" 12 | #include "BeatmapSaveDataVersion3/ObstacleData.hpp" 13 | #include "BeatmapSaveDataVersion3/WaypointData.hpp" 14 | #include "BeatmapSaveDataVersion3/BasicEventData.hpp" 15 | #include "BeatmapSaveDataVersion3/BpmChangeEventData.hpp" 16 | #include "BeatmapSaveDataVersion3/BeatmapSaveData.hpp" 17 | #include "BeatmapSaveDataVersion3/BeatmapSaveDataItem.hpp" 18 | #include "BeatmapSaveDataVersion3/LightColorBaseData.hpp" 19 | #include "BeatmapSaveDataVersion3/IndexFilter.hpp" 20 | #include "BeatmapSaveDataVersion3/EventBox.hpp" 21 | #include "BeatmapSaveDataVersion3/LightRotationEventBox.hpp" 22 | #include "BeatmapSaveDataVersion3/LightTranslationEventBox.hpp" 23 | #include "BeatmapSaveDataVersion3/LightColorEventBox.hpp" 24 | #include "BeatmapSaveDataVersion3/LightRotationBaseData.hpp" 25 | #include "BeatmapSaveDataVersion3/LightColorBaseData.hpp" 26 | #include "BeatmapSaveDataVersion3/LightTranslationBaseData.hpp" 27 | #include "BeatmapSaveDataVersion3/IntFxEventBaseData.hpp" 28 | #include "BeatmapSaveDataVersion3/FloatFxEventBaseData.hpp" 29 | #include "BeatmapSaveDataVersion3/FxEventsCollection.hpp" 30 | #include "BeatmapSaveDataVersion3/FxEventBox.hpp" 31 | #include "BeatmapSaveDataVersion3/FxEventBoxGroup.hpp" 32 | #include "BeatmapSaveDataVersion3/FxEventType.hpp" 33 | #include "BeatmapSaveDataVersion3/ColorBoostEventData.hpp" 34 | #include "BeatmapSaveDataVersion3/RotationEventData.hpp" 35 | #include "BeatmapSaveDataVersion3/RotationEventData.hpp" 36 | 37 | #include "GlobalNamespace/StandardLevelInfoSaveData.hpp" 38 | 39 | #include "LowLevelUtils.hpp" 40 | #include "CustomEventData.h" 41 | #include "CJDLogger.h" 42 | #include "JSONWrapper.h" 43 | #include "songcore/shared/CustomJSONData.hpp" 44 | 45 | namespace CustomJSONData::v2 { 46 | class CustomBeatmapSaveData; 47 | } 48 | 49 | namespace CustomJSONData::v3::Constants { 50 | // worst naming scheme ever 51 | static inline auto const beat = "b"; 52 | static inline auto const colorType = "c"; 53 | static inline auto const line = "x"; 54 | static inline auto const layer = "y"; 55 | static inline auto const cutDirection = "d"; 56 | static inline auto const tailBeat = "tb"; 57 | static inline auto const tailLine = "tx"; 58 | static inline auto const tailLayer = "ty"; 59 | static inline auto const eventBoxes = "e"; 60 | static inline auto const groupId = "g"; 61 | static inline auto const indexFilter = "f"; 62 | static inline auto const beatDistributionParam = "w"; 63 | static inline auto const beatDistributionParamType = "d"; 64 | 65 | static inline auto const customData = "customData"; 66 | } // namespace CustomJSONData::v3::Constants 67 | 68 | namespace CustomJSONData::v3 { 69 | using CustomDataOpt = std::optional>; 70 | using CustomDataOptUTF16 = std::optional>; 71 | 72 | } // namespace CustomJSONData::v3 73 | 74 | DECLARE_CLASS_CODEGEN(CustomJSONData::v3, CustomBeatmapSaveData, BeatmapSaveDataVersion3::BeatmapSaveData) { 75 | DECLARE_FASTER_CTOR( 76 | ctor, System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::BpmChangeEventData*> * bpmEvents, 77 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::RotationEventData*> * rotationEvents, 78 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::ColorNoteData*> * colorNotes, 79 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::BombNoteData*> * bombNotes, 80 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::ObstacleData*> * obstacles, 81 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::SliderData*> * sliders, 82 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::BurstSliderData*> * burstSliders, 83 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::WaypointData*> * waypoints, 84 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::BasicEventData*> * basicBeatmapEvents, 85 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::ColorBoostEventData*> * colorBoostBeatmapEvents, 86 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::LightColorEventBoxGroup*> * 87 | lightColorEventBoxGroups, 88 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::LightRotationEventBoxGroup*> * 89 | lightRotationEventBoxGroups, 90 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::LightTranslationEventBoxGroup*> * 91 | lightTranslationEventBoxGroups, 92 | ::System::Collections::Generic::List_1<::BeatmapSaveDataVersion3::FxEventBoxGroup*> * lightFxEventBoxGroups, 93 | BeatmapSaveDataVersion3::FxEventsCollection * eventsCollection, 94 | BeatmapSaveDataCommon::BasicEventTypesWithKeywords * basicEventTypesWithKeywords, 95 | bool useNormalEventsAsCompatibleEvents); 96 | 97 | DECLARE_SIMPLE_DTOR(); 98 | 99 | public: 100 | static CustomBeatmapSaveData* Deserialize(std::shared_ptr const& sharedDoc); 101 | static CustomBeatmapSaveData* Convert2_6_0(CustomJSONData::v2::CustomBeatmapSaveData * beatmap); 102 | 103 | std::shared_ptr> customEventsData; 104 | std::shared_ptr doc; 105 | CustomDataOpt customData; 106 | CustomDataOptUTF16 beatmapCustomData; 107 | CustomDataOptUTF16 levelCustomData; 108 | 109 | DECLARE_INSTANCE_FIELD(bool, isV2); 110 | }; 111 | 112 | DECLARE_CLASS_CODEGEN(CustomJSONData::v3, CustomBeatmapSaveData_ColorNoteData, BeatmapSaveDataVersion3::ColorNoteData) { 113 | DECLARE_FASTER_CTOR(ctor, float beat, int line, int layer, BeatmapSaveDataCommon::NoteColorType color, 114 | BeatmapSaveDataCommon::NoteCutDirection cutDirection, int angleOffset); 115 | DECLARE_SIMPLE_DTOR(); 116 | 117 | public: 118 | CustomDataOpt customData; 119 | }; 120 | 121 | DECLARE_CLASS_CODEGEN(CustomJSONData::v3, CustomBeatmapSaveData_BombNoteData, BeatmapSaveDataVersion3::BombNoteData) { 122 | DECLARE_FASTER_CTOR(ctor, float beat, int line, int layer); 123 | DECLARE_SIMPLE_DTOR(); 124 | 125 | public: 126 | CustomDataOpt customData; 127 | }; 128 | 129 | DECLARE_CLASS_CODEGEN(CustomJSONData::v3, CustomBeatmapSaveData_SliderData, BeatmapSaveDataVersion3::SliderData) { 130 | DECLARE_FASTER_CTOR(ctor, BeatmapSaveDataCommon::NoteColorType colorType, float headBeat, int headLine, int headLayer, 131 | float headControlPointLengthMultiplier, BeatmapSaveDataCommon::NoteCutDirection headCutDirection, 132 | float tailBeat, int tailLine, int tailLayer, float tailControlPointLengthMultiplier, 133 | BeatmapSaveDataCommon::NoteCutDirection tailCutDirection, 134 | BeatmapSaveDataCommon::SliderMidAnchorMode sliderMidAnchorMode); 135 | DECLARE_SIMPLE_DTOR(); 136 | 137 | public: 138 | CustomDataOpt customData; 139 | }; 140 | 141 | DECLARE_CLASS_CODEGEN(CustomJSONData::v3, CustomBeatmapSaveData_BurstSliderData, 142 | BeatmapSaveDataVersion3::BurstSliderData) { 143 | DECLARE_FASTER_CTOR(ctor, BeatmapSaveDataCommon::NoteColorType colorType, float headBeat, int headLine, int headLayer, 144 | BeatmapSaveDataCommon::NoteCutDirection headCutDirection, float tailBeat, int tailLine, 145 | int tailLayer, int sliceCount, float squishAmount); 146 | DECLARE_SIMPLE_DTOR(); 147 | 148 | public: 149 | CustomDataOpt customData; 150 | }; 151 | 152 | DECLARE_CLASS_CODEGEN(CustomJSONData::v3, CustomBeatmapSaveData_ObstacleData, BeatmapSaveDataVersion3::ObstacleData) { 153 | DECLARE_FASTER_CTOR(ctor, float beat, int line, int layer, float duration, int width, int height); 154 | 155 | DECLARE_SIMPLE_DTOR(); 156 | 157 | public: 158 | CustomDataOpt customData; 159 | }; 160 | 161 | DECLARE_CLASS_CODEGEN(CustomJSONData::v3, CustomBeatmapSaveData_BasicEventData, 162 | BeatmapSaveDataVersion3::BasicEventData) { 163 | DECLARE_FASTER_CTOR(ctor, float beat, BeatmapSaveDataCommon::BeatmapEventType eventType, int value, float floatValue); 164 | 165 | DECLARE_SIMPLE_DTOR(); 166 | 167 | public: 168 | CustomDataOpt customData; 169 | }; 170 | 171 | // clang-format on 172 | 173 | namespace CustomJSONData::v3::Parser { 174 | CustomBeatmapSaveData_BurstSliderData* DeserializeBurstSlider(rapidjson::Value const& val); 175 | CustomBeatmapSaveData_SliderData* DeserializeSlider(rapidjson::Value const& val); 176 | 177 | CustomBeatmapSaveData_ObstacleData* DeserializeObstacle(rapidjson::Value const& val); 178 | 179 | CustomBeatmapSaveData_BombNoteData* DeserializeBombNote(rapidjson::Value const& val); 180 | CustomBeatmapSaveData_ColorNoteData* DeserializeColorNote(rapidjson::Value const& val); 181 | 182 | extern UnorderedEventCallback ParsedEvent; 183 | } // namespace CustomJSONData::v3::Parser 184 | -------------------------------------------------------------------------------- /shared/CustomEventData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "_config.hpp" 4 | 5 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 6 | #include "custom-types/shared/macros.hpp" 7 | 8 | #include "CJDLogger.h" 9 | #include "GlobalNamespace/BeatmapDataItem.hpp" 10 | #include "GlobalNamespace/BeatmapDataCallbackWrapper.hpp" 11 | #include "GlobalNamespace/BeatmapCallbacksController.hpp" 12 | 13 | #include "System/Collections/Generic/LinkedListNode_1.hpp" 14 | 15 | #include "UnityEngine/Object.hpp" 16 | 17 | #include "LowLevelUtils.hpp" 18 | 19 | namespace GlobalNamespace { 20 | class BeatmapCallbacksController; 21 | } 22 | 23 | #define FindMethodGetter(methodName) ::il2cpp_utils::il2cpp_type_check::MetadataGetter::methodInfo() 24 | 25 | DECLARE_CLASS_CODEGEN(CustomJSONData, CustomEventData, GlobalNamespace::BeatmapDataItem) { 26 | 27 | private: 28 | DECLARE_FASTER_CTOR(ctor, float time); 29 | 30 | public: 31 | static CustomEventData* New(float time, std::string_view type, size_t typeHash, rapidjson::Value const* data); 32 | 33 | DECLARE_OVERRIDE_METHOD(CustomEventData*, GetCopy, il2cpp_utils::FindMethod("", "BeatmapDataItem", "GetCopy")); 34 | 35 | public: 36 | std::string_view type; 37 | size_t typeHash; 38 | rapidjson::Value const* data; 39 | }; 40 | 41 | DECLARE_CLASS_CODEGEN(CustomJSONData, CustomBeatmapDataCallbackWrapper, GlobalNamespace::BeatmapDataCallbackWrapper) { 42 | DECLARE_FASTER_CTOR(ctor); 43 | 44 | DECLARE_SIMPLE_DTOR(); 45 | 46 | DECLARE_OVERRIDE_METHOD(void, CallCallback, 47 | FindMethodGetter(&GlobalNamespace::BeatmapDataCallbackWrapper::CallCallback), 48 | GlobalNamespace::BeatmapDataItem* item); 49 | 50 | DECLARE_INSTANCE_FIELD(GlobalNamespace::BeatmapCallbacksController*, controller); 51 | 52 | std::function 53 | redirectEvent; 54 | }; 55 | 56 | namespace CustomJSONData { 57 | 58 | class CustomEventSaveData { 59 | public: 60 | std::string_view type; 61 | size_t typeHash; 62 | float time; 63 | rapidjson::Value const* data; 64 | 65 | constexpr CustomEventSaveData(std::string_view type, size_t typeHash, float time, rapidjson::Value const* data) 66 | : type(type), typeHash(typeHash), time(time), data(data) {} 67 | 68 | constexpr CustomEventSaveData(std::string_view type, float time, rapidjson::Value const* data) 69 | : type(type), time(time), data(data) { 70 | typeHash = std::hash()(type); 71 | } 72 | }; 73 | 74 | struct CustomEventCallbackData { 75 | void (*callback)(GlobalNamespace::BeatmapCallbacksController* callbackController, CustomJSONData::CustomEventData*); 76 | 77 | constexpr explicit CustomEventCallbackData(void (*callback)(GlobalNamespace::BeatmapCallbacksController*, 78 | CustomEventData*)) 79 | : callback(callback) {} 80 | }; 81 | 82 | class CJD_MOD_EXPORT CustomEventCallbacks { 83 | public: 84 | static std::vector customEventCallbacks; 85 | // For Noodle 86 | static SafePtr> firstNode; 87 | 88 | static void AddCustomEventCallback(void (*callback)(GlobalNamespace::BeatmapCallbacksController* callbackController, 89 | CustomJSONData::CustomEventData*)); 90 | 91 | static void RegisterCallbacks(GlobalNamespace::BeatmapCallbacksController* callbackController); 92 | }; 93 | 94 | } // end namespace CustomJSONData -------------------------------------------------------------------------------- /shared/JSONWrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "custom-types/shared/macros.hpp" 4 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 5 | 6 | #include "System/Object.hpp" 7 | 8 | #include "CJDLogger.h" 9 | 10 | #include 11 | #include 12 | 13 | #include "songcore/shared/CustomJSONData.hpp" 14 | #include "LowLevelUtils.hpp" 15 | 16 | DECLARE_CLASS_CODEGEN(CustomJSONData, DocumentWrapper, Il2CppObject) { 17 | 18 | DECLARE_DEFAULT_CTOR(); 19 | DECLARE_SIMPLE_DTOR(); 20 | 21 | public: 22 | std::shared_ptr doc; 23 | }; 24 | 25 | DECLARE_CLASS_CODEGEN(CustomJSONData, JSONWrapper, Il2CppObject) { 26 | DECLARE_FASTER_CTOR(ctor); 27 | DECLARE_SIMPLE_DTOR(); 28 | 29 | DECLARE_INSTANCE_METHOD(JSONWrapper*, GetCopy); 30 | 31 | public: 32 | std::optional> value; 33 | std::unordered_map associatedData; 34 | }; 35 | 36 | DECLARE_CLASS_CODEGEN(CustomJSONData, JSONWrapperUTF16, Il2CppObject) { 37 | 38 | DECLARE_FASTER_CTOR(ctor); 39 | DECLARE_SIMPLE_DTOR(); 40 | 41 | public: 42 | std::optional> value; 43 | std::unordered_map associatedData; 44 | 45 | DECLARE_INSTANCE_METHOD(JSONWrapperUTF16*, GetCopy); 46 | }; 47 | -------------------------------------------------------------------------------- /shared/JsonUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "sombrero/shared/FastVector3.hpp" 7 | #include "sombrero/shared/FastVector2.hpp" 8 | #include "sombrero/shared/FastQuaternion.hpp" 9 | #include "sombrero/shared/FastColor.hpp" 10 | 11 | #ifndef RAPIDJSON_NEON 12 | #define RAPIDJSON_NEON 13 | #endif 14 | 15 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 16 | #include "CJDLogger.h" 17 | 18 | namespace NEJSON { 19 | 20 | static std::optional ReadOptionalBool(rapidjson::Value const& object, std::string_view const key) { 21 | auto itr = object.FindMember(key.data()); 22 | if (itr != object.MemberEnd()) { 23 | if (itr->value.IsString()) { 24 | std::string boolS = itr->value.GetString(); 25 | CJDLogger::Logger.fmtLog("ReadOptionalBool: THE VALUE IS A STRING WHY! value: \"{}\"", boolS); 26 | 27 | if (boolS == "true") { 28 | return true; 29 | } 30 | 31 | return false; 32 | } 33 | 34 | if (itr->value.IsNumber()) { 35 | return itr->value.GetInt() != 0; 36 | } 37 | 38 | return itr->value.GetBool(); 39 | } 40 | return std::nullopt; 41 | } 42 | 43 | static std::optional ReadOptionalString(rapidjson::Value const& object, std::string_view const key) { 44 | auto itr = object.FindMember(key.data()); 45 | if (itr != object.MemberEnd() && itr->value.IsString()) { 46 | return itr->value.GetString(); 47 | } 48 | return std::nullopt; 49 | } 50 | 51 | static std::optional ReadOptionalFloat(rapidjson::Value const& object, std::string_view const key) { 52 | auto itr = object.FindMember(key.data()); 53 | if (itr != object.MemberEnd()) { 54 | return itr->value.GetFloat(); 55 | } 56 | return std::nullopt; 57 | } 58 | 59 | static std::optional ReadOptionalInt(rapidjson::Value const& object, std::string_view const key) { 60 | auto itr = object.FindMember(key.data()); 61 | if (itr != object.MemberEnd()) { 62 | return itr->value.GetInt(); 63 | } 64 | return std::nullopt; 65 | } 66 | 67 | static std::optional ReadOptionalVector2(rapidjson::Value const& object, 68 | std::string_view const key) { 69 | auto itr = object.FindMember(key.data()); 70 | if (itr != object.MemberEnd() && itr->value.Size() >= 2) { 71 | float x = itr->value[0].GetFloat(); 72 | float y = itr->value[1].GetFloat(); 73 | return Sombrero::FastVector2(x, y); 74 | } 75 | return std::nullopt; 76 | } 77 | 78 | // Used for note flip 79 | static std::optional ReadOptionalVector2_emptyY(rapidjson::Value const& object, 80 | std::string_view const key) { 81 | auto itr = object.FindMember(key.data()); 82 | 83 | if (itr != object.MemberEnd() && itr->value.Size() >= 1) { 84 | float x = itr->value[0].GetFloat(); 85 | float y = 0; 86 | 87 | if (itr->value.Size() > 1) { 88 | y = itr->value[1].GetFloat(); 89 | } 90 | return Sombrero::FastVector2(x, y); 91 | } 92 | return std::nullopt; 93 | } 94 | 95 | using OptPair = std::pair, std::optional>; 96 | 97 | static OptPair ReadOptionalPair(rapidjson::Value const& object, std::string_view const key) { 98 | auto itr = object.FindMember(key.data()); 99 | 100 | if (itr != object.MemberEnd() && itr->value.Size() >= 1) { 101 | float x = itr->value[0].GetFloat(); 102 | float y = 0; 103 | 104 | if (itr->value.Size() >= 2) { 105 | y = itr->value[1].GetFloat(); 106 | return { std::optional(x), std::optional(y) }; 107 | } 108 | return { std::optional(x), std::nullopt }; 109 | } 110 | return { std::nullopt, std::nullopt }; 111 | } 112 | 113 | static std::optional ReadOptionalRotation(rapidjson::Value const& object, 114 | std::string_view const key) { 115 | auto itr = object.FindMember(key.data()); 116 | if (itr != object.MemberEnd()) { 117 | Sombrero::FastVector3 rot; 118 | if (itr->value.IsArray() && itr->value.Size() >= 3) { 119 | float x = itr->value[0].GetFloat(); 120 | float y = itr->value[1].GetFloat(); 121 | float z = itr->value[2].GetFloat(); 122 | rot = Sombrero::FastVector3(x, y, z); 123 | } else if (itr->value.IsNumber()) { 124 | rot = Sombrero::FastVector3(0, itr->value.GetFloat(), 0); 125 | } 126 | 127 | return Sombrero::FastQuaternion::Euler(rot); 128 | } 129 | return std::nullopt; 130 | } 131 | 132 | static std::optional ReadOptionalVector3(rapidjson::Value const& object, 133 | std::string_view const key) { 134 | auto itr = object.FindMember(key.data()); 135 | if (itr != object.MemberEnd() && itr->value.Size() >= 3) { 136 | float x = itr->value[0].GetFloat(); 137 | float y = itr->value[1].GetFloat(); 138 | float z = itr->value[2].GetFloat(); 139 | return Sombrero::FastVector3(x, y, z); 140 | } 141 | return std::nullopt; 142 | } 143 | 144 | static std::optional, 3>> ReadOptionalScale(rapidjson::Value const& object, 145 | std::string_view const key) { 146 | auto itr = object.FindMember(key.data()); 147 | if (itr != object.MemberEnd() && itr->value.IsArray()) { 148 | rapidjson::SizeType size = itr->value.Size(); 149 | std::optional x = size >= 1 ? std::optional{ itr->value[0].GetFloat() } : std::nullopt; 150 | std::optional y = size >= 2 ? std::optional{ itr->value[1].GetFloat() } : std::nullopt; 151 | std::optional z = size >= 3 ? std::optional{ itr->value[2].GetFloat() } : std::nullopt; 152 | return { { x, y, z } }; 153 | } 154 | return std::nullopt; 155 | } 156 | 157 | static std::optional ReadOptionalObject(rapidjson::Value const& object, 158 | std::string_view const key) { 159 | auto itr = object.FindMember(key.data()); 160 | if (itr != object.MemberEnd()) { 161 | return itr->value.GetObject(); 162 | } 163 | return std::nullopt; 164 | } 165 | static std::optional ReadOptionalValuePtr(rapidjson::Value const& object, 166 | std::string_view const key) { 167 | auto itr = object.FindMember(key.data()); 168 | if (itr != object.MemberEnd()) { 169 | return &itr->value; 170 | } 171 | return std::nullopt; 172 | } 173 | static std::optional> ReadOptionalValue(rapidjson::Value const& object, 174 | std::string_view const key) { 175 | auto itr = object.FindMember(key.data()); 176 | if (itr != object.MemberEnd()) { 177 | return std::ref(itr->value); 178 | } 179 | return std::nullopt; 180 | } 181 | 182 | template 183 | static std::optional> ReadOptionalArray(rapidjson::Value const& object, std::string_view const key, 184 | F&& func) { 185 | auto itr = object.FindMember(key.data()); 186 | if (itr == object.MemberEnd()) { 187 | return std::nullopt; 188 | } 189 | 190 | auto jsonArray = itr->value.GetArray(); 191 | 192 | std::vector vec; 193 | vec.reserve(jsonArray.Size()); 194 | 195 | for (auto const& v : jsonArray) { 196 | vec.emplace_back(func(v)); 197 | } 198 | 199 | return vec; 200 | } 201 | 202 | template 203 | static inline std::vector ReadArrayOrEmpty(rapidjson::Value const& object, std::string_view const key, F&& func) { 204 | auto opt = ReadOptionalArray(object, key, std::forward(func)); 205 | 206 | if (!opt) { 207 | return {}; 208 | } 209 | 210 | return *opt; 211 | } 212 | 213 | template 214 | static std::optional ReadOptionalType(rapidjson::Value const& object, std::string_view const key) { 215 | auto itr = object.FindMember(key.data()); 216 | if (itr != object.MemberEnd()) { 217 | return itr->value.Get(); 218 | } 219 | return std::nullopt; 220 | } 221 | template 222 | static std::optional ReadOptionalType(rapidjson::Value const& object, std::string_view const key, F&& func) { 223 | auto itr = object.FindMember(key.data()); 224 | if (itr != object.MemberEnd()) { 225 | return func(itr->value); 226 | } 227 | return std::nullopt; 228 | } 229 | 230 | static std::string ToStringJSONValue(rapidjson::Value const& json) { 231 | using namespace rapidjson; 232 | 233 | StringBuffer sb; 234 | PrettyWriter writer(sb); 235 | json.Accept(writer); 236 | return sb.GetString(); 237 | } 238 | 239 | } // namespace NEJSON -------------------------------------------------------------------------------- /shared/LowLevelUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "beatsaber-hook/shared/utils/il2cpp-utils-methods.hpp" 5 | 6 | /// Collection of methods that are intended to be low level performance optimizations for 7 | /// niche use cases 8 | /// Might PR to bs-hooks sometime later 9 | namespace CustomJSONData { 10 | 11 | template 12 | concept CtorArgs = requires(T t, TArgs&&... args) { 13 | { T::New_ctor(std::forward(args)...) }; 14 | }; 15 | 16 | template 17 | requires(CtorArgs, TArgs...>) 18 | constexpr T NewFast(TArgs&&... args) { 19 | return il2cpp_utils::NewSpecificUnsafe(std::forward(args)...); 20 | } 21 | 22 | // // Faster allocation with method cache 23 | // template 24 | // // Bonus points for a requires clause here for classof(T) 25 | // T NewFastKlass(Il2CppClass* klass, TArgs&&... args) { 26 | // // return CRASH_UNLESS(il2cpp_utils::New(klass, 27 | // // std::forward(args)...)); 28 | // static auto ctor = CRASH_UNLESS(il2cpp_utils::FindMethod( 29 | // klass, ".ctor", std::array{ il2cpp_utils::ExtractIndependentType()... })); 30 | // auto* obj = CRASH_UNLESS(il2cpp_functions::object_new(klass)); 31 | // il2cpp_utils::RunMethodRethrow(obj, ctor, std::forward(args)...); 32 | // return reinterpret_cast(obj); 33 | // } 34 | 35 | // // Faster allocation with method cache 36 | // template 37 | // // Bonus points for a requires clause here for classof(T) 38 | // T NewFastUnsafe(TArgs&&... args) { 39 | // static auto klass = classof(T); 40 | // return NewFastKlass(klass, std::forward(args)...); 41 | // } 42 | 43 | // // Faster allocation with method cache 44 | // template 45 | // requires(CtorArgs, TArgs...>) 46 | // // Bonus points for a requires clause here for classof(T) 47 | // T NewFast(TArgs&&... args) { 48 | // static auto klass = classof(T); 49 | // return NewFastKlass(klass, std::forward(args)...); 50 | // } 51 | 52 | // Declare a method with name that will be called on construction. 53 | //#define DECLARE_FASTER_CTOR(name, ...) DECLARE_CTOR(name, __VA_ARGS__) 54 | 55 | #define DECLARE_FASTER_CTOR(name, ...) \ 56 | public: \ 57 | void name(__VA_ARGS__); \ 58 | template <::il2cpp_utils::CreationType creationType = ::il2cpp_utils::CreationType::Temporary, class... TArgs> \ 59 | static ___TargetType* New_ctor(TArgs&&... args) { \ 60 | static_assert(::custom_types::Decomposer::convertible(), \ 61 | "Arguments provided to New_ctor must be convertible to the constructor!"); \ 62 | ___TargetType* obj; \ 63 | if constexpr (creationType == ::il2cpp_utils::CreationType::Temporary) { \ 64 | obj = reinterpret_cast<___TargetType*>(::il2cpp_functions::object_new(___TypeRegistration::klass_ptr)); \ 65 | } else { \ 66 | obj = reinterpret_cast<___TargetType*>(::il2cpp_utils::createManual(___TypeRegistration::klass_ptr)); \ 67 | } \ 68 | obj->name(std::forward(args)...); \ 69 | return obj; \ 70 | } \ 71 | ___CREATE_INSTANCE_METHOD(name, ".ctor", \ 72 | METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_HIDE_BY_SIG | METHOD_ATTRIBUTE_SPECIAL_NAME | \ 73 | METHOD_ATTRIBUTE_RT_SPECIAL_NAME, \ 74 | nullptr) 75 | } // namespace CustomJSONData -------------------------------------------------------------------------------- /shared/VList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "System/Collections/Generic/List_1.hpp" 3 | #include "CJDLogger.h" 4 | 5 | // template 6 | // class ListIterator { 7 | // public: 8 | // using iterator_category = random_access_iterator_tag; 9 | // using difference_type = std::ptrdiff_t; 10 | // using value_type = ListTy::value_type; 11 | // using pointer = ListTy::pointer; 12 | // using reference = ListTy::reference; 13 | 14 | // ListIterator(pointer ptr_) : ptr(ptr_) {} 15 | 16 | // reference operator*() const { 17 | // return *ptr; 18 | // } 19 | 20 | // pointer operator->() { 21 | // return ptr; 22 | // } 23 | 24 | // ListIterator& operator++() { 25 | // ptr++; 26 | // return *this; 27 | // } 28 | 29 | // ListIterator operator++(int) { 30 | // ListIterator tmp = *this; 31 | // (*this)++; 32 | // return tmp; 33 | // } 34 | 35 | // bool operator==(const Iterator& other) const { 36 | // return ptr == other.ptr; 37 | // }; 38 | 39 | // bool operator!=(const Iterator& other) const { 40 | // return ptr != other.ptr; 41 | // }; 42 | // private: 43 | // pointer ptr; 44 | // } 45 | 46 | namespace CustomJSONData { 47 | template 48 | inline ListW*> SpanToSystemList(std::span const span) { 49 | auto inner = System::Collections::Generic::List_1::New_ctor(span.size()); 50 | // update size field, otherwise will be "empty" 51 | inner->_size = span.size(); 52 | 53 | std::copy(span.begin(), span.end(), inner->_items.begin()); 54 | 55 | 56 | return {inner}; 57 | } 58 | 59 | template 60 | inline auto SpanToSystemList(std::vector const& list) { 61 | return SpanToSystemList(std::span(list)); 62 | } 63 | } // namespace CustomJSONData 64 | 65 | template 66 | using VList = ListW*>; 67 | 68 | // template 69 | // class VList { 70 | // private: 71 | // using InnerTy = System::Collections::Generic::List_1; 72 | // InnerTy* inner; 73 | 74 | // public: 75 | // using value_type = Ty; 76 | // using pointer = Ty*; 77 | // using const_pointer = Ty const*; 78 | // using reference = Ty&; 79 | 80 | // // Maybe I'll use my own iterator type if needed 81 | // using iterator = pointer; 82 | // using const_iterator = const_pointer; 83 | 84 | // public: 85 | // constexpr VList() : inner(InnerTy::New_ctor()) {} 86 | 87 | // constexpr VList(int size) : inner(InnerTy::New_ctor(size)) {} 88 | 89 | // constexpr VList(InnerTy* list) : inner(list) {} 90 | // constexpr VList(void* list) : inner(static_cast(list)) {} 91 | 92 | // constexpr InnerTy* operator*() const { 93 | // return inner; 94 | // } 95 | 96 | // constexpr operator InnerTy*() const { 97 | // return inner; 98 | // } 99 | 100 | // constexpr Ty& operator[](const size_t pos) const { 101 | // return inner->items.get(pos); 102 | // } 103 | 104 | // [[nodiscard]] constexpr int size() const { 105 | // return inner->size; 106 | // } 107 | 108 | // constexpr auto resize(const size_t cap) { 109 | // return inner->EnsureCapacity(cap); 110 | // } 111 | 112 | // constexpr void trim() const { 113 | // return inner->TrimExcess(); 114 | // } 115 | 116 | // constexpr void insert_at(int index, Ty const& val) { 117 | // // TODO: C++ impl 118 | // return inner->Insert(index, val); 119 | // } 120 | 121 | // constexpr void push_back(Ty const& val) { 122 | // // TODO: C++ impl 123 | // return inner->Add(val); 124 | // } 125 | 126 | // iterator constexpr begin() { 127 | // return inner->items.begin(); 128 | // } 129 | 130 | // iterator constexpr end() { 131 | // return inner->items.begin() + size(); 132 | // } 133 | 134 | // [[nodiscard]] constexpr InnerTy* getInner() const { 135 | // return inner; 136 | // } 137 | 138 | // [[nodiscard]] constexpr void* convert() const noexcept { 139 | // return inner; 140 | // } 141 | 142 | // constexpr operator std::span() { 143 | // return std::span(begin(), size()); 144 | // } 145 | 146 | // constexpr std::span toSpan() { 147 | // return std::span(begin(), size()); 148 | // } 149 | // }; 150 | 151 | // If it's not just a pointer then bad things will happen 152 | // static_assert(sizeof(VList) == 0x8); 153 | 154 | // template struct il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class> { 155 | // static inline Il2CppClass* get() { 156 | // return classof(System::Collections::Generic::List_1*); 157 | // } 158 | // }; 159 | 160 | // static_assert(il2cpp_utils::has_il2cpp_conversion>); 161 | 162 | // template struct ::il2cpp_utils::il2cpp_type_check::need_box> { 163 | // constexpr static bool value = false; 164 | // }; -------------------------------------------------------------------------------- /shared/_config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define CJD_MOD_EXPORT __attribute__((visibility("default"))) 4 | #define CJD_MOD_EXTERN_FUNC extern "C" CJD_MOD_EXPORT -------------------------------------------------------------------------------- /shared/misc/BeatmapFieldUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/BeatmapSaveDataItem.hpp" 4 | #include "../CustomBeatmapSaveDatav3.h" 5 | #include "../JSONWrapper.h" 6 | 7 | namespace CustomJSONData { 8 | 9 | static v3::CustomDataOpt JSONObjectOrNull(v3::CustomDataOpt const& val) { 10 | if (!val || !val->get().IsObject()) { 11 | return std::nullopt; 12 | } 13 | 14 | return val; 15 | } 16 | static JSONWrapper* JSONWrapperOrNull(v3::CustomDataOpt const& val) { 17 | auto* wrapper = JSONWrapper::New_ctor(); 18 | 19 | if (!val || !val->get().IsObject()) { 20 | return wrapper; 21 | } 22 | 23 | wrapper->value = val; 24 | 25 | return wrapper; 26 | } 27 | 28 | static v3::CustomDataOptUTF16 JSONObjectOrNull(v3::CustomDataOptUTF16 const& val) { 29 | if (!val || !val->get().IsObject()) { 30 | return std::nullopt; 31 | } 32 | 33 | return val; 34 | } 35 | static JSONWrapperUTF16* JSONWrapperOrNull(v3::CustomDataOptUTF16 const& val) { 36 | auto* wrapper = JSONWrapperUTF16::New_ctor(); 37 | 38 | if (!val || !val->get().IsObject()) { 39 | return wrapper; 40 | } 41 | 42 | wrapper->value = val; 43 | 44 | return wrapper; 45 | } 46 | 47 | constexpr float BeatmapSaveDataItem_GetBeat(GlobalNamespace::BeatmapDataItem const* item) { 48 | if (!item) return 0; 49 | return const_cast(item)->time; 50 | } 51 | 52 | constexpr float 53 | BeatmapSaveDataItem_GetBeat(BeatmapSaveDataVersion2_6_0AndEarlier::BeatmapSaveDataItem const* const item) { 54 | if (!item) return 0; 55 | 56 | return const_cast < BeatmapSaveDataVersion2_6_0AndEarlier::BeatmapSaveDataItem*>(item)->time; 57 | } 58 | 59 | constexpr float BeatmapSaveDataItem_GetBeat(BeatmapSaveDataVersion3::BeatmapSaveDataItem const* const item) { 60 | if (!item) return 0; 61 | 62 | return item->b; 63 | } 64 | 65 | 66 | constexpr float BeatmapSaveDataItem_GetBeat(BeatmapSaveDataVersion3::LightColorBaseData const* const item) { 67 | if (!item) return 0; 68 | 69 | return item->b; 70 | } 71 | 72 | // 73 | 74 | constexpr auto& 75 | LightColorBaseData_GetTransitionType(BeatmapSaveDataVersion3::LightColorBaseData const* item) { 76 | return item->i; 77 | } 78 | 79 | constexpr auto& 80 | LightColorBaseData_GetColorType(BeatmapSaveDataVersion3::LightColorBaseData const* item) { 81 | return item->c; 82 | } 83 | 84 | constexpr auto& 85 | LightColorBaseData_GetBrightness(BeatmapSaveDataVersion3::LightColorBaseData const* item) { 86 | return item->s; 87 | } 88 | 89 | constexpr auto& 90 | LightColorBaseData_GetStrobeFrequency(BeatmapSaveDataVersion3::LightColorBaseData const* item) { 91 | return item->f; 92 | } 93 | 94 | // 95 | 96 | constexpr auto 97 | LightRotationBaseData_GetStrobeFrequency(BeatmapSaveDataVersion3::LightRotationBaseData const* item) { 98 | return item->p == 1; 99 | } 100 | 101 | constexpr auto& 102 | LightRotationBaseData_GetRotation(BeatmapSaveDataVersion3::LightRotationBaseData const* item) { 103 | return item->r; 104 | } 105 | 106 | constexpr auto& 107 | LightRotationBaseData_GetLoopsCount(BeatmapSaveDataVersion3::LightRotationBaseData const* item) { 108 | return item->l; 109 | } 110 | 111 | // 112 | 113 | constexpr auto& EventBox_GetBeatDistributionParam(BeatmapSaveDataVersion3::EventBox const* item) { 114 | return item->w; 115 | } 116 | 117 | constexpr auto& EventBox_GetBeatDistributionParamType(BeatmapSaveDataVersion3::EventBox const* item) { 118 | return item->d; 119 | } 120 | 121 | // 122 | 123 | constexpr auto& IndexFilter_GetParam0(BeatmapSaveDataVersion3::IndexFilter const* filter) { 124 | return filter->p; 125 | } 126 | 127 | constexpr auto& IndexFilter_GetParam1(BeatmapSaveDataVersion3::IndexFilter const* filter) { 128 | return filter->t; 129 | } 130 | 131 | constexpr auto IndexFilter_GetReversed(BeatmapSaveDataVersion3::IndexFilter const* filter) { 132 | return filter->r != 0; 133 | } 134 | 135 | constexpr auto& IndexFilter_GetFilterType(BeatmapSaveDataVersion3::IndexFilter const* filter) { 136 | return filter->f; 137 | } 138 | // 139 | 140 | } // namespace CustomJSONData -------------------------------------------------------------------------------- /src/CustomBeatmapData.cpp: -------------------------------------------------------------------------------- 1 | #include "CustomBeatmapData.h" 2 | 3 | #include "GlobalNamespace/BeatmapDataSortedListForTypeAndIds_1.hpp" 4 | #include "GlobalNamespace/ISortedList_1.hpp" 5 | #include "GlobalNamespace/SortedList_2.hpp" 6 | #include "GlobalNamespace/BeatmapDataItem.hpp" 7 | #include "GlobalNamespace/ISortedListItemProcessor_1.hpp" 8 | #include "System/Collections/Generic/Dictionary_2.hpp" 9 | #include "CustomJSONDataHooks.h" 10 | 11 | #include "System/Collections/Generic/HashSet_1.hpp" 12 | 13 | using namespace GlobalNamespace; 14 | 15 | DEFINE_TYPE(CustomJSONData, CustomBeatmapData); 16 | DEFINE_TYPE(CustomJSONData, CustomBeatmapEventData); 17 | DEFINE_TYPE(CustomJSONData, CustomObstacleData); 18 | DEFINE_TYPE(CustomJSONData, CustomNoteData); 19 | DEFINE_TYPE(CustomJSONData, CustomWaypointData); 20 | DEFINE_TYPE(CustomJSONData, CustomSliderData); 21 | 22 | void CustomJSONData::CustomBeatmapData::ctor(int numberOfLines) { 23 | static auto const* ctor = il2cpp_utils::FindMethodUnsafe("", "BeatmapData", ".ctor", 1); 24 | PAPER_IL2CPP_CATCH_HANDLER(il2cpp_utils::RunMethodRethrow(this, ctor, numberOfLines);) 25 | 26 | INVOKE_CTOR(); 27 | 28 | ____beatmapDataItemsPerTypeAndId->_sortedListsDataProcessors->Add(csTypeOf(CustomEventData*), nullptr); 29 | // _beatmapDataItemsPerTypeAndId->_items->Add(csTypeOf(CustomEventData*), 30 | // reinterpret_cast*>( 31 | // SortedList_2::New_ctor(nullptr))); 33 | } 34 | 35 | [[deprecated("to remove")]] 36 | void CustomJSONData::CustomBeatmapData::AddBeatmapObjectDataOverride( 37 | GlobalNamespace::BeatmapObjectData* beatmapObjectData) { 38 | static auto const* base = il2cpp_utils::FindMethodUnsafe("", "BeatmapData", "AddBeatmapObjectData", 1); 39 | 40 | il2cpp_utils::RunMethodRethrow(this, base, beatmapObjectData); 41 | } 42 | 43 | [[deprecated("To remove")]] 44 | void CustomJSONData::CustomBeatmapData::AddBeatmapObjectDataInOrderOverride( 45 | GlobalNamespace::BeatmapObjectData* beatmapObjectData) { 46 | static auto const* base = il2cpp_utils::FindMethodUnsafe("", "BeatmapData", "AddBeatmapObjectDataInOrder", 1); 47 | 48 | PAPER_IL2CPP_CATCH_HANDLER(il2cpp_utils::RunMethodRethrow(this, base, beatmapObjectData);) 49 | } 50 | 51 | [[deprecated("to remove")]] 52 | void CustomJSONData::CustomBeatmapData::InsertBeatmapEventDataOverride( 53 | GlobalNamespace::BeatmapEventData* beatmapObjectData) { 54 | static auto const* base = il2cpp_utils::FindMethodUnsafe("", "BeatmapData", "InsertBeatmapEventData", 1); 55 | 56 | PAPER_IL2CPP_CATCH_HANDLER(il2cpp_utils::RunMethodRethrow(this, base, beatmapObjectData);) 57 | } 58 | 59 | [[deprecated("To remove")]] 60 | void CustomJSONData::CustomBeatmapData::InsertBeatmapEventDataInOrderOverride( 61 | GlobalNamespace::BeatmapEventData* beatmapEventData) { 62 | static auto const* base = il2cpp_utils::FindMethodUnsafe("", "BeatmapData", "InsertBeatmapEventDataInOrder", 1); 63 | 64 | PAPER_IL2CPP_CATCH_HANDLER(il2cpp_utils::RunMethodRethrow(this, base, beatmapEventData);) 65 | } 66 | 67 | void CustomJSONData::CustomBeatmapData::InsertCustomEventData(CustomJSONData::CustomEventData* customEventData) { 68 | customEventDatas.emplace_back(customEventData); 69 | auto* node = _beatmapDataItemsPerTypeAndId->InsertItem(customEventData); 70 | if (updateAllBeatmapDataOnInsert) { 71 | InsertToAllBeatmapData(customEventData, node); 72 | } 73 | } 74 | 75 | void CustomJSONData::CustomBeatmapData::InsertCustomEventDataInOrder(CustomEventData* customEventData) { 76 | InsertCustomEventData(customEventData); 77 | InsertToAllBeatmapData(customEventData, nullptr); // default is null 78 | } 79 | 80 | System::Type* CustomJSONData::CustomBeatmapData::GetCustomType(Il2CppObject* obj) { 81 | return GetCustomType(obj->klass); 82 | } 83 | 84 | System::Type* CustomJSONData::CustomBeatmapData::GetCustomType(Il2CppClass* obj) { 85 | static auto* CustomKlass = classof(CustomEventData*); 86 | 87 | if (obj == nullptr) { 88 | return nullptr; 89 | } 90 | 91 | static std::unordered_map typeMap; 92 | 93 | auto& typePtr = typeMap[obj]; 94 | 95 | if (typePtr == nullptr) { 96 | CJDLogger::Logger.fmtLog( 97 | "Checking if {} is equal to ref {} for obj {} vs ref {}", obj->namespaze, CustomKlass->namespaze, 98 | il2cpp_utils::ClassStandardName(obj), il2cpp_utils::ClassStandardName(CustomKlass)); 99 | if (std::string_view(obj->namespaze) == std::string_view(CustomKlass->namespaze) && obj != CustomKlass) { 100 | CJDLogger::Logger.fmtLog("They are custom, using parent type {}", 101 | il2cpp_utils::ClassStandardName(obj->parent)); 102 | 103 | typePtr = il2cpp_utils::GetSystemType(obj->parent); 104 | } else { 105 | CJDLogger::Logger.fmtLog("They are not custom (basegame), using self type {}", 106 | il2cpp_utils::ClassStandardName(obj)); 107 | 108 | typePtr = il2cpp_utils::GetSystemType(obj); 109 | } 110 | } 111 | 112 | return reinterpret_cast(typePtr); 113 | } 114 | 115 | CustomJSONData::CustomBeatmapData* CustomJSONData::CustomBeatmapData::BaseCopy() { 116 | auto* copy = CustomJSONData::CustomBeatmapData::New_ctor(numberOfLines); 117 | 118 | // copy the rest 119 | copy->doc = this->doc; 120 | if (this->customData) { 121 | copy->customData = this->customData->GetCopy(); 122 | } 123 | if (copy->beatmapCustomData) { 124 | copy->beatmapCustomData = this->beatmapCustomData->GetCopy(); 125 | } 126 | if (copy->levelCustomData) { 127 | copy->levelCustomData = this->levelCustomData->GetCopy(); 128 | } 129 | 130 | copy->v2orEarlier = v2orEarlier; 131 | 132 | auto enumerator = this->_specialBasicBeatmapEventKeywords->GetEnumerator(); 133 | while (enumerator.MoveNext()) { 134 | copy->AddSpecialBasicBeatmapEventKeyword(enumerator.Current); 135 | } 136 | 137 | return copy; 138 | } 139 | 140 | void CustomJSONData::CustomBeatmapEventData::ctor(float time, 141 | ::GlobalNamespace::BasicBeatmapEventType basicBeatmapEventType, 142 | int value, float floatValue) { 143 | static auto const* BeatmapEventData_Ctor = 144 | CRASH_UNLESS(il2cpp_utils::FindMethodUnsafe(classof(BasicBeatmapEventData*), ".ctor", 4)); 145 | il2cpp_utils::RunMethodRethrow(this, BeatmapEventData_Ctor, time, 146 | basicBeatmapEventType, value, floatValue); 147 | INVOKE_CTOR(); 148 | this->_time_k__BackingField = time; 149 | this->basicBeatmapEventType = basicBeatmapEventType; 150 | this->value = value; 151 | this->floatValue = floatValue; 152 | } 153 | 154 | CustomJSONData::CustomBeatmapEventData* CustomJSONData::CustomBeatmapEventData::GetCopy() { 155 | auto* copy = 156 | CustomJSONData::CustomBeatmapEventData::New_ctor(this->time, type.value__, this->value, this->floatValue); 157 | copy->set_previousSameTypeEventData(previousSameTypeEventData); 158 | copy->set_nextSameTypeEventData(nextSameTypeEventData); 159 | copy->type = type; 160 | copy->____executionOrder_k__BackingField = this->____executionOrder_k__BackingField; 161 | copy->____subtypeIdentifier_k__BackingField = this->____subtypeIdentifier_k__BackingField; 162 | copy->basicBeatmapEventType = basicBeatmapEventType; 163 | copy->subtypeIdentifier = subtypeIdentifier; 164 | copy->sameTypeIndex = sameTypeIndex; 165 | // For some reason this is needed 166 | if (this->customData) { 167 | copy->customData = this->customData->GetCopy(); 168 | } 169 | return copy; 170 | } 171 | void CustomJSONData::CustomObstacleData::ctor(float time, float beat, float endBeat, int rotation, int lineIndex, 172 | ::GlobalNamespace::NoteLineLayer lineLayer, float duration, int width, 173 | int height) { 174 | static auto const* ObstacleData_Ctor = il2cpp_utils::FindMethodUnsafe(classof(ObstacleData*), ".ctor", 9); 175 | il2cpp_utils::RunMethodRethrow(this, ObstacleData_Ctor, time, beat, endBeat, rotation, lineIndex, 176 | lineLayer, duration, width, height); 177 | INVOKE_CTOR(); 178 | this->____executionOrder_k__BackingField = beat; 179 | this->____subtypeIdentifier_k__BackingField = rotation; 180 | this->aheadTimeNoodle = 0; 181 | } 182 | 183 | CustomJSONData::CustomObstacleData* CustomJSONData::CustomObstacleData::GetCopy() { 184 | auto* copy = CustomJSONData::CustomObstacleData::New_ctor(this->time, this->executionOrder, this->endBeat, 185 | this->subtypeIdentifier, this->lineIndex, this->lineLayer, 186 | this->duration, this->width, height); 187 | if (this->customData) { 188 | copy->customData = this->customData->GetCopy(); 189 | } 190 | copy->bpm = this->bpm; 191 | copy->____executionOrder_k__BackingField = this->____executionOrder_k__BackingField; 192 | copy->____subtypeIdentifier_k__BackingField = this->____subtypeIdentifier_k__BackingField; 193 | copy->aheadTimeNoodle = this->aheadTimeNoodle; 194 | return copy; 195 | } 196 | 197 | void CustomJSONData::CustomSliderData::ctor( 198 | GlobalNamespace::SliderData::Type sliderType, ::GlobalNamespace::ColorType colorType, bool hasHeadNote, 199 | float headTime, float headBeat, int headRotation, int headLineIndex, ::GlobalNamespace::NoteLineLayer headLineLayer, 200 | ::GlobalNamespace::NoteLineLayer headBeforeJumpLineLayer, float headControlPointLengthMultiplier, 201 | ::GlobalNamespace::NoteCutDirection headCutDirection, float headCutDirectionAngleOffset, bool hasTailNote, 202 | float tailTime, int tailRotation, int tailLineIndex, ::GlobalNamespace::NoteLineLayer tailLineLayer, 203 | ::GlobalNamespace::NoteLineLayer tailBeforeJumpLineLayer, float tailControlPointLengthMultiplier, 204 | ::GlobalNamespace::NoteCutDirection tailCutDirection, float tailCutDirectionAngleOffset, 205 | ::GlobalNamespace::SliderMidAnchorMode midAnchorMode, int sliceCount, float squishAmount) { 206 | static auto const* SliderData_Ctor = il2cpp_utils::FindMethodUnsafe(classof(SliderData*), ".ctor", 24); 207 | il2cpp_utils::RunMethodRethrow( 208 | this, SliderData_Ctor, time, beat, rotation, sliderType, colorType, hasHeadNote, headTime, headLineIndex, 209 | headLineLayer, headBeforeJumpLineLayer, headControlPointLengthMultiplier, headCutDirection, 210 | headCutDirectionAngleOffset, hasTailNote, tailTime, tailLineIndex, tailLineLayer, tailBeforeJumpLineLayer, 211 | tailControlPointLengthMultiplier, tailCutDirection, tailCutDirectionAngleOffset, midAnchorMode, sliceCount, 212 | squishAmount); 213 | INVOKE_CTOR(); 214 | } 215 | 216 | CustomJSONData::CustomSliderData* CustomJSONData::CustomSliderData::GetCopy() { 217 | auto* copy = CustomJSONData::CustomSliderData::New_ctor( 218 | sliderType, colorType, hasHeadNote, time, executionOrder, rotation, headLineIndex, headLineLayer, 219 | headBeforeJumpLineLayer, headControlPointLengthMultiplier, headCutDirection, headCutDirectionAngleOffset, 220 | hasTailNote, tailTime, tailRotation, tailLineIndex, tailLineLayer, tailBeforeJumpLineLayer, 221 | tailControlPointLengthMultiplier, tailCutDirection, tailCutDirectionAngleOffset, midAnchorMode, sliceCount, 222 | squishAmount); 223 | if (this->customData) { 224 | copy->customData = this->customData->GetCopy(); 225 | } 226 | copy->bpm = this->bpm; 227 | return copy; 228 | } 229 | 230 | void CustomJSONData::CustomNoteData::ctor( 231 | float time, float beat, int rotation, int lineIndex, ::GlobalNamespace::NoteLineLayer noteLineLayer, 232 | ::GlobalNamespace::NoteLineLayer beforeJumpNoteLineLayer, ::GlobalNamespace::NoteData::GameplayType gameplayType, 233 | ::GlobalNamespace::NoteData::ScoringType scoringType, ::GlobalNamespace::ColorType colorType, 234 | ::GlobalNamespace::NoteCutDirection cutDirection, float timeToNextColorNote, float timeToPrevColorNote, 235 | int flipLineIndex, float flipYSide, float cutDirectionAngleOffset, float cutSfxVolumeMultiplier) { 236 | static auto const* NoteData_Ctor = il2cpp_utils::FindMethodUnsafe(classof(NoteData*), ".ctor", 16); 237 | il2cpp_utils::RunMethodRethrow(this, NoteData_Ctor, time, beat, rotation, lineIndex, noteLineLayer, 238 | beforeJumpNoteLineLayer, gameplayType, scoringType, colorType, 239 | cutDirection, timeToNextColorNote, timeToPrevColorNote, flipLineIndex, 240 | flipYSide, cutDirectionAngleOffset, cutSfxVolumeMultiplier); 241 | INVOKE_CTOR(); 242 | this->____executionOrder_k__BackingField = beat; 243 | this->____subtypeIdentifier_k__BackingField = rotation; 244 | this->aheadTimeNoodle = 0; 245 | } 246 | 247 | CustomJSONData::CustomNoteData* CustomJSONData::CustomNoteData::GetCopy() { 248 | auto* copy = CustomJSONData::CustomNoteData::New_ctor( 249 | time, executionOrder, subtypeIdentifier, lineIndex, noteLineLayer, beforeJumpNoteLineLayer, gameplayType, 250 | scoringType, colorType, cutDirection, timeToNextColorNote, timeToPrevColorNote, flipLineIndex, flipYSide, 251 | cutDirectionAngleOffset, cutSfxVolumeMultiplier); 252 | if (this->customData) { 253 | copy->customData = this->customData->GetCopy(); 254 | } 255 | copy->bpm = this->bpm; 256 | copy->____executionOrder_k__BackingField = this->____executionOrder_k__BackingField; 257 | copy->____subtypeIdentifier_k__BackingField = this->____subtypeIdentifier_k__BackingField; 258 | copy->aheadTimeNoodle = this->aheadTimeNoodle; 259 | return copy; 260 | } 261 | 262 | void CustomJSONData::CustomWaypointData::ctor(float time, float beat, int rotation, int lineIndex, 263 | GlobalNamespace::NoteLineLayer noteLineLayer, 264 | GlobalNamespace::OffsetDirection offsetDirection) { 265 | static auto const* WaypointData_Ctor = il2cpp_utils::FindMethodUnsafe(classof(WaypointData*), ".ctor", 6); 266 | il2cpp_utils::RunMethodRethrow(this, WaypointData_Ctor, time, beat, rotation, lineIndex, noteLineLayer, 267 | offsetDirection); 268 | INVOKE_CTOR(); 269 | this->____executionOrder_k__BackingField = beat; 270 | this->____subtypeIdentifier_k__BackingField = rotation; 271 | } 272 | 273 | CustomJSONData::CustomWaypointData* CustomJSONData::CustomWaypointData::GetCopy() { 274 | auto* copy = CustomJSONData::CustomWaypointData::New_ctor(time, executionOrder, subtypeIdentifier, lineIndex, 275 | lineLayer, offsetDirection); 276 | copy->bpm = this->bpm; 277 | copy->____executionOrder_k__BackingField = this->____executionOrder_k__BackingField; 278 | copy->____subtypeIdentifier_k__BackingField = this->____subtypeIdentifier_k__BackingField; 279 | return copy; 280 | } -------------------------------------------------------------------------------- /src/CustomBeatmapSaveDatav2.cpp: -------------------------------------------------------------------------------- 1 | #include "CustomBeatmapSaveDatav2.h" 2 | #include "misc/BeatmapFieldUtils.hpp" 3 | 4 | #include "JSONWrapper.h" 5 | #include "VList.h" 6 | #include "JsonUtils.h" 7 | 8 | #include "cpp-semver/shared/cpp-semver.hpp" 9 | #include "paper2_scotland2/shared/Profiler.hpp" 10 | 11 | using namespace GlobalNamespace; 12 | using namespace BeatmapSaveDataVersion2_6_0AndEarlier; 13 | 14 | DEFINE_TYPE(CustomJSONData::v2, CustomBeatmapSaveData); 15 | DEFINE_TYPE(CustomJSONData::v2, CustomBeatmapSaveData_NoteData); 16 | DEFINE_TYPE(CustomJSONData::v2, CustomBeatmapSaveData_SliderData); 17 | DEFINE_TYPE(CustomJSONData::v2, CustomBeatmapSaveData_ObstacleData); 18 | DEFINE_TYPE(CustomJSONData::v2, CustomBeatmapSaveData_EventData); 19 | 20 | void CustomJSONData::v2::CustomBeatmapSaveData::ctor(System::Collections::Generic::List_1* events, 21 | System::Collections::Generic::List_1* notes, 22 | System::Collections::Generic::List_1* sliders, 23 | System::Collections::Generic::List_1* waypoints, 24 | System::Collections::Generic::List_1* obstacles, 25 | SpecialEventKeywordFiltersData* specialEventsKeywordFilters) { 26 | INVOKE_CTOR(); 27 | static auto const* ctor = il2cpp_utils::FindMethodUnsafe(classof(BeatmapSaveData*), ".ctor", 6); 28 | 29 | il2cpp_utils::RunMethodRethrow(this, ctor, events, notes, sliders, waypoints, obstacles, specialEventsKeywordFilters); 30 | // this->events = events; 31 | // this->notes = notes; 32 | // this->waypoints = waypoints; 33 | // this->obstacles = obstacles; 34 | // this->specialEventsKeywordFilters = specialEventsKeywordFilters; 35 | } 36 | 37 | void CustomJSONData::v2::CustomBeatmapSaveData_NoteData::ctor(float time, int lineIndex, 38 | BeatmapSaveDataCommon::NoteLineLayer lineLayer, 39 | NoteType type, 40 | BeatmapSaveDataCommon::NoteCutDirection cutDirection) { 41 | INVOKE_CTOR(); 42 | static auto const* ctor = il2cpp_utils::FindMethodUnsafe(classof(NoteData*), ".ctor", 5); 43 | il2cpp_utils::RunMethodRethrow(this, ctor, time, lineIndex, lineLayer, type, cutDirection); 44 | // this->time = time; 45 | // this->lineIndex = lineIndex; 46 | // this->lineLayer = lineLayer; 47 | // this->type = type; 48 | // this->cutDirection = cutDirection; 49 | } 50 | 51 | void CustomJSONData::v2::CustomBeatmapSaveData_ObstacleData::ctor(float time, int lineIndex, ObstacleType type, 52 | float duration, int width) { 53 | INVOKE_CTOR(); 54 | static auto const* ctor = il2cpp_utils::FindMethodUnsafe(classof(ObstacleData*), ".ctor", 5); 55 | il2cpp_utils::RunMethodRethrow(this, ctor, time, lineIndex, type, duration, width); 56 | this->_time = time; 57 | this->_lineIndex = lineIndex; 58 | this->_type = type; 59 | this->_duration = duration; 60 | this->_width = width; 61 | } 62 | 63 | void CustomJSONData::v2::CustomBeatmapSaveData_EventData::ctor(float time, BeatmapSaveDataCommon::BeatmapEventType type, 64 | int value, float floatValue) { 65 | INVOKE_CTOR(); 66 | static auto const* ctor = il2cpp_utils::FindMethodUnsafe(classof(EventData*), ".ctor", 4); 67 | il2cpp_utils::RunMethodRethrow(this, ctor, time, type, value, floatValue); 68 | 69 | this->_time = time; 70 | this->_type = type; 71 | this->_value = value; 72 | this->_floatValue = floatValue; 73 | } 74 | 75 | void CustomJSONData::v2::CustomBeatmapSaveData_SliderData::ctor( 76 | ColorType colorType, float headTime, int headLineIndex, BeatmapSaveDataCommon::NoteLineLayer headLineLayer, 77 | float headControlPointLengthMultiplier, BeatmapSaveDataCommon::NoteCutDirection headCutDirection, float tailTime, 78 | int tailLineIndex, BeatmapSaveDataCommon::NoteLineLayer tailLineLayer, float tailControlPointLengthMultiplier, 79 | BeatmapSaveDataCommon::NoteCutDirection tailCutDirection, 80 | BeatmapSaveDataCommon::SliderMidAnchorMode sliderMidAnchorMode) { 81 | INVOKE_CTOR(); 82 | static auto const* ctor = il2cpp_utils::FindMethodUnsafe(classof(SliderData*), ".ctor", 12); 83 | il2cpp_utils::RunMethodRethrow( 84 | this, ctor, colorType, headTime, headLineIndex, headLineLayer, headControlPointLengthMultiplier, headCutDirection, 85 | tailTime, tailLineIndex, tailLineLayer, tailControlPointLengthMultiplier, tailCutDirection, sliderMidAnchorMode); 86 | } 87 | 88 | static void ConvertBeatmapSaveDataPreV2_5_0(CustomJSONData::v2::CustomBeatmapSaveData* beatmapSaveData) { 89 | CJDLogger::Logger.fmtLog("Converting pre v2.5.0"); 90 | 91 | using namespace CustomJSONData::v2; 92 | 93 | auto size = beatmapSaveData->events ? beatmapSaveData->events->_size : 0; 94 | auto list = VList::New(); 95 | list->EnsureCapacity(size); 96 | 97 | for (auto* originalEventData : ListW(beatmapSaveData->events)) { 98 | auto* eventData = il2cpp_utils::cast(originalEventData); 99 | CustomBeatmapSaveData_EventData* newData = nullptr; 100 | 101 | // Legacy BPM conversion here is deliberately ommited. 102 | // There are no maps using these events, but plenty missversioned 103 | 104 | if (eventData->type == BeatmapSaveDataCommon::BeatmapEventType::BpmChange) { 105 | if (eventData->value != 0) { 106 | newData = CRASH_UNLESS(CustomBeatmapSaveData_EventData::New_ctor(eventData->time, eventData->type, 107 | eventData->value, (float)eventData->value)); 108 | } 109 | } else { 110 | newData = CRASH_UNLESS( 111 | CustomBeatmapSaveData_EventData::New_ctor(eventData->time, eventData->type, eventData->value, 1.0F)); 112 | } 113 | 114 | if (newData) { 115 | newData->customData = eventData->customData; 116 | } 117 | 118 | list.push_back(newData ? newData : eventData); 119 | } 120 | 121 | beatmapSaveData->_events = list; 122 | } 123 | 124 | static decltype(CustomJSONData::JSONWrapper::value) GetCustomData(rapidjson::Value const& doc) { 125 | auto customDataIt = doc.FindMember("_customData"); 126 | if (customDataIt != doc.MemberEnd() && customDataIt->value.IsObject()) { 127 | return customDataIt->value; 128 | } 129 | 130 | return std::nullopt; 131 | } 132 | 133 | CustomJSONData::v2::CustomBeatmapSaveData* 134 | CustomJSONData::v2::CustomBeatmapSaveData::Deserialize(std::shared_ptr const& sharedDoc) { 135 | auto const& doc = *sharedDoc; 136 | 137 | CJDLogger::Logger.fmtLog("Parse notes"); 138 | 139 | Paper::Profiler profile; 140 | profile.startTimer(); 141 | 142 | auto notes = VList::New(); 143 | auto notesArrIt = doc.FindMember("_notes"); 144 | 145 | if (notesArrIt != doc.MemberEnd() && notesArrIt->value.IsArray()) { 146 | auto const& notesArr = notesArrIt->value; 147 | notes->EnsureCapacity(notesArr.Size()); 148 | 149 | for (rapidjson::SizeType i = 0; i < notesArr.Size(); i++) { 150 | rapidjson::Value const& note_json = notesArr[i]; 151 | 152 | float time = NEJSON::ReadOptionalFloat(note_json, "_time").value_or(0); 153 | int lineIndex = NEJSON::ReadOptionalFloat(note_json, "_lineIndex").value_or(0); 154 | auto lineLayer = 155 | BeatmapSaveDataCommon::NoteLineLayer(NEJSON::ReadOptionalFloat(note_json, "_lineLayer").value_or(0)); 156 | auto type = 157 | BeatmapSaveDataVersion2_6_0AndEarlier::NoteType(NEJSON::ReadOptionalFloat(note_json, "_type").value_or(0)); 158 | auto cutDirection = 159 | BeatmapSaveDataCommon::NoteCutDirection(NEJSON::ReadOptionalFloat(note_json, "_cutDirection").value_or(0)); 160 | auto* note = 161 | CRASH_UNLESS(CustomBeatmapSaveData_NoteData::New_ctor(time, lineIndex, lineLayer, type, cutDirection)); 162 | 163 | note->customData = GetCustomData(note_json); 164 | 165 | notes.push_back(note); 166 | } 167 | } 168 | 169 | CJDLogger::Logger.fmtLog("Parsed {} notes", notes.size()); 170 | 171 | auto sliders = VList::New(); 172 | auto slidersArrIt = doc.FindMember("_sliders"); 173 | 174 | if (slidersArrIt != doc.MemberEnd() && slidersArrIt->value.IsArray()) { 175 | auto const& slidersArr = slidersArrIt->value; 176 | sliders->EnsureCapacity(slidersArr.Size()); 177 | 178 | for (rapidjson::SizeType i = 0; i < slidersArr.Size(); i++) { 179 | rapidjson::Value const& slider_json = slidersArr[i]; 180 | 181 | int lineIndex = slider_json["_lineIndex"].GetInt(); 182 | auto lineLayer = BeatmapSaveDataCommon::NoteLineLayer(slider_json["_lineLayer"].GetInt()); 183 | auto type = BeatmapSaveDataVersion2_6_0AndEarlier::NoteType(slider_json["_type"].GetInt()); 184 | auto cutDirection = BeatmapSaveDataCommon::NoteCutDirection(slider_json["_cutDirection"].GetInt()); 185 | 186 | float time = slider_json["_time"].GetFloat(); 187 | auto colorType = ColorType(slider_json["_colorType"].GetInt()); 188 | int headLineIndex = slider_json["_headLineIndex"].GetInt(); 189 | BeatmapSaveDataCommon::NoteLineLayer noteLineLayer = slider_json["_noteLineLayer"].GetInt(); 190 | float headControlPointLengthMultiplier = slider_json["_headControlPointLengthMultiplier"].GetFloat(); 191 | BeatmapSaveDataCommon::NoteCutDirection noteCutDirection = slider_json["_noteCutDirection"].GetInt(); 192 | float tailTime = slider_json["_tailTime"].GetFloat(); 193 | int tailLineIndex = slider_json["_tailLineIndex"].GetInt(); 194 | BeatmapSaveDataCommon::NoteLineLayer tailLineLayer = slider_json["_tailLineLayer"].GetInt(); 195 | float tailControlPointLengthMultiplier = slider_json["_tailControlPointLengthMultiplier"].GetFloat(); 196 | BeatmapSaveDataCommon::NoteCutDirection tailCutDirection = slider_json["_tailCutDirection"].GetInt(); 197 | BeatmapSaveDataCommon::SliderMidAnchorMode sliderMidAnchorMode = slider_json["_sliderMidAnchorMode"].GetInt(); 198 | 199 | auto* slider = CRASH_UNLESS(CustomBeatmapSaveData_SliderData::New_ctor( 200 | colorType, time, headLineIndex, noteLineLayer, headControlPointLengthMultiplier, noteCutDirection, tailTime, 201 | tailLineIndex, tailLineLayer, tailControlPointLengthMultiplier, tailCutDirection, sliderMidAnchorMode)); 202 | slider->customData = GetCustomData(slider_json); 203 | 204 | sliders.push_back(slider); 205 | } 206 | } 207 | CJDLogger::Logger.fmtLog("Parsed {} sliders", sliders.size()); 208 | 209 | CJDLogger::Logger.fmtLog("Parse obstacles"); 210 | auto obstaclesArrIt = doc.FindMember("_obstacles"); 211 | 212 | auto obstacles = VList::New(); 213 | 214 | if (obstaclesArrIt->value.IsArray()) { 215 | auto const& obstaclesArr = obstaclesArrIt->value; 216 | 217 | obstacles->EnsureCapacity(obstaclesArr.Size()); 218 | 219 | for (rapidjson::SizeType i = 0; i < obstaclesArr.Size(); i++) { 220 | rapidjson::Value const& obstacle_json = obstaclesArr[i]; 221 | 222 | float time = obstacle_json["_time"].GetFloat(); 223 | int lineIndex = obstacle_json["_lineIndex"].GetInt(); 224 | auto type = ObstacleType(obstacle_json["_type"].GetInt()); 225 | float duration = obstacle_json["_duration"].GetFloat(); 226 | int width = obstacle_json["_width"].GetInt(); 227 | auto* obstacle = 228 | CRASH_UNLESS(CustomBeatmapSaveData_ObstacleData::New_ctor(time, lineIndex, type, duration, width)); 229 | 230 | obstacle->customData = GetCustomData(obstacle_json); 231 | 232 | obstacles.push_back(obstacle); 233 | } 234 | } 235 | 236 | CJDLogger::Logger.fmtLog("Parsed {} obstacles", obstacles.size()); 237 | 238 | CJDLogger::Logger.fmtLog("Parse events"); 239 | 240 | auto eventsArrIt = doc.FindMember("_events"); 241 | auto events = VList::New(); 242 | 243 | if (eventsArrIt != doc.MemberEnd() && eventsArrIt->value.IsArray()) { 244 | // Parse events 245 | rapidjson::Value const& eventsArr = eventsArrIt->value; 246 | events->EnsureCapacity(eventsArr.Size()); 247 | 248 | for (rapidjson::SizeType i = 0; i < eventsArr.Size(); i++) { 249 | rapidjson::Value const& event_json = eventsArr[i]; 250 | 251 | float time = event_json["_time"].GetFloat(); 252 | auto type = BeatmapSaveDataCommon::BeatmapEventType(event_json["_type"].GetInt()); 253 | int value = event_json["_value"].GetInt(); 254 | float floatValue = 0; 255 | 256 | auto floatValueIt = event_json.FindMember("_floatValue"); 257 | if (floatValueIt != event_json.MemberEnd()) { 258 | floatValue = floatValueIt->value.GetFloat(); 259 | } 260 | 261 | auto* event = CRASH_UNLESS(CustomBeatmapSaveData_EventData::New_ctor(time, type, value, floatValue)); 262 | event->customData = GetCustomData(event_json); 263 | 264 | events.push_back(event); 265 | } 266 | } 267 | CJDLogger::Logger.fmtLog("Parsed {} events", events.size()); 268 | 269 | CJDLogger::Logger.fmtLog("Parse waypoints"); 270 | auto waypoints_arrIt = doc.FindMember("_waypoints"); 271 | 272 | VList waypoints = VList::New(); 273 | 274 | if (waypoints_arrIt != doc.MemberEnd() && waypoints_arrIt->value.IsArray()) { 275 | rapidjson::Value const& waypoints_arr = doc["_waypoints"]; 276 | 277 | waypoints->EnsureCapacity(waypoints_arr.Size()); 278 | 279 | for (rapidjson::SizeType i = 0; i < waypoints_arr.Size(); i++) { 280 | rapidjson::Value const& waypoint_json = waypoints_arr[i]; 281 | 282 | float time = waypoint_json["_time"].GetFloat(); 283 | int lineIndex = waypoint_json["_lineIndex"].GetInt(); 284 | auto lineLayer = BeatmapSaveDataCommon::NoteLineLayer(waypoint_json["_lineLayer"].GetInt()); 285 | auto offsetDirection = BeatmapSaveDataCommon::OffsetDirection(waypoint_json["_offsetDirection"].GetInt()); 286 | auto* waypoint = CustomJSONData::NewFast(time, lineIndex, lineLayer, offsetDirection); 287 | waypoints.push_back(waypoint); 288 | } 289 | } 290 | CJDLogger::Logger.fmtLog("Parsed {} waypoints", waypoints.size()); 291 | 292 | CJDLogger::Logger.fmtLog("Parse specialEventsKeywordFilters"); 293 | auto specialEventsKeywordFiltersJsonObjIt = doc.FindMember("_specialEventsKeywordFilters"); 294 | VList specialEventsKeywordFiltersList = VList::New(); 295 | 296 | if (specialEventsKeywordFiltersJsonObjIt != doc.MemberEnd()) { 297 | rapidjson::Value const& specialEventsKeywordFiltersJsonObj = specialEventsKeywordFiltersJsonObjIt->value; 298 | 299 | auto _keywords = specialEventsKeywordFiltersJsonObj.FindMember("_keywords"); 300 | 301 | if (_keywords != specialEventsKeywordFiltersJsonObj.MemberEnd()) { 302 | specialEventsKeywordFiltersList->EnsureCapacity(_keywords->value.Size()); 303 | 304 | for (auto const& keyword_json : _keywords->value.GetArray()) { 305 | std::string keyword = keyword_json["_keyword"].GetString(); 306 | Il2CppString* keyword_il2cpp = il2cpp_utils::newcsstr(keyword); 307 | 308 | auto specialEventsArray = keyword_json["_specialEvents"].GetArray(); 309 | auto specialEvents = VList::New(specialEventsArray.Size()); 310 | 311 | for (auto const& specialEvent : specialEventsArray) { 312 | // safety, why not? 313 | if (!specialEvent.IsNumber()) { 314 | continue; 315 | } 316 | 317 | specialEvents.push_back(specialEvent.GetInt()); 318 | } 319 | 320 | specialEventsKeywordFiltersList.push_back( 321 | CustomJSONData::NewFast(keyword_il2cpp, specialEvents.getPtr())); 322 | } 323 | } 324 | } 325 | auto* specialEventsKeywordFilters = 326 | CustomJSONData::NewFast(specialEventsKeywordFiltersList.getPtr()); 327 | 328 | CJDLogger::Logger.fmtLog("Parse root"); 329 | auto* saveData = CRASH_UNLESS( 330 | CustomBeatmapSaveData::New_ctor(events, notes, sliders, waypoints, obstacles, specialEventsKeywordFilters)); 331 | 332 | saveData->doc = sharedDoc; 333 | 334 | saveData->customEventsData = std::make_shared>(); 335 | auto customDataIt = doc.FindMember("_customData"); 336 | if (customDataIt->value.IsObject()) { 337 | rapidjson::Value const& customData = customDataIt->value; 338 | saveData->customData = customData; 339 | 340 | auto customEventsIt = customData.FindMember("_customEvents"); 341 | if (customEventsIt != customData.MemberEnd() && customEventsIt->value.IsArray()) { 342 | CJDLogger::Logger.fmtLog("Parse custom events"); 343 | 344 | rapidjson::Value const& customEventsArr = customEventsIt->value; 345 | for (rapidjson::SizeType i = 0; i < customEventsArr.Size(); i++) { 346 | rapidjson::Value const& eventValue = customEventsArr[i]; 347 | 348 | // Any consequences? Nah never 349 | auto typeIt = eventValue.FindMember("_type"); 350 | if (typeIt == eventValue.MemberEnd() || typeIt->value.IsNull()) { 351 | continue; 352 | } 353 | 354 | float time = 0; 355 | // Dammit Reaxt 356 | auto timeIt = eventValue.FindMember("_time"); 357 | if (timeIt != eventValue.MemberEnd()) { 358 | rapidjson::Value const& timeValue = timeIt->value; 359 | if (timeValue.GetType() == rapidjson::Type::kStringType) { 360 | // Reaxt why 361 | time = std::stof(timeValue.GetString()); 362 | } else { 363 | time = timeValue.GetFloat(); 364 | } 365 | } 366 | 367 | std::string_view type = typeIt->value.GetString(); 368 | 369 | rapidjson::Value const* data = &eventValue["_data"]; 370 | saveData->customEventsData->emplace_back(type, time, data); 371 | } 372 | 373 | CJDLogger::Logger.fmtLog("Parsed {} custom events", saveData->customEventsData->size()); 374 | } 375 | } 376 | 377 | auto versionIt = doc.FindMember("_version"); 378 | if (versionIt != doc.MemberEnd()) { 379 | saveData->_version = versionIt->value.GetString(); 380 | } else { 381 | saveData->_version = nullptr; 382 | } 383 | 384 | profile.endTimer(); 385 | 386 | CJDLogger::Logger.fmtLog( 387 | "v2 json parse took {}ms", 388 | static_cast(std::chrono::duration_cast(profile.elapsedTime()).count())); 389 | CJDLogger::Logger.fmtLog("v2 Version {}", static_cast(saveData->version ?: "null")); 390 | 391 | return saveData; 392 | } 393 | -------------------------------------------------------------------------------- /src/CustomEventData.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "CustomEventData.h" 4 | 5 | #include "CJDLogger.h" 6 | #include "GlobalNamespace/BasicBeatmapEventData.hpp" 7 | 8 | #include "CustomJSONDataHooks.h" 9 | 10 | #include "beatsaber-hook/shared/utils/il2cpp-type-check.hpp" 11 | 12 | #include "GlobalNamespace/CallbacksInTime.hpp" 13 | 14 | #include "System/Collections/Generic/Dictionary_2.hpp" 15 | 16 | using namespace CustomJSONData; 17 | using namespace GlobalNamespace; 18 | 19 | DEFINE_TYPE(CustomJSONData, CustomEventData) 20 | DEFINE_TYPE(CustomJSONData, CustomBeatmapDataCallbackWrapper) 21 | 22 | CJD_MOD_EXPORT std::vector CustomEventCallbacks::customEventCallbacks; 23 | CJD_MOD_EXPORT SafePtr> 24 | CustomEventCallbacks::firstNode; 25 | 26 | void CustomEventData::ctor(float time) { 27 | INVOKE_CTOR(); 28 | static auto const* ctor = il2cpp_utils::FindMethodUnsafe(classof(BeatmapDataItem*), ".ctor", 4); 29 | il2cpp_utils::RunMethodRethrow(this, ctor, time, 0, 0, BeatmapDataItemType(2)); 30 | BeatmapDataItem::_time_k__BackingField = time; 31 | BeatmapDataItem::type = 2; 32 | } 33 | CustomEventData* CustomEventData::New(float time, std::string_view type, size_t typeHash, rapidjson::Value const* data) { 34 | auto event = CustomEventData::New_ctor(time); 35 | CRASH_UNLESS(data); 36 | 37 | event->typeHash = typeHash; 38 | event->type = type; 39 | event->data = data; 40 | 41 | return event; 42 | } 43 | 44 | CustomEventData* CustomEventData::GetCopy() { 45 | auto* copy = CustomJSONData::CustomEventData::New(this->time, this->type, typeHash, this->data); 46 | return copy; 47 | } 48 | 49 | void CustomBeatmapDataCallbackWrapper::ctor() { 50 | INVOKE_CTOR(); 51 | reinterpret_cast(this)->_ctor(0.0F, csTypeOf(CustomEventData*), 52 | ArrayW((il2cpp_array_size_t)0)); 53 | 54 | redirectEvent = nullptr; 55 | } 56 | 57 | void CustomBeatmapDataCallbackWrapper::CallCallback(BeatmapDataItem* item) { 58 | static auto CustomEventDataKlass = classof(CustomEventData*); 59 | // CRASH_UNLESS(item->klass == CustomEventDataKlass); 60 | 61 | 62 | 63 | PAPER_IL2CPP_CATCH_HANDLER( 64 | // 65 | if (redirectEvent) { 66 | redirectEvent(controller, item); 67 | return; 68 | } 69 | // 70 | ) 71 | 72 | // should never get to this point 73 | if (item->klass != CustomEventDataKlass) { 74 | CJDLogger::Logger.debug("Invokiong item {} time {}", il2cpp_utils::ClassStandardName(item->klass), item->time); 75 | return; 76 | } 77 | 78 | PAPER_IL2CPP_CATCH_HANDLER( 79 | // 80 | auto castedItem = il2cpp_utils::cast(item); 81 | for (auto const& customEvents : CustomEventCallbacks::customEventCallbacks) { 82 | try { 83 | customEvents.callback(controller, castedItem); 84 | } catch (std::exception const& e) { 85 | CJDLogger::Logger.fmtLog("Caught exception in callback {}", fmt::ptr(customEvents.callback)); 86 | throw e; 87 | } catch (...) { 88 | CJDLogger::Logger.fmtLog("Caught exception in callback {}", fmt::ptr(customEvents.callback)); 89 | throw; 90 | } 91 | }) 92 | } 93 | 94 | void CJD_MOD_EXPORT CustomEventCallbacks::AddCustomEventCallback( 95 | void (*callback)(GlobalNamespace::BeatmapCallbacksController*, CustomJSONData::CustomEventData*)) { 96 | customEventCallbacks.emplace_back(callback); 97 | } 98 | 99 | void CJD_MOD_EXPORT 100 | CustomEventCallbacks::RegisterCallbacks(GlobalNamespace::BeatmapCallbacksController* callbackController) { 101 | CJDLogger::Logger.fmtLog("REGISTER CUSTOM CALLBACK!"); 102 | auto* wrapper = CustomBeatmapDataCallbackWrapper::New_ctor(); 103 | wrapper->controller = callbackController; 104 | 105 | // register it 106 | // AddBeatmapCallback 107 | // do this to avoid using delegates 108 | auto callbacksInTime = callbackController->_callbacksInTimes->get_Item(0); 109 | callbacksInTime->AddCallback(wrapper); 110 | CJDLogger::Logger.fmtLog("REGISTERED CUSTOM CALLBACK!"); 111 | } 112 | -------------------------------------------------------------------------------- /src/JSONWrapper.cpp: -------------------------------------------------------------------------------- 1 | // This is my most favorite file in this project 2 | 3 | #include "JSONWrapper.h" 4 | 5 | using namespace CustomJSONData; 6 | 7 | DEFINE_TYPE(CustomJSONData, JSONWrapper); 8 | DEFINE_TYPE(CustomJSONData, JSONWrapperUTF16); 9 | DEFINE_TYPE(CustomJSONData, DocumentWrapper); 10 | 11 | void JSONWrapper::ctor() { 12 | INVOKE_CTOR(); 13 | } 14 | 15 | JSONWrapper* JSONWrapper::GetCopy() { 16 | auto* copy = JSONWrapper::New_ctor(); 17 | 18 | copy->value = value; 19 | copy->associatedData = associatedData; 20 | 21 | return copy; 22 | } 23 | 24 | void JSONWrapperUTF16::ctor() { 25 | INVOKE_CTOR(); 26 | } 27 | 28 | JSONWrapperUTF16* JSONWrapperUTF16::GetCopy() { 29 | auto* copy = JSONWrapperUTF16::New_ctor(); 30 | 31 | copy->value = value; 32 | copy->associatedData = associatedData; 33 | 34 | return copy; 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/BeatmapHooks.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "CustomJSONDataHooks.h" 3 | 4 | #include "HookUtils.hpp" 5 | #include "CustomBeatmapData.h" 6 | 7 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/zzzz__EventData_def.hpp" 8 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/BeatmapSaveDataItem.hpp" 9 | #include "BeatmapDataLoaderVersion2_6_0AndEarlier/BeatmapDataLoader.hpp" 10 | 11 | #include "BeatmapDataLoaderVersion3/BeatmapDataLoader.hpp" 12 | #include "BeatmapSaveDataVersion3/BeatmapSaveDataItem.hpp" 13 | 14 | #include "BeatmapDataLoaderVersion4/BeatmapDataLoader.hpp" 15 | #include "BeatmapSaveDataVersion4/LightshowSaveData.hpp" 16 | 17 | #include "GlobalNamespace/BpmTimeProcessor.hpp" 18 | #include "GlobalNamespace/EnvironmentKeywords.hpp" 19 | #include "GlobalNamespace/IEnvironmentInfo.hpp" 20 | #include "GlobalNamespace/IEnvironmentLightGroups.hpp" 21 | #include "GlobalNamespace/EnvironmentLightGroups.hpp" 22 | #include "GlobalNamespace/DefaultEnvironmentEvents.hpp" 23 | #include "GlobalNamespace/BeatmapObjectData.hpp" 24 | #include "GlobalNamespace/NoteData.hpp" 25 | #include "GlobalNamespace/BeatmapDataSortedListForTypeAndIds_1.hpp" 26 | #include "GlobalNamespace/BasicBeatmapEventDataProcessor.hpp" 27 | #include "GlobalNamespace/BeatmapDataStrobeFilterTransform.hpp" 28 | #include "GlobalNamespace/LightColorBeatmapEventData.hpp" 29 | #include "GlobalNamespace/EnvironmentIntensityReductionOptions.hpp" 30 | #include "GlobalNamespace/CallbacksInTime.hpp" 31 | #include "GlobalNamespace/IReadonlyBeatmapData.hpp" 32 | #include "GlobalNamespace/BeatmapEventDataLightsExtensions.hpp" 33 | #include "GlobalNamespace/BurstSliderSpawner.hpp" 34 | #include "GlobalNamespace/VariableMovementDataProvider.hpp" 35 | #include "GlobalNamespace/NoteSpawnData.hpp" 36 | #include "GlobalNamespace/NoteCutDirectionExtensions.hpp" 37 | #include "GlobalNamespace/BeatmapObjectSpawnMovementData.hpp" 38 | 39 | #include "System/Action.hpp" 40 | 41 | #include "UnityEngine/JsonUtility.hpp" 42 | 43 | #include "System/Reflection/MemberInfo.hpp" 44 | #include "System/Collections/Generic/InsertionBehavior.hpp" 45 | 46 | #include "beatsaber-hook/shared/utils/typedefs-list.hpp" 47 | #include "beatsaber-hook/shared/utils/hooking.hpp" 48 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 49 | // for rapidjson error parsing 50 | #include "beatsaber-hook/shared/rapidjson/include/rapidjson/error/en.h" 51 | 52 | #include "cpp-semver/shared/cpp-semver.hpp" 53 | 54 | #include "paper2_scotland2/shared/Profiler.hpp" 55 | 56 | #include "sombrero/shared/linq_functional.hpp" 57 | #include "sombrero/shared/Vector2Utils.hpp" 58 | #include "sombrero/shared/Vector3Utils.hpp" 59 | 60 | #include "custom-types/shared/register.hpp" 61 | 62 | #include "songcore/shared/CustomJSONData.hpp" 63 | 64 | #include "JSONWrapper.h" 65 | #include "CustomBeatmapSaveDatav2.h" 66 | #include "CustomBeatmapSaveDatav3.h" 67 | #include "CustomBeatmapData.h" 68 | #include "misc/BeatmapFieldUtils.hpp" 69 | #include "misc/BeatmapDataLoaderUtils.hpp" 70 | #include "CustomJSONDataHooks.h" 71 | #include "CJDLogger.h" 72 | #include "VList.h" 73 | 74 | #include 75 | #include 76 | #include 77 | #include 78 | 79 | using namespace System; 80 | using namespace System::Collections::Generic; 81 | using namespace GlobalNamespace; 82 | using namespace CustomJSONData; 83 | using namespace BeatmapSaveDataVersion3; 84 | using namespace BeatmapSaveDataVersion4; 85 | 86 | MAKE_HOOK_FIND_INSTANCE(CustomBeatmapDataSortedListForTypes_InsertItem, 87 | classof(BeatmapDataSortedListForTypeAndIds_1*), "InsertItem", 88 | System::Collections::Generic::LinkedListNode_1*, 89 | BeatmapDataSortedListForTypeAndIds_1* self, BeatmapDataItem* item) { 90 | auto* list = self->GetList(CustomBeatmapData::GetCustomType(item), item->get_subtypeGroupIdentifier()); 91 | 92 | auto* node = list->Insert(item); 93 | // Remove to avoid exception 94 | self->_itemToNodeMap->TryInsert(item, node, InsertionBehavior::OverwriteExisting); 95 | 96 | return node; 97 | } 98 | 99 | MAKE_HOOK_FIND_INSTANCE(CustomBeatmapDataSortedListForTypes_RemoveItem, 100 | classof(BeatmapDataSortedListForTypeAndIds_1*), "RemoveItem", void, 101 | BeatmapDataSortedListForTypeAndIds_1* self, BeatmapDataItem* item) { 102 | auto* list = self->GetList(CustomBeatmapData::GetCustomType(item), item->get_subtypeGroupIdentifier()); 103 | System::Collections::Generic::LinkedListNode_1* node = nullptr; 104 | if (self->_itemToNodeMap->TryGetValue(item, byref(node))) { 105 | list->Remove(node); 106 | } 107 | } 108 | 109 | MAKE_PAPER_HOOK_MATCH(BeatmapData_GetCopy, &CustomBeatmapData::GetCopy, BeatmapData*, BeatmapData* self) { 110 | static auto* CustomBeatmapDataKlass = classof(CustomBeatmapData*); 111 | 112 | if (self->klass == CustomBeatmapDataKlass) { 113 | return reinterpret_cast(self)->GetCopyOverride(); 114 | } 115 | 116 | return BeatmapData_GetCopy(self); 117 | } 118 | 119 | MAKE_PAPER_HOOK_MATCH( 120 | BeatmapData_GetFilteredCopy, &CustomBeatmapData::GetFilteredCopy, BeatmapData*, BeatmapData* self, 121 | System::Func_2<::GlobalNamespace::BeatmapDataItem*, ::GlobalNamespace::BeatmapDataItem*>* processDataItem) { 122 | static auto* CustomBeatmapDataKlass = classof(CustomBeatmapData*); 123 | 124 | if (self->klass == CustomBeatmapDataKlass) { 125 | return reinterpret_cast(self)->GetFilteredCopyOverride( 126 | [&](auto i) constexpr { return processDataItem->Invoke(i); }); 127 | } 128 | 129 | return BeatmapData_GetFilteredCopy(self, processDataItem); 130 | } 131 | 132 | MAKE_PAPER_HOOK_MATCH(CustomAddBeatmapObjectData, &BeatmapData::AddBeatmapObjectData, void, BeatmapData* self, 133 | BeatmapObjectData* item) { 134 | if (auto customSelf = il2cpp_utils::try_cast(self).value_or(nullptr)) { 135 | customSelf->beatmapObjectDatas.emplace_back(item); 136 | } 137 | CustomAddBeatmapObjectData(self, item); 138 | } 139 | 140 | MAKE_PAPER_HOOK_MATCH(CustomInsertBeatmapEventData, &BeatmapData::InsertBeatmapEventData, void, BeatmapData* self, 141 | BeatmapEventData* item) { 142 | if (auto customSelf = il2cpp_utils::try_cast(self).value_or(nullptr)) { 143 | customSelf->beatmapEventDatas.emplace_back(item); 144 | } 145 | CustomInsertBeatmapEventData(self, item); 146 | } 147 | MAKE_PAPER_HOOK_MATCH(BurstSliderSpawner_ProcessSliderData, &BurstSliderSpawner::ProcessSliderData, void, 148 | ::GlobalNamespace::SliderData* sliderData, 149 | ::ByRef<::GlobalNamespace::SliderSpawnData> sliderSpawnData, bool forceIsFirstNote, 150 | ::GlobalNamespace::VariableMovementDataProvider* variableMovementDataProvider, 151 | ::GlobalNamespace::BurstSliderSpawner_ProcessNoteDataDelegate* processNoteData) { 152 | 153 | static auto CustomKlass = classof(CustomJSONData::CustomSliderData*); 154 | 155 | auto* customSliderData = il2cpp_utils::try_cast(sliderData).value_or(nullptr); 156 | if (customSliderData == nullptr) { 157 | return BurstSliderSpawner_ProcessSliderData(sliderData, sliderSpawnData, forceIsFirstNote, 158 | variableMovementDataProvider, processNoteData); 159 | } 160 | 161 | float halfJumpDuration = variableMovementDataProvider->halfJumpDuration; 162 | float headGravity = variableMovementDataProvider->CalculateCurrentNoteJumpGravity(sliderSpawnData->headGravityBase); 163 | float tailGravity = variableMovementDataProvider->CalculateCurrentNoteJumpGravity(sliderSpawnData->tailGravityBase); 164 | 165 | auto vector = Sombrero::FastVector2(sliderSpawnData->headNoteOffset.x, 166 | sliderSpawnData->headNoteOffset.y + headGravity * halfJumpDuration * halfJumpDuration * 0.5f); 167 | 168 | auto vector2 = Sombrero::FastVector2(sliderSpawnData->tailNoteOffset.x, 169 | sliderSpawnData->tailNoteOffset.y + tailGravity * halfJumpDuration * halfJumpDuration * 0.5f); 170 | 171 | auto vector3 = vector2 - vector; 172 | float magnitude = vector3.Magnitude(); 173 | 174 | float angle = (NoteCutDirectionExtensions::RotationAngle(sliderData->headCutDirection) - 90.0f + 175 | sliderData->headCutDirectionAngleOffset) * 0.017453292f; 176 | auto vector4 = Sombrero::FastVector2(std::cos(angle), std::sin(angle)) * 0.5f * magnitude; 177 | 178 | int sliceCount = sliderData->sliceCount; 179 | float squishAmount = sliderData->squishAmount; 180 | float time = sliderData->time; 181 | float tailTime = sliderData->tailTime; 182 | float num3 = (tailTime - time) * 0.5f; 183 | 184 | auto bezierCurve = [](Sombrero::FastVector2 p0, Sombrero::FastVector2 p1, Sombrero::FastVector2 p2, float t, 185 | Sombrero::FastVector2& pos, Sombrero::FastVector2& tangent) constexpr { 186 | float num = 1.0f - t; 187 | pos = p0 * num * num + p1 * 2.0f * num * t + p2 * t * t; 188 | tangent = (p1 - p0) * 2.0f * (1.0f - t) + (p2 - p1) * 2.0f * t; 189 | }; 190 | 191 | for (int i = 1; i < sliceCount; i++) { 192 | float sliceT = (float)i / (float)(sliceCount - 1); 193 | int index = ((i < sliceCount - 1) ? sliderData->headLineIndex : sliderData->tailLineIndex); 194 | auto noteLineLayer = ((i < sliceCount - 1) ? sliderData->headLineLayer : sliderData->tailLineLayer); 195 | 196 | // TRANSPILE HERE 197 | auto noteData = 198 | CreateCustomBurstNoteData(std::lerp(time, tailTime, sliceT), sliderData->beat, sliderData->rotation, index, 199 | noteLineLayer, sliderData->headBeforeJumpLineLayer, sliderData->colorType, 200 | NoteCutDirection::Any, 1.0f, customSliderData->customData->value); 201 | // copy the AD from the original note 202 | noteData->customData->associatedData = customSliderData->customData->associatedData; 203 | // TRANSPILE END 204 | 205 | noteData->timeToPrevColorNote = sliceT * num3; 206 | Sombrero::FastVector2 position; 207 | Sombrero::FastVector2 tangent; 208 | bezierCurve(Sombrero::FastVector2::zero(), vector4, vector3, sliceT * squishAmount, position, tangent); 209 | noteData->SetCutDirectionAngleOffset(Sombrero::FastVector2::SignedAngle({ 0.0f, -1.0f }, tangent)); 210 | noteData->timeToNextColorNote = ((i == sliceCount - 1) ? 1.0f : 0.4f); 211 | 212 | auto headNoteOffset = Sombrero::FastVector3(sliderSpawnData->headNoteOffset); 213 | auto noteSpawnData = NoteSpawnData(headNoteOffset + Sombrero::FastVector3(vector.x, 0.0f, 0.0f), 214 | headNoteOffset + Sombrero::FastVector3(vector.x, 0.0f, 0.0f), 215 | headNoteOffset + Sombrero::FastVector3(vector.x, 0.0f, 0.0f), 216 | headGravity * halfJumpDuration * halfJumpDuration * 0.5f + vector.y); 217 | 218 | processNoteData->Invoke(noteData, noteSpawnData, forceIsFirstNote); 219 | } 220 | } 221 | 222 | void CustomJSONData::InstallBeatmapHooks() { 223 | INSTALL_HOOK_ORIG(CJDLogger::Logger, CustomAddBeatmapObjectData); 224 | INSTALL_HOOK_ORIG(CJDLogger::Logger, CustomInsertBeatmapEventData); 225 | 226 | INSTALL_HOOK_ORIG(CJDLogger::Logger, CustomBeatmapDataSortedListForTypes_InsertItem); 227 | INSTALL_HOOK_ORIG(CJDLogger::Logger, CustomBeatmapDataSortedListForTypes_RemoveItem); 228 | INSTALL_HOOK_ORIG(CJDLogger::Logger, BeatmapData_GetFilteredCopy); 229 | INSTALL_HOOK_ORIG(CJDLogger::Logger, BeatmapData_GetCopy); 230 | 231 | INSTALL_HOOK_ORIG(CJDLogger::Logger, BurstSliderSpawner_ProcessSliderData); 232 | } -------------------------------------------------------------------------------- /src/hooks/CustomJSONDataHooks.cpp: -------------------------------------------------------------------------------- 1 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/zzzz__EventData_def.hpp" 2 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/BeatmapSaveDataItem.hpp" 3 | #include "BeatmapDataLoaderVersion2_6_0AndEarlier/BeatmapDataLoader.hpp" 4 | 5 | #include "BeatmapDataLoaderVersion3/BeatmapDataLoader.hpp" 6 | #include "BeatmapSaveDataVersion3/BeatmapSaveDataItem.hpp" 7 | 8 | #include "BeatmapDataLoaderVersion4/BeatmapDataLoader.hpp" 9 | #include "BeatmapSaveDataVersion4/LightshowSaveData.hpp" 10 | 11 | #include "GlobalNamespace/BpmTimeProcessor.hpp" 12 | #include "GlobalNamespace/EnvironmentKeywords.hpp" 13 | #include "GlobalNamespace/IEnvironmentInfo.hpp" 14 | #include "GlobalNamespace/IEnvironmentLightGroups.hpp" 15 | #include "GlobalNamespace/EnvironmentLightGroups.hpp" 16 | #include "GlobalNamespace/DefaultEnvironmentEvents.hpp" 17 | #include "GlobalNamespace/BeatmapObjectData.hpp" 18 | #include "GlobalNamespace/NoteData.hpp" 19 | #include "GlobalNamespace/BeatmapDataSortedListForTypeAndIds_1.hpp" 20 | #include "GlobalNamespace/BasicBeatmapEventDataProcessor.hpp" 21 | #include "GlobalNamespace/BeatmapDataStrobeFilterTransform.hpp" 22 | #include "GlobalNamespace/LightColorBeatmapEventData.hpp" 23 | #include "GlobalNamespace/EnvironmentIntensityReductionOptions.hpp" 24 | #include "GlobalNamespace/CallbacksInTime.hpp" 25 | #include "GlobalNamespace/IReadonlyBeatmapData.hpp" 26 | #include "GlobalNamespace/BeatmapEventDataLightsExtensions.hpp" 27 | 28 | #include "System/Action.hpp" 29 | 30 | #include "UnityEngine/JsonUtility.hpp" 31 | 32 | #include "System/Reflection/MemberInfo.hpp" 33 | #include "System/Collections/Generic/InsertionBehavior.hpp" 34 | 35 | #include "beatsaber-hook/shared/utils/typedefs-list.hpp" 36 | #include "beatsaber-hook/shared/utils/hooking.hpp" 37 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 38 | // for rapidjson error parsing 39 | #include "beatsaber-hook/shared/rapidjson/include/rapidjson/error/en.h" 40 | 41 | #include "cpp-semver/shared/cpp-semver.hpp" 42 | 43 | #include "paper2_scotland2/shared/Profiler.hpp" 44 | 45 | #include "sombrero/shared/linq_functional.hpp" 46 | #include "sombrero/shared/Vector2Utils.hpp" 47 | #include "sombrero/shared/Vector3Utils.hpp" 48 | 49 | #include "custom-types/shared/register.hpp" 50 | 51 | #include "songcore/shared/CustomJSONData.hpp" 52 | 53 | #include "JSONWrapper.h" 54 | #include "CustomBeatmapSaveDatav2.h" 55 | #include "CustomBeatmapSaveDatav3.h" 56 | #include "CustomBeatmapData.h" 57 | #include "misc/BeatmapFieldUtils.hpp" 58 | #include "misc/BeatmapDataLoaderUtils.hpp" 59 | #include "CustomJSONDataHooks.h" 60 | #include "CJDLogger.h" 61 | #include "VList.h" 62 | 63 | #include 64 | #include 65 | #include 66 | #include 67 | 68 | using namespace System; 69 | using namespace System::Collections::Generic; 70 | using namespace GlobalNamespace; 71 | using namespace CustomJSONData; 72 | using namespace BeatmapSaveDataVersion3; 73 | using namespace BeatmapSaveDataVersion4; 74 | 75 | 76 | 77 | constexpr bool Approximately(float a, float b) { 78 | return std::abs(b - a) < std::max(1E-06F * std::max(std::abs(a), std::abs(b)), 1E-45F * 8.0F); 79 | } 80 | 81 | // clang-format off 82 | /** 83 | BeatmapData beatmapData2 = new BeatmapData(beatmapData.numberOfLines); 84 | bool flag = environmentIntensityReductionOptions.compressExpand == EnvironmentIntensityReductionOptions.CompressExpandReductionType.RemoveWithStrobeFilter; 85 | bool flag2 = environmentIntensityReductionOptions.rotateRings == EnvironmentIntensityReductionOptions.RotateRingsReductionType.RemoveWithStrobeFilter; 86 | Dictionary dictionary = new Dictionary 87 | { 88 | { 89 | BasicBeatmapEventType.Event0, 90 | new BeatmapDataStrobeFilterTransform.StrobeStreakData() 91 | }, 92 | { 93 | BasicBeatmapEventType.Event1, 94 | new BeatmapDataStrobeFilterTransform.StrobeStreakData() 95 | }, 96 | { 97 | BasicBeatmapEventType.Event2, 98 | new BeatmapDataStrobeFilterTransform.StrobeStreakData() 99 | }, 100 | { 101 | BasicBeatmapEventType.Event3, 102 | new BeatmapDataStrobeFilterTransform.StrobeStreakData() 103 | }, 104 | { 105 | BasicBeatmapEventType.Event4, 106 | new BeatmapDataStrobeFilterTransform.StrobeStreakData() 107 | } 108 | }; 109 | foreach (BeatmapDataItem beatmapDataItem in beatmapData.allBeatmapDataItems) 110 | { 111 | LightColorBeatmapEventData lightColorBeatmapEventData = beatmapDataItem as LightColorBeatmapEventData; 112 | if (lightColorBeatmapEventData != null) 113 | { 114 | lightColorBeatmapEventData.DisableStrobe(); 115 | beatmapData2.InsertBeatmapEventDataInOrder(lightColorBeatmapEventData); 116 | } 117 | else 118 | { 119 | BasicBeatmapEventData basicBeatmapEventData = beatmapDataItem as BasicBeatmapEventData; 120 | if (basicBeatmapEventData == null) 121 | { 122 | BeatmapEventData beatmapEventData = beatmapDataItem as BeatmapEventData; 123 | if (beatmapEventData != null) 124 | { 125 | beatmapData2.InsertBeatmapEventDataInOrder(beatmapEventData); 126 | } 127 | else 128 | { 129 | BeatmapObjectData beatmapObjectData = beatmapDataItem as BeatmapObjectData; 130 | if (beatmapObjectData != null) 131 | { 132 | beatmapData2.AddBeatmapObjectDataInOrder(beatmapObjectData); 133 | } 134 | } 135 | } 136 | else if ((!flag || basicBeatmapEventData.basicBeatmapEventType != BasicBeatmapEventType.Event9) && (!flag2 || basicBeatmapEventData.basicBeatmapEventType != BasicBeatmapEventType.Event8)) 137 | { 138 | if (!basicBeatmapEventData.basicBeatmapEventType.IsCoreLightIntensityChangeEvent()) 139 | { 140 | beatmapData2.InsertBeatmapEventDataInOrder(basicBeatmapEventData); 141 | } 142 | else if (basicBeatmapEventData.basicBeatmapEventType.IsCoreLightIntensityChangeEvent() && basicBeatmapEventData.HasLightFadeEventDataValue()) 143 | { 144 | beatmapData2.InsertBeatmapEventDataInOrder(basicBeatmapEventData); 145 | } 146 | else 147 | { 148 | BeatmapDataStrobeFilterTransform.StrobeStreakData strobeStreakData = dictionary[basicBeatmapEventData.basicBeatmapEventType]; 149 | if (strobeStreakData.isActive) 150 | { 151 | if (basicBeatmapEventData.time - strobeStreakData.lastSwitchTime < 0.1f) 152 | { 153 | strobeStreakData.AddStrobeData(basicBeatmapEventData); 154 | } 155 | else 156 | { 157 | if (!Mathf.Approximately(strobeStreakData.strobeStartTime, strobeStreakData.lastSwitchTime)) 158 | { 159 | int onEventDataValue = BeatmapDataStrobeFilterTransform.GetOnEventDataValue(strobeStreakData.startColorType); 160 | BasicBeatmapEventData beatmapEventData2 = new BasicBeatmapEventData(strobeStreakData.strobeStartTime, basicBeatmapEventData.basicBeatmapEventType, onEventDataValue, basicBeatmapEventData.floatValue); 161 | beatmapData2.InsertBeatmapEventDataInOrder(beatmapEventData2); 162 | int value; 163 | if (strobeStreakData.lastIsOn) 164 | { 165 | value = BeatmapDataStrobeFilterTransform.GetOnEventDataValue(strobeStreakData.lastColorType); 166 | } 167 | else 168 | { 169 | value = BeatmapDataStrobeFilterTransform.GetFlashAndFadeToBlackEventDataValue(strobeStreakData.lastColorType); 170 | } 171 | BasicBeatmapEventData beatmapEventData3 = new BasicBeatmapEventData(strobeStreakData.lastSwitchTime, basicBeatmapEventData.basicBeatmapEventType, value, basicBeatmapEventData.floatValue); 172 | beatmapData2.InsertBeatmapEventDataInOrder(beatmapEventData3); 173 | } 174 | else 175 | { 176 | beatmapData2.InsertBeatmapEventDataInOrder(strobeStreakData.originalBasicBeatmapEventData); 177 | } 178 | strobeStreakData.StartPotentialStrobe(basicBeatmapEventData); 179 | } 180 | } 181 | else 182 | { 183 | strobeStreakData.StartPotentialStrobe(basicBeatmapEventData); 184 | } 185 | } 186 | } 187 | } 188 | } 189 | foreach (KeyValuePair keyValuePair in dictionary) 190 | { 191 | if (keyValuePair.Value.isActive) 192 | { 193 | beatmapData2.InsertBeatmapEventDataInOrder(keyValuePair.Value.originalBasicBeatmapEventData); 194 | } 195 | } 196 | foreach (string specialBasicBeatmapEventKeyword in beatmapData.specialBasicBeatmapEventKeywords) 197 | { 198 | beatmapData2.AddSpecialBasicBeatmapEventKeyword(specialBasicBeatmapEventKeyword); 199 | } 200 | return beatmapData2; 201 | */ 202 | // clang-format on 203 | MAKE_PAPER_HOOK_MATCH(BeatmapDataStrobeFilterTransform_CreateTransformedData, 204 | &BeatmapDataStrobeFilterTransform::CreateTransformedData, IReadonlyBeatmapData*, 205 | IReadonlyBeatmapData* beatmapData, 206 | EnvironmentIntensityReductionOptions* environmentIntensityReductionOptions) { 207 | if (!beatmapData || reinterpret_cast(beatmapData)->klass != classof(CustomBeatmapData*)) { 208 | return BeatmapDataStrobeFilterTransform_CreateTransformedData(beatmapData, environmentIntensityReductionOptions); 209 | } 210 | // Won't work since the constructors are base game 211 | // 212 | // auto fixedBeatmap = BeatmapDataStrobeFilterTransform_CreateTransformedData(beatmapData, 213 | // environmentIntensityReductionOptions); 214 | // 215 | // auto customBeatmapData = il2cpp_utils::cast(beatmapData); 216 | // CustomBeatmapData *newBeatmap; 217 | // 218 | //// if (auto customBeatmapData = il2cpp_utils::try_cast(beatmapData)) { 219 | //// for (auto const &c: customBeatmapData.value()->customEventDatas) { 220 | //// beatmapData2->InsertCustomEventDataInOrder(c); 221 | //// } 222 | // newBeatmap = customBeatmapData->GetFilteredCopyOverride( 223 | // [&](BeatmapDataItem *const &i) -> BeatmapDataItem * { 224 | // 225 | // if (i->type == BeatmapDataItem::BeatmapDataItemType::BeatmapEvent) { 226 | // return nullptr; 227 | // } 228 | // 229 | // return i; 230 | // }); 231 | //// } else { 232 | //// newBeatmap = CustomBeatmapData::New_ctor(beatmapData->i_IBeatmapDataBasicInfo()->get_numberOfLines()); 233 | //// 234 | //// } 235 | // 236 | // 237 | // auto const &linkedItems = fixedBeatmap->get_allBeatmapDataItems(); 238 | // for (auto node = linkedItems->get_First(); 239 | // node != nullptr; node = CustomBeatmapData::LinkedListNode_1_get_Next(node)) { 240 | // if (!node->item || node->item->type != BeatmapDataItem::BeatmapDataItemType::BeatmapEvent) { 241 | // continue; 242 | // } 243 | // 244 | // newBeatmap->InsertBeatmapEventDataInOrder(il2cpp_utils::cast(node->item)); 245 | // } 246 | 247 | auto* customBeatmapData = il2cpp_utils::cast(beatmapData); 248 | bool flag = environmentIntensityReductionOptions->compressExpand == 249 | EnvironmentIntensityReductionOptions::CompressExpandReductionType::RemoveWithStrobeFilter; 250 | bool flag2 = environmentIntensityReductionOptions->rotateRings == 251 | EnvironmentIntensityReductionOptions::RotateRingsReductionType::RemoveWithStrobeFilter; 252 | std::unordered_map dictionary( 253 | { { BasicBeatmapEventType::Event0.value__, BeatmapDataStrobeFilterTransform::StrobeStreakData::New_ctor() }, 254 | { BasicBeatmapEventType::Event1.value__, BeatmapDataStrobeFilterTransform::StrobeStreakData::New_ctor() }, 255 | { BasicBeatmapEventType::Event2.value__, BeatmapDataStrobeFilterTransform::StrobeStreakData::New_ctor() }, 256 | { BasicBeatmapEventType::Event3.value__, BeatmapDataStrobeFilterTransform::StrobeStreakData::New_ctor() }, 257 | { BasicBeatmapEventType::Event4.value__, BeatmapDataStrobeFilterTransform::StrobeStreakData::New_ctor() } }); 258 | 259 | auto* newBeatmap = customBeatmapData->BaseCopy(); 260 | 261 | for (auto const& o : customBeatmapData->beatmapObjectDatas) { 262 | if (!o) { 263 | continue; 264 | } 265 | newBeatmap->AddBeatmapObjectDataInOrder(o); 266 | } 267 | 268 | for (auto const& beatmapDataItem : customBeatmapData->beatmapEventDatas) { 269 | if (!beatmapDataItem) { 270 | continue; 271 | } 272 | 273 | BasicBeatmapEventData* basicBeatmapEventData = 274 | il2cpp_utils::try_cast(beatmapDataItem).value_or(nullptr); 275 | LightColorBeatmapEventData* lightColorBeatmapEventData = 276 | il2cpp_utils::try_cast(beatmapDataItem).value_or(nullptr); 277 | 278 | if (lightColorBeatmapEventData) { 279 | lightColorBeatmapEventData->DisableStrobe(); 280 | newBeatmap->InsertBeatmapEventDataInOrder(lightColorBeatmapEventData); 281 | continue; 282 | } 283 | if (!basicBeatmapEventData) { 284 | newBeatmap->InsertBeatmapEventDataInOrder(beatmapDataItem); 285 | continue; 286 | } 287 | if ((!flag || basicBeatmapEventData->basicBeatmapEventType != BasicBeatmapEventType::Event9) && 288 | (!flag2 || basicBeatmapEventData->basicBeatmapEventType != BasicBeatmapEventType::Event8)) { 289 | if (!BeatmapEventTypeExtensions::IsCoreLightIntensityChangeEvent(basicBeatmapEventData->basicBeatmapEventType)) { 290 | newBeatmap->InsertBeatmapEventDataInOrder(basicBeatmapEventData); 291 | continue; 292 | } 293 | 294 | if (BeatmapEventDataLightsExtensions::HasLightFadeEventDataValue(basicBeatmapEventData)) { 295 | newBeatmap->InsertBeatmapEventDataInOrder(basicBeatmapEventData); 296 | continue; 297 | } 298 | 299 | BeatmapDataStrobeFilterTransform::StrobeStreakData* strobeStreakData = 300 | dictionary[basicBeatmapEventData->basicBeatmapEventType.value__]; 301 | CRASH_UNLESS(strobeStreakData); 302 | if (strobeStreakData->isActive) { 303 | if (basicBeatmapEventData->time - strobeStreakData->lastSwitchTime < 0.1F) { 304 | strobeStreakData->AddStrobeData(basicBeatmapEventData); 305 | } else { 306 | if (!Approximately(strobeStreakData->strobeStartTime, strobeStreakData->lastSwitchTime)) { 307 | int onEventDataValue = 308 | BeatmapDataStrobeFilterTransform::GetOnEventDataValue(strobeStreakData->startColorType); 309 | auto* beatmapEventData2 = static_cast(basicBeatmapEventData->GetCopy()); 310 | beatmapEventData2->_time_k__BackingField = strobeStreakData->strobeStartTime; 311 | beatmapEventData2->value = onEventDataValue; 312 | newBeatmap->InsertBeatmapEventDataInOrder(beatmapEventData2); 313 | int value = 0; 314 | if (strobeStreakData->lastIsOn) { 315 | value = BeatmapDataStrobeFilterTransform::GetOnEventDataValue(strobeStreakData->lastColorType); 316 | } else { 317 | value = BeatmapDataStrobeFilterTransform::GetFlashAndFadeToBlackEventDataValue( 318 | strobeStreakData->lastColorType); 319 | } 320 | auto* beatmapEventData3 = static_cast(basicBeatmapEventData->GetCopy()); 321 | beatmapEventData3->_time_k__BackingField = strobeStreakData->lastSwitchTime; 322 | beatmapEventData3->value = value; 323 | newBeatmap->InsertBeatmapEventDataInOrder(beatmapEventData3); 324 | } else { 325 | newBeatmap->InsertBeatmapEventDataInOrder(strobeStreakData->originalBasicBeatmapEventData); 326 | } 327 | strobeStreakData->StartPotentialStrobe(basicBeatmapEventData); 328 | } 329 | } else { 330 | strobeStreakData->StartPotentialStrobe(basicBeatmapEventData); 331 | } 332 | } 333 | } 334 | 335 | for (auto const& keyValuePair : dictionary) { 336 | if (!keyValuePair.second) { 337 | continue; 338 | } 339 | if (keyValuePair.second->isActive) { 340 | newBeatmap->InsertBeatmapEventDataInOrder(keyValuePair.second->originalBasicBeatmapEventData); 341 | } 342 | } 343 | return newBeatmap->i___GlobalNamespace__IReadonlyBeatmapData(); 344 | } 345 | 346 | 347 | 348 | BeatmapCallbacksController* beatmapCallbacksController; 349 | 350 | MAKE_PAPER_HOOK_MATCH(BeatmapCallbacksController_ManualUpdate, &BeatmapCallbacksController::ManualUpdate, void, 351 | BeatmapCallbacksController* self, float songTime) { 352 | if (songTime == self->_prevSongTime) { 353 | return BeatmapCallbacksController_ManualUpdate(self, songTime); 354 | } 355 | 356 | // TRANSPILE HERE 357 | if (self != beatmapCallbacksController) { 358 | CustomEventCallbacks::RegisterCallbacks(self); 359 | beatmapCallbacksController = self; 360 | } 361 | // 362 | 363 | return BeatmapCallbacksController_ManualUpdate(self, songTime); 364 | } 365 | 366 | MAKE_PAPER_HOOK_MATCH(BeatmapCallbacksController_Dispose, &BeatmapCallbacksController::Dispose, void, 367 | BeatmapCallbacksController* self) { 368 | CustomEventCallbacks::firstNode.emplace(nullptr); 369 | return BeatmapCallbacksController_Dispose(self); 370 | } 371 | 372 | static float GetAheadTime(Il2CppObject const* obj) { 373 | 374 | static auto const* CustomNoteKlass = classof(CustomJSONData::CustomNoteData*); 375 | static auto const* CustomObstacleKlass = classof(CustomJSONData::CustomObstacleData*); 376 | 377 | if (obj->klass == CustomNoteKlass) { 378 | return static_cast(obj)->aheadTimeNoodle; 379 | } 380 | 381 | if (obj->klass == CustomObstacleKlass) { 382 | return static_cast(obj)->aheadTimeNoodle; 383 | } 384 | 385 | return 0; 386 | } 387 | 388 | // clang-format off 389 | /* 390 | if (songTime == this._prevSongTime) 391 | { 392 | return; 393 | } 394 | this._songTime = songTime; 395 | this._processingCallbacks = true; 396 | if (songTime > this._prevSongTime) 397 | { 398 | using (Dictionary.Enumerator enumerator = this._callbacksInTimes.GetEnumerator()) 399 | { 400 | while (enumerator.MoveNext()) 401 | { 402 | KeyValuePair keyValuePair = enumerator.Current; 403 | CallbacksInTime value = keyValuePair.Value; 404 | for (LinkedListNode linkedListNode = (value.lastProcessedNode != null) ? value.lastProcessedNode.Next : this._beatmapData.allBeatmapDataItems.First; linkedListNode != null; linkedListNode = linkedListNode.Next) 405 | { 406 | BeatmapDataItem value2 = linkedListNode.Value; 407 | if (value2.time - value.aheadTime > songTime) 408 | { 409 | break; 410 | } 411 | if (value2.type == BeatmapDataItem.BeatmapDataItemType.BeatmapEvent || (value2.type == BeatmapDataItem.BeatmapDataItemType.BeatmapObject && value2.time >= this._startFilterTime)) 412 | { 413 | this._callCallbacksBehavior.CallCallbacks(value, value2); 414 | } 415 | value.lastProcessedNode = linkedListNode; 416 | } 417 | } 418 | goto IL_1B4; 419 | } 420 | } 421 | foreach (KeyValuePair keyValuePair2 in this._callbacksInTimes) 422 | { 423 | CallbacksInTime value3 = keyValuePair2.Value; 424 | LinkedListNode linkedListNode2 = value3.lastProcessedNode; 425 | while (linkedListNode2 != null) 426 | { 427 | BeatmapDataItem value4 = linkedListNode2.Value; 428 | if (value4.time - value3.aheadTime <= songTime) 429 | { 430 | break; 431 | } 432 | if (value4.type == BeatmapDataItem.BeatmapDataItemType.BeatmapEvent) 433 | { 434 | BeatmapEventData beatmapEventData = (BeatmapEventData)value4; 435 | if (beatmapEventData.previousSameTypeEventData != null) 436 | { 437 | this._callCallbacksBehavior.CallCallbacks(value3, beatmapEventData.previousSameTypeEventData); 438 | } 439 | else 440 | { 441 | BeatmapEventData @default = beatmapEventData.GetDefault(beatmapEventData); 442 | if (@default != null) 443 | { 444 | this._callCallbacksBehavior.CallCallbacks(value3, @default); 445 | } 446 | } 447 | } 448 | linkedListNode2 = linkedListNode2.Previous; 449 | value3.lastProcessedNode = linkedListNode2; 450 | } 451 | } 452 | IL_1B4: 453 | this._prevSongTime = songTime; 454 | this._processingCallbacks = false; 455 | Action action = this.didProcessAllCallbacksThisFrameEvent; 456 | if (action == null) 457 | { 458 | return; 459 | } 460 | action(); 461 | */ 462 | // clang-format on 463 | MAKE_PAPER_HOOK_MATCH(BeatmapCallbacksController_ManualUpdateTranspile, &BeatmapCallbacksController::ManualUpdate, void, 464 | BeatmapCallbacksController* self, float songTime) { 465 | // TRANSPILE HERE 466 | if (self != beatmapCallbacksController) { 467 | CustomEventCallbacks::RegisterCallbacks(self); 468 | beatmapCallbacksController = self; 469 | } 470 | // 471 | 472 | if (songTime == self->_prevSongTime) { 473 | return; 474 | } 475 | 476 | self->_songTime = songTime; 477 | self->_processingCallbacks = true; 478 | if (songTime > self->_prevSongTime) { 479 | auto enumerator = self->_callbacksInTimes->GetEnumerator(); 480 | 481 | while (enumerator.MoveNext()) { 482 | auto keyValuePair = enumerator.get_Current(); 483 | auto* value = keyValuePair.get_Value(); 484 | 485 | using NodePtr = System::Collections::Generic::LinkedListNode_1*; 486 | 487 | auto* firstNode = CustomEventCallbacks::firstNode ? (NodePtr)CustomEventCallbacks::firstNode : nullptr; 488 | 489 | for (auto* linkedListNode = 490 | (value->lastProcessedNode != nullptr) 491 | ? CustomJSONData::LinkedListNode_1_get_Next(value->lastProcessedNode) 492 | : (firstNode ? firstNode : self->_beatmapData->get_allBeatmapDataItems()->get_First()); 493 | linkedListNode != nullptr; linkedListNode = CustomJSONData::LinkedListNode_1_get_Next(linkedListNode)) { 494 | auto* value2 = linkedListNode->get_Value(); 495 | // transpile here NE 496 | if (value2->time - value->aheadTime - GetAheadTime(value2) > songTime) { 497 | break; 498 | } 499 | // 500 | if (value2->type == BeatmapDataItem::BeatmapDataItemType::BeatmapEvent || 501 | /// TRANSPILE HERE 502 | value2->type.value__ == 2 || 503 | /// TRANSPILE HERE 504 | (value2->type == BeatmapDataItem::BeatmapDataItemType::BeatmapObject && 505 | value2->time >= self->_startFilterTime)) { 506 | value->CallCallbacks(value2); 507 | } 508 | value->lastProcessedNode = linkedListNode; 509 | } 510 | } 511 | enumerator.Dispose(); 512 | } else { 513 | auto callbacksInTimesEnumerator = self->_callbacksInTimes->GetEnumerator(); 514 | 515 | while (callbacksInTimesEnumerator.MoveNext()) { 516 | auto keyValuePair2 = callbacksInTimesEnumerator.get_Current(); 517 | auto* value3 = keyValuePair2.get_Value(); 518 | auto* linkedListNode2 = value3->lastProcessedNode; 519 | while (linkedListNode2 != nullptr) { 520 | auto* value4 = linkedListNode2->get_Value(); 521 | if (value4->time - value3->aheadTime <= songTime) { 522 | break; 523 | } 524 | 525 | /// TRANSPILE HERE 526 | /// STOPS INFINITE LOOP BY RUNNING THIS REGARDLESS IF THE CONDITION ABOVE IS MET 527 | /// WHILE THIS SHOULD BE FIXED IN PINKCORE, WE KEEP IT AS A SAFEGUARD 528 | if (value4->type != BeatmapDataItem::BeatmapDataItemType::BeatmapEvent) { 529 | break; 530 | } 531 | /// 532 | 533 | auto* beatmapEventData = static_cast(value4); 534 | if (beatmapEventData->previousSameTypeEventData != nullptr) { 535 | value3->CallCallbacks(beatmapEventData->previousSameTypeEventData); 536 | } else { 537 | auto* def = beatmapEventData->GetDefault(beatmapEventData); 538 | if (def != nullptr) { 539 | value3->CallCallbacks(def); 540 | } 541 | } 542 | 543 | value3->lastProcessedNode = linkedListNode2 = linkedListNode2->get_Previous(); 544 | } 545 | } 546 | callbacksInTimesEnumerator.Dispose(); 547 | } 548 | 549 | self->_prevSongTime = songTime; 550 | self->_processingCallbacks = false; 551 | 552 | if (self->didProcessAllCallbacksThisFrameEvent) { 553 | self->didProcessAllCallbacksThisFrameEvent->Invoke(); 554 | } 555 | } 556 | 557 | // clang-format off 558 | /* 559 | beatmapData.InsertBeatmapEventData(new BasicBeatmapEventData(0f, BasicBeatmapEventType.Event0, 1, 1f)); 560 | beatmapData.InsertBeatmapEventData(new BasicBeatmapEventData(0f, BasicBeatmapEventType.Event4, 1, 1f)); 561 | */ 562 | // clang-format on 563 | MAKE_PAPER_HOOK_MATCH(InsertDefaultEvents, &DefaultEnvironmentEventsFactory::InsertDefaultEvents, void, 564 | BeatmapData* beatmapData) { 565 | // TRANSPILE HERE 566 | beatmapData->InsertBeatmapEventData(CustomBeatmapEventData::New_ctor(0.0F, BasicBeatmapEventType::Event0, 1, 1.0F)); 567 | beatmapData->InsertBeatmapEventData(CustomBeatmapEventData::New_ctor(0.0F, BasicBeatmapEventType::Event4, 1, 1.0F)); 568 | // END TRANSPILE HERE 569 | } 570 | 571 | void CustomJSONData::InstallHooks() { 572 | 573 | il2cpp_functions::Class_Init(classof(BeatmapData*)); 574 | custom_types::Register::AutoRegister(); 575 | 576 | // Stupid workaround because stupid NE 577 | INSTALL_HOOK_ORIG(CJDLogger::Logger, BeatmapCallbacksController_ManualUpdateTranspile); 578 | INSTALL_HOOK_ORIG(CJDLogger::Logger, BeatmapDataStrobeFilterTransform_CreateTransformedData); 579 | INSTALL_HOOK_ORIG(CJDLogger::Logger, InsertDefaultEvents); 580 | INSTALL_HOOK(CJDLogger::Logger, BeatmapCallbacksController_Dispose); 581 | 582 | v2::InstallHooks(); 583 | v3::InstallHooks(); 584 | InstallBeatmapHooks(); 585 | } 586 | -------------------------------------------------------------------------------- /src/hooks/V2Hooks.cpp: -------------------------------------------------------------------------------- 1 | #include "CustomBeatmapSaveDatav2.h" 2 | 3 | #include "CustomJSONDataHooks.h" 4 | 5 | #include "HookUtils.hpp" 6 | 7 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/zzzz__EventData_def.hpp" 8 | #include "BeatmapSaveDataVersion2_6_0AndEarlier/BeatmapSaveDataItem.hpp" 9 | #include "BeatmapDataLoaderVersion2_6_0AndEarlier/BeatmapDataLoader.hpp" 10 | 11 | #include "BeatmapDataLoaderVersion4/BeatmapDataLoader.hpp" 12 | #include "BeatmapSaveDataVersion4/LightshowSaveData.hpp" 13 | 14 | #include "GlobalNamespace/BpmTimeProcessor.hpp" 15 | #include "GlobalNamespace/EnvironmentKeywords.hpp" 16 | #include "GlobalNamespace/IEnvironmentInfo.hpp" 17 | #include "GlobalNamespace/IEnvironmentLightGroups.hpp" 18 | #include "GlobalNamespace/EnvironmentLightGroups.hpp" 19 | #include "GlobalNamespace/DefaultEnvironmentEvents.hpp" 20 | #include "GlobalNamespace/BeatmapObjectData.hpp" 21 | #include "GlobalNamespace/NoteData.hpp" 22 | #include "GlobalNamespace/LightColorBeatmapEventData.hpp" 23 | #include "GlobalNamespace/CallbacksInTime.hpp" 24 | #include "GlobalNamespace/IReadonlyBeatmapData.hpp" 25 | #include "GlobalNamespace/RotationTimeProcessor.hpp" 26 | 27 | #include "UnityEngine/JsonUtility.hpp" 28 | 29 | #include "beatsaber-hook/shared/utils/typedefs-list.hpp" 30 | #include "beatsaber-hook/shared/utils/hooking.hpp" 31 | #include "beatsaber-hook/shared/config/rapidjson-utils.hpp" 32 | // for rapidjson error parsing 33 | #include "beatsaber-hook/shared/rapidjson/include/rapidjson/error/en.h" 34 | 35 | #include "cpp-semver/shared/cpp-semver.hpp" 36 | 37 | #include "CustomBeatmapSaveDatav2.h" 38 | #include "CustomBeatmapData.h" 39 | #include "misc/BeatmapFieldUtils.hpp" 40 | #include "misc/BeatmapDataLoaderUtils.hpp" 41 | #include "CustomJSONDataHooks.h" 42 | #include "CJDLogger.h" 43 | #include "VList.h" 44 | #include "paper2_scotland2/shared/Profiler.hpp" 45 | 46 | using namespace System; 47 | using namespace System::Collections::Generic; 48 | using namespace GlobalNamespace; 49 | using namespace CustomJSONData; 50 | using namespace BeatmapSaveDataVersion3; 51 | using namespace BeatmapSaveDataVersion4; 52 | 53 | std::optional ParseBeatmapSaveDataJson_v2(StringW stringData) { 54 | CJDLogger::Logger.fmtLog("Parsing json"); 55 | auto startTime = std::chrono::high_resolution_clock::now(); 56 | 57 | if (!stringData) { 58 | CJDLogger::Logger.fmtLog("No string data"); 59 | return std::nullopt; 60 | } 61 | 62 | try { 63 | auto contents = std::string(stringData); 64 | auto sharedDoc = parseDocument(contents); 65 | if (!sharedDoc) return nullptr; 66 | 67 | auto version = GetVersionFromPath(contents); 68 | 69 | v2::CustomBeatmapSaveData* saveData = v2::CustomBeatmapSaveData::Deserialize(*sharedDoc); 70 | 71 | CJDLogger::Logger.fmtLog("Parsing 2.0.0 beatmap"); 72 | 73 | // cachedSaveData = saveData; 74 | 75 | CJDLogger::Logger.fmtLog("Finished reading beatmap data {}", fmt::ptr(saveData)); 76 | auto stopTime = std::chrono::high_resolution_clock::now(); 77 | CJDLogger::Logger.fmtLog( 78 | "This took {}ms", 79 | static_cast(std::chrono::duration_cast(stopTime - startTime).count())); 80 | 81 | return saveData; 82 | } catch (std::exception const& e) { 83 | CJDLogger::Logger.fmtLog("There was an error loading the beatmap through CJD. Cause of error: {}", 84 | e.what()); 85 | return std::nullopt; 86 | } 87 | } 88 | 89 | // clang-format off 90 | /* 91 | if (string.IsNullOrEmpty(beatmapJson)) 92 | { 93 | return null; 94 | } 95 | BeatmapSaveDataVersion2_6_0AndEarlier.BeatmapSaveData beatmapSaveData = JsonUtility.FromJson(beatmapJson); 96 | if (beatmapSaveData == null) 97 | { 98 | return null; 99 | } 100 | LightshowSaveData defaultLightshowSaveData = (!string.IsNullOrEmpty(defaultLightshowSaveDataJson)) ? JsonUtility.FromJson(defaultLightshowSaveDataJson) : null; 101 | EnvironmentKeywords environmentKeywords; 102 | IEnvironmentLightGroups environmentLightGroups; 103 | if (environmentInfo != null) 104 | { 105 | environmentKeywords = new EnvironmentKeywords(environmentInfo.environmentKeywords); 106 | environmentLightGroups = environmentInfo.environmentLightGroups; 107 | } 108 | else 109 | { 110 | environmentKeywords = new EnvironmentKeywords(new string[0]); 111 | environmentLightGroups = new EnvironmentLightGroups(new List()); 112 | } 113 | return BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.GetBeatmapDataFromSaveData(beatmapSaveData, defaultLightshowSaveData, beatmapDifficulty, startBpm, loadingForDesignatedEnvironment, environmentKeywords, environmentLightGroups, playerSpecificSettings); 114 | */ 115 | // clang-format on 116 | MAKE_PAPER_HOOK_MATCH(BeatmapDataLoader_GetBeatmapDataFromSaveDataJson_v2, 117 | &BeatmapDataLoaderVersion2_6_0AndEarlier::BeatmapDataLoader::GetBeatmapDataFromSaveDataJson, 118 | GlobalNamespace::BeatmapData*, ::StringW beatmapJson, ::StringW defaultLightshowSaveDataJson, 119 | ::GlobalNamespace::BeatmapDifficulty beatmapDifficulty, float_t startBpm, 120 | bool loadingForDesignatedEnvironment, ::GlobalNamespace::IEnvironmentInfo* environmentInfo, 121 | ::GlobalNamespace::BeatmapLevelDataVersion beatmapLevelDataVersion, 122 | ::GlobalNamespace::PlayerSpecificSettings* playerSpecificSettings, 123 | ::GlobalNamespace::IBeatmapLightEventConverter* lightEventConverter) { 124 | CJDLogger::Logger.fmtLog("Loading Beatmap Data"); 125 | 126 | auto saveData = ParseBeatmapSaveDataJson_v2(beatmapJson); 127 | if (!saveData) { 128 | return BeatmapDataLoader_GetBeatmapDataFromSaveDataJson_v2( 129 | beatmapJson, defaultLightshowSaveDataJson, beatmapDifficulty, startBpm, loadingForDesignatedEnvironment, 130 | environmentInfo, beatmapLevelDataVersion, playerSpecificSettings, lightEventConverter); 131 | } 132 | auto saveDataPtr = saveData.value(); 133 | 134 | LightshowSaveData* defaultLightshowSaveData = 135 | ((!String::IsNullOrEmpty(defaultLightshowSaveDataJson)) 136 | ? UnityEngine::JsonUtility::FromJson(defaultLightshowSaveDataJson) 137 | : nullptr); 138 | 139 | EnvironmentKeywords* environmentKeywords; 140 | IEnvironmentLightGroups* environmentLightGroups; 141 | 142 | if (environmentInfo != nullptr) { 143 | environmentKeywords = EnvironmentKeywords::New_ctor(environmentInfo->environmentKeywords); 144 | environmentLightGroups = environmentInfo->environmentLightGroups; 145 | } else { 146 | environmentKeywords = 147 | EnvironmentKeywords::New_ctor(ListW({})->i___System__Collections__Generic__IReadOnlyList_1_T_()); 148 | environmentLightGroups = EnvironmentLightGroups::New_ctor(ListW>::New().getPtr()) 149 | ->i___GlobalNamespace__IEnvironmentLightGroups(); 150 | } 151 | 152 | // std::string contents(beatmapJson); 153 | // auto version = GetVersionFromPath(contents); 154 | 155 | auto beatmapData = BeatmapDataLoaderVersion2_6_0AndEarlier::BeatmapDataLoader::GetBeatmapDataFromSaveData( 156 | saveDataPtr, defaultLightshowSaveData, beatmapDifficulty, startBpm, loadingForDesignatedEnvironment, 157 | environmentKeywords, environmentLightGroups, playerSpecificSettings, lightEventConverter); 158 | return beatmapData; 159 | } 160 | 161 | // clang-format off 162 | /* 163 | List list = new List(this.events.Count); 164 | foreach (EventData eventData in this.events) 165 | { 166 | if (eventData.type == BeatmapEventType.Event10) 167 | { 168 | eventData = new EventData(eventData.time, BeatmapEventType.BpmChange, eventData.value, eventData.floatValue); 169 | } 170 | if (eventData.type == BeatmapEventType.BpmChange) 171 | { 172 | if (eventData.value != 0) 173 | { 174 | eventData = new EventData(eventData.time, eventData.type, eventData.value, (float)eventData.value); 175 | } 176 | } 177 | else 178 | { 179 | eventData = new EventData(eventData.time, eventData.type, eventData.value, 1f); 180 | } 181 | list.Add(eventData); 182 | } 183 | this._events = list; 184 | */ 185 | // clang-format on 186 | 187 | MAKE_PAPER_HOOK_MATCH(BeatmapSaveData_ConvertBeatmapSaveDataPreV2_5_0Inline, 188 | &BeatmapSaveDataVersion2_6_0AndEarlier::BeatmapSaveData::ConvertBeatmapSaveDataPreV2_5_0Inline, 189 | void, BeatmapSaveDataVersion2_6_0AndEarlier::BeatmapSaveData* self) { 190 | CJDLogger::Logger.info("Using CJD 2.5.0 fixup"); 191 | 192 | std::vector events; 193 | events.reserve(self->events->_size); 194 | 195 | for (auto const& originalEventData : VList(self->events)) { 196 | auto newEventData = v2::CustomBeatmapSaveData_EventData::New_ctor( 197 | originalEventData->time, originalEventData->type, originalEventData->value, originalEventData->floatValue); 198 | 199 | auto const customEventData = il2cpp_utils::try_cast(originalEventData); 200 | auto customData = customEventData.has_value() ? customEventData.value()->customData : std::nullopt; 201 | 202 | newEventData->customData = customData; 203 | // Legacy BPM conversion here is deliberately ommited. 204 | // There are no maps using these events, but plenty missversioned 205 | if (newEventData->_type == BeatmapSaveDataCommon::BeatmapEventType::BpmChange) { 206 | if (newEventData->_value != 0) { 207 | newEventData->_floatValue = (float)newEventData->_value; 208 | } 209 | } else { 210 | newEventData->_floatValue = 1.0f; 211 | } 212 | 213 | events.emplace_back(newEventData); 214 | } 215 | auto list = ListW::New( 216 | std::span(events)); 217 | 218 | self->_events = list; 219 | } 220 | 221 | // clang-format off 222 | /* 223 | beatmapSaveData.version != "2.6.0"; 224 | if (!string.IsNullOrEmpty(beatmapSaveData.version)) 225 | { 226 | Version version = new Version(beatmapSaveData.version); 227 | Version value = new Version("2.5.0"); 228 | if (version.CompareTo(value) < 0) 229 | { 230 | BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ConvertBeatmapSaveDataPreV2_5_0Inline(beatmapSaveData); 231 | } 232 | } 233 | else 234 | { 235 | BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ConvertBeatmapSaveDataPreV2_5_0Inline(beatmapSaveData); 236 | } 237 | if (!BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.BeatmapSaveDataAreSorted(beatmapSaveData.notes)) 238 | { 239 | beatmapSaveData.notes.Sort(); 240 | } 241 | if (!BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.BeatmapSaveDataAreSorted(beatmapSaveData.obstacles)) 242 | { 243 | beatmapSaveData.obstacles.Sort(); 244 | } 245 | if (!BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.BeatmapSaveDataAreSorted(beatmapSaveData.sliders)) 246 | { 247 | beatmapSaveData.sliders.Sort(); 248 | } 249 | if (!BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.BeatmapSaveDataAreSorted(beatmapSaveData.waypoints)) 250 | { 251 | beatmapSaveData.waypoints.Sort(); 252 | } 253 | if (!BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.BeatmapSaveDataAreSorted(beatmapSaveData.events)) 254 | { 255 | beatmapSaveData.events.Sort(); 256 | } 257 | bool flag = loadingForDesignatedEnvironment || defaultLightshowSaveData == null; 258 | bool flag2 = playerSpecificSettings == null || playerSpecificSettings.GetEnvironmentEffectsFilterPreset(beatmapDifficulty) != EnvironmentEffectsFilterPreset.NoEffects; 259 | bool flag3 = flag && flag2; 260 | BeatmapData beatmapData = new BeatmapData(4); 261 | BpmTimeProcessor bpmTimeProcessor = new BpmTimeProcessor(startBpm, beatmapSaveData.events); 262 | BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ColorNoteConverter colorNoteConverter = new BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ColorNoteConverter(bpmTimeProcessor); 263 | BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ObstacleConverter obstacleConverter = new BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.ObstacleConverter(bpmTimeProcessor); 264 | BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.WaypointConverter waypointConverter = new BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.WaypointConverter(bpmTimeProcessor); 265 | BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.SliderConverter sliderConverter = new BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.SliderConverter(bpmTimeProcessor); 266 | foreach (ValueTuple valueTuple in new MultipleSortedBeatmapObjectsListsEnumerator(new ValueTuple, int>[] 267 | { 268 | new ValueTuple, int>(beatmapSaveData.notes, 0), 269 | new ValueTuple, int>(beatmapSaveData.obstacles, 1), 270 | new ValueTuple, int>(beatmapSaveData.waypoints, 2), 271 | new ValueTuple, int>(beatmapSaveData.sliders, 3) 272 | })) 273 | { 274 | BeatmapObjectData beatmapObjectData; 275 | switch (valueTuple.Item2) 276 | { 277 | case 0: 278 | beatmapObjectData = colorNoteConverter.Convert((BeatmapSaveDataVersion2_6_0AndEarlier.NoteData)valueTuple.Item1); 279 | break; 280 | case 1: 281 | beatmapObjectData = obstacleConverter.Convert((BeatmapSaveDataVersion2_6_0AndEarlier.ObstacleData)valueTuple.Item1); 282 | break; 283 | case 2: 284 | beatmapObjectData = waypointConverter.Convert((BeatmapSaveDataVersion2_6_0AndEarlier.WaypointData)valueTuple.Item1); 285 | break; 286 | case 3: 287 | beatmapObjectData = sliderConverter.Convert((BeatmapSaveDataVersion2_6_0AndEarlier.SliderData)valueTuple.Item1); 288 | break; 289 | default: 290 | beatmapObjectData = null; 291 | break; 292 | } 293 | BeatmapObjectData beatmapObjectData2 = beatmapObjectData; 294 | if (beatmapObjectData2 != null) 295 | { 296 | beatmapData.AddBeatmapObjectData(beatmapObjectData2); 297 | } 298 | } 299 | bpmTimeProcessor.Reset(); 300 | BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.SpecialEventsFilter specialEventsFilter = new BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.SpecialEventsFilter(beatmapSaveData.specialEventsKeywordFilters, environmentKeywords); 301 | BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.BasicEventConverter basicEventConverter = new BeatmapDataLoaderVersion2_6_0AndEarlier.BeatmapDataLoader.BasicEventConverter(bpmTimeProcessor, specialEventsFilter, flag3); 302 | foreach (EventData e in beatmapSaveData.events) 303 | { 304 | BeatmapEventData beatmapEventData = basicEventConverter.Convert(e); 305 | if (beatmapEventData != null) 306 | { 307 | beatmapData.InsertBeatmapEventData(beatmapEventData); 308 | } 309 | } 310 | if (!flag3 && defaultLightshowSaveData != null) 311 | { 312 | BeatmapDataLoaderVersion4.BeatmapDataLoader.LoadLightshow(beatmapData, defaultLightshowSaveData, bpmTimeProcessor, environmentKeywords, environmentLightGroups); 313 | } 314 | else 315 | { 316 | DefaultEnvironmentEventsFactory.InsertDefaultEvents(beatmapData); 317 | } 318 | beatmapData.ProcessRemainingData(); 319 | beatmapData.ProcessAndSortBeatmapData(); 320 | return beatmapData; 321 | */ 322 | // clang-format on 323 | MAKE_PAPER_HOOK_MATCH(BeatmapDataLoader_GetBeatmapDataFromSaveData_v2, 324 | &BeatmapDataLoaderVersion2_6_0AndEarlier::BeatmapDataLoader::GetBeatmapDataFromSaveData, 325 | GlobalNamespace::BeatmapData*, 326 | ::BeatmapSaveDataVersion2_6_0AndEarlier::BeatmapSaveData* beatmapSaveData, 327 | ::BeatmapSaveDataVersion4::LightshowSaveData* defaultLightshowSaveData, 328 | ::GlobalNamespace::BeatmapDifficulty beatmapDifficulty, float_t startBpm, 329 | bool loadingForDesignatedEnvironment, ::GlobalNamespace::EnvironmentKeywords* environmentKeywords, 330 | ::GlobalNamespace::IEnvironmentLightGroups* environmentLightGroups, 331 | ::GlobalNamespace::PlayerSpecificSettings* playerSpecificSettings, 332 | ::GlobalNamespace::IBeatmapLightEventConverter* lightEventConverter) { 333 | 334 | CJDLogger::Logger.info("Converting v2 save data to beatmap data"); 335 | CJDLogger::Logger.Backtrace(100); 336 | 337 | if (!beatmapSaveData) { 338 | return BeatmapDataLoader_GetBeatmapDataFromSaveData_v2( 339 | beatmapSaveData, defaultLightshowSaveData, beatmapDifficulty, startBpm, loadingForDesignatedEnvironment, 340 | environmentKeywords, environmentLightGroups, playerSpecificSettings, lightEventConverter); 341 | } 342 | 343 | Paper::Profiler profile; 344 | profile.startTimer(); 345 | 346 | // i hate this 347 | auto fancyCast = [](auto&& list) { 348 | return reinterpret_cast<::System::Collections::Generic::IReadOnlyList_1< 349 | ::BeatmapSaveDataVersion2_6_0AndEarlier::BeatmapSaveDataItem*>*>(list); 350 | }; 351 | auto fancyCast2 = [](auto&& list) { 352 | return reinterpret_cast< 353 | ::System::Collections::Generic::IReadOnlyList_1<::BeatmapSaveDataVersion2_6_0AndEarlier::EventData*>*>( 354 | list->i___System__Collections__Generic__IReadOnlyList_1_T_()); 355 | }; 356 | 357 | if (String::IsNullOrEmpty(beatmapSaveData->version) || semver::lt(beatmapSaveData->version, "2.5.0")) { 358 | CJDLogger::Logger.info("Fixing pre 2.5.0 map: {}", beatmapSaveData->version); 359 | BeatmapDataLoaderVersion2_6_0AndEarlier::BeatmapDataLoader::ConvertBeatmapSaveDataPreV2_5_0Inline(beatmapSaveData); 360 | } 361 | 362 | bool flag = loadingForDesignatedEnvironment || defaultLightshowSaveData == nullptr; 363 | bool flag2 = playerSpecificSettings == nullptr || playerSpecificSettings->GetEnvironmentEffectsFilterPreset( 364 | beatmapDifficulty) != EnvironmentEffectsFilterPreset::NoEffects; 365 | CJDLogger::Logger.info("flag1 {} || {} == nullptr", loadingForDesignatedEnvironment, 366 | fmt::ptr(defaultLightshowSaveData)); 367 | 368 | CJDLogger::Logger.info( 369 | "flag2 {} == nullptr || {} != EnvironmentEffectsFilterPreset::NoEffects", fmt::ptr(playerSpecificSettings), 370 | playerSpecificSettings && playerSpecificSettings->GetEnvironmentEffectsFilterPreset(beatmapDifficulty).value__); 371 | 372 | bool canUseEnvironmentEventsAndShouldLoadDynamicEvents = flag && flag2; 373 | auto beatmapData = CustomBeatmapData::New_ctor(4); 374 | 375 | if (auto cBeatmapSaveData = il2cpp_utils::try_cast(beatmapSaveData)) { 376 | beatmapData->customData = CustomJSONData::JSONWrapperOrNull(cBeatmapSaveData.value()->customData); 377 | beatmapData->levelCustomData = CustomJSONData::JSONWrapperOrNull(cBeatmapSaveData.value()->levelCustomData); 378 | beatmapData->beatmapCustomData = CustomJSONData::JSONWrapperOrNull(cBeatmapSaveData.value()->beatmapCustomData); 379 | beatmapData->v2orEarlier = true; 380 | } 381 | 382 | // auto bpmTimeProcessor = GlobalNamespace::BpmTimeProcessor::New_ctor(startBpm, 383 | // fancyCast2(beatmapSaveData->events)); 384 | 385 | ListW<::BeatmapSaveDataVersion2_6_0AndEarlier::EventData*> bpmEvents = beatmapSaveData->events; 386 | sortInPlace(std::span(bpmEvents)); 387 | 388 | CustomJSONData::BpmTimeProcessor bpmTimeProcessor(startBpm, bpmEvents); 389 | auto rotationTimeProcessor = 390 | RotationTimeProcessor::New_ctor(beatmapSaveData->events->i___System__Collections__Generic__IReadOnlyList_1_T_()); 391 | CJDLogger::Logger.info("BPM Events {}", bpmTimeProcessor.bpmChangeDataList.size()); 392 | CJDLogger::Logger.info("Events total {}", beatmapSaveData->events->_size); 393 | 394 | auto bpmTimeProcessor2 = GlobalNamespace::BpmTimeProcessor::New_ctor(startBpm, fancyCast2(bpmEvents)); 395 | 396 | auto const BeatToTime = [&bpmTimeProcessor, &bpmTimeProcessor2](float beat) constexpr { 397 | auto time = bpmTimeProcessor.ConvertBeatToTime(beat); 398 | return time; 399 | }; 400 | 401 | auto const ResetBPM = [&bpmTimeProcessor, bpmTimeProcessor2]() constexpr { 402 | bpmTimeProcessor.Reset(); 403 | bpmTimeProcessor2->Reset(); 404 | }; 405 | 406 | ResetBPM(); 407 | { 408 | CppConverter 409 | objectConverter; 410 | objectConverter.AddConverter( 411 | [&BeatToTime, 412 | &rotationTimeProcessor](v2::CustomBeatmapSaveData_NoteData* n) -> GlobalNamespace::BeatmapObjectData* { 413 | switch (n->type) { 414 | case BeatmapSaveDataVersion2_6_0AndEarlier::NoteType::NoteA: 415 | case BeatmapSaveDataVersion2_6_0AndEarlier::NoteType::NoteB: 416 | return CreateCustomBasicNoteData( 417 | BeatToTime(n->time), n->time, rotationTimeProcessor->ConvertBeatToRotation(n->time), n->lineIndex, 418 | ConvertNoteLineLayer(n->lineLayer), 419 | (n->type == BeatmapSaveDataVersion2_6_0AndEarlier::NoteType::NoteA) ? ColorType::ColorA 420 | : ColorType::ColorB, 421 | ConvertNoteCutDirection(n->cutDirection), n->customData); 422 | case BeatmapSaveDataVersion2_6_0AndEarlier::NoteType::Bomb: 423 | return CreateCustomBombNoteData(BeatToTime(n->time), n->time, 424 | rotationTimeProcessor->ConvertBeatToRotation(n->time), n->lineIndex, 425 | ConvertNoteLineLayer(n->lineLayer), n->customData); 426 | default: 427 | return nullptr; 428 | } 429 | 430 | return nullptr; 431 | }); 432 | 433 | objectConverter.AddConverter( 434 | [&BeatToTime, &rotationTimeProcessor](v2::CustomBeatmapSaveData_ObstacleData* o) constexpr { 435 | float num = BeatToTime(o->_time); 436 | float num2 = BeatToTime(o->_time + o->_duration); 437 | auto* obstacle = 438 | CustomObstacleData::New_ctor(num, o->_time, o->_time + o->_duration, rotationTimeProcessor->ConvertBeatToRotation(o->time), 439 | o->lineIndex, ConvertNoteLineLayer(GetLayerForObstacleType(o->type)), 440 | num2 - num, o->width, GetHeightForObstacleType(o->type)); 441 | 442 | obstacle->customData = CustomJSONData::JSONWrapperOrNull(o->customData); 443 | 444 | return obstacle; 445 | }); 446 | 447 | objectConverter.AddConverter( 448 | [&BeatToTime, &rotationTimeProcessor](v2::CustomBeatmapSaveData_SliderData* data) { 449 | return CustomSliderData_CreateCustomSliderData( 450 | ConvertColorType(data->colorType), BeatToTime(data->time), data->time, 451 | rotationTimeProcessor->ConvertBeatToRotation(data->time), data->headLineIndex, 452 | ConvertNoteLineLayer(data->headLineLayer), ConvertNoteLineLayer(data->headLineLayer), 453 | data->headControlPointLengthMultiplier, ConvertNoteCutDirection(data->headCutDirection), 454 | BeatToTime(data->tailTime), rotationTimeProcessor->ConvertBeatToRotation(data->tailTime), 455 | data->tailLineIndex, ConvertNoteLineLayer(data->tailLineLayer), ConvertNoteLineLayer(data->tailLineLayer), 456 | data->tailControlPointLengthMultiplier, ConvertNoteCutDirection(data->tailCutDirection), 457 | ConvertSliderMidAnchorMode(data->sliderMidAnchorMode), data->customData); 458 | }); 459 | 460 | objectConverter.AddConverter( 461 | [&BeatToTime, &rotationTimeProcessor](BeatmapSaveDataVersion2_6_0AndEarlier::WaypointData* data) constexpr { 462 | return CustomJSONData::NewFast( 463 | BeatToTime(data->time), data->time, rotationTimeProcessor->ConvertBeatToRotation(data->time), 464 | data->lineIndex, ConvertNoteLineLayer(data->lineLayer), ConvertOffsetDirection(data->offsetDirection)); 465 | }); 466 | 467 | std::vector beatmapDataObjectItems; 468 | beatmapDataObjectItems.reserve(beatmapSaveData->notes->_size + beatmapSaveData->obstacles->_size + 469 | +beatmapSaveData->waypoints->_size + beatmapSaveData->sliders->_size); 470 | 471 | CJDLogger::Logger.fmtLog("Color notes {} {}", fmt::ptr(beatmapSaveData->notes), 472 | beatmapSaveData->notes->_size); 473 | addAllToVector(beatmapDataObjectItems, beatmapSaveData->notes); 474 | CJDLogger::Logger.fmtLog("Obstacles {}", fmt::ptr(beatmapSaveData->obstacles)); 475 | addAllToVector(beatmapDataObjectItems, beatmapSaveData->obstacles); 476 | CJDLogger::Logger.fmtLog("Sliders {}", fmt::ptr(beatmapSaveData->sliders)); 477 | addAllToVector(beatmapDataObjectItems, beatmapSaveData->sliders); 478 | CJDLogger::Logger.fmtLog("Waypoints {}", fmt::ptr(beatmapSaveData->waypoints)); 479 | addAllToVector(beatmapDataObjectItems, beatmapSaveData->waypoints); 480 | 481 | cleanAndSort(beatmapDataObjectItems); 482 | for (auto const& o : beatmapDataObjectItems) { 483 | auto* beatmapObjectData = objectConverter.ProcessItem(o); 484 | 485 | if (beatmapObjectData != nullptr) { 486 | beatmapData->AddBeatmapObjectDataOverride(beatmapObjectData); 487 | } 488 | } 489 | } 490 | 491 | ResetBPM(); 492 | { 493 | auto specialEventsFilter = 494 | BeatmapDataLoaderVersion2_6_0AndEarlier::BeatmapDataLoader::SpecialEventsFilter::New_ctor( 495 | beatmapSaveData->specialEventsKeywordFilters, environmentKeywords); 496 | 497 | auto basicEventConverter = 498 | [&BeatToTime, &specialEventsFilter, &canUseEnvironmentEventsAndShouldLoadDynamicEvents, &rotationTimeProcessor]( 499 | v2::CustomBeatmapSaveData_EventData* e) constexpr -> GlobalNamespace::BeatmapEventData* { 500 | if (!specialEventsFilter->IsEventValid(e->type)) { 501 | return nullptr; 502 | } 503 | if (e->type == BeatmapSaveDataCommon::BeatmapEventType::BpmChange) { 504 | return nullptr; 505 | } 506 | if (e->type == BeatmapSaveDataCommon::BeatmapEventType::Event5 && 507 | canUseEnvironmentEventsAndShouldLoadDynamicEvents) { 508 | return ColorBoostBeatmapEventData::New_ctor(BeatToTime(e->time), e->value == 1); 509 | } 510 | if (!canUseEnvironmentEventsAndShouldLoadDynamicEvents) { 511 | return nullptr; 512 | } 513 | auto event = CustomBeatmapEventData::New_ctor(BeatToTime(e->time), 514 | ConvertBasicBeatmapEventType(e->type), e->value, e->floatValue); 515 | 516 | event->customData = CustomJSONData::JSONWrapperOrNull(e->customData); 517 | 518 | return event; 519 | }; 520 | 521 | VList events = beatmapSaveData->events; 522 | std::stable_sort(events.begin(), events.end(), TimeCompare); 523 | 524 | for (auto const& e : events) { 525 | auto beatmapEventData = basicEventConverter(il2cpp_utils::cast(e)); 526 | if (beatmapEventData != nullptr) { 527 | beatmapData->InsertBeatmapEventDataOverride(beatmapEventData); 528 | } 529 | } 530 | } 531 | 532 | ResetBPM(); 533 | if (auto customBeatmapSaveData = il2cpp_utils::try_cast(beatmapSaveData)) { 534 | if (customBeatmapSaveData.value()->customEventsData) { 535 | std::stable_sort(customBeatmapSaveData.value()->customEventsData->begin(), 536 | customBeatmapSaveData.value()->customEventsData->end(), 537 | [](auto const& a, auto const& b) constexpr { return a.time < b.time; }); 538 | 539 | for (auto const& customEventSaveData : *customBeatmapSaveData.value()->customEventsData) { 540 | beatmapData->InsertCustomEventData( 541 | CustomEventData::New(bpmTimeProcessor.ConvertBeatToTime(customEventSaveData.time), customEventSaveData.type, 542 | customEventSaveData.typeHash, customEventSaveData.data)); 543 | } 544 | 545 | CJDLogger::Logger.fmtLog("Added {} custom events", 546 | customBeatmapSaveData.value()->customEventsData->size()); 547 | } 548 | } 549 | 550 | ResetBPM(); 551 | { 552 | CJDLogger::Logger.info("Lightshow time {} || {} != nullptr", canUseEnvironmentEventsAndShouldLoadDynamicEvents, 553 | fmt::ptr(defaultLightshowSaveData)); 554 | // FIX STATIC LIGHTS BEING ENABLED IN 555 | // CUSTOMS 556 | // BY MAKING EITHER BRANCH DEPEND ON canUseEnvironmentEventsAndShouldLoadDynamicEvents 557 | if (!canUseEnvironmentEventsAndShouldLoadDynamicEvents) { 558 | if (defaultLightshowSaveData != nullptr) { 559 | BeatmapDataLoaderVersion4::BeatmapDataLoader::LoadLightshow(beatmapData, defaultLightshowSaveData, 560 | bpmTimeProcessor2, environmentKeywords, 561 | environmentLightGroups, lightEventConverter); 562 | } else { 563 | CJDLogger::Logger.info("Inserting default environment events flag1 {} flag2 {} flag3 {}", flag, flag2, 564 | canUseEnvironmentEventsAndShouldLoadDynamicEvents); 565 | 566 | DefaultEnvironmentEventsFactory::InsertDefaultEvents(beatmapData); 567 | } 568 | } 569 | } 570 | beatmapData->ProcessRemainingData(); 571 | beatmapData->ProcessAndSortBeatmapData(); 572 | 573 | profile.endTimer(); 574 | 575 | CJDLogger::Logger.fmtLog("Finished processing beatmap data"); 576 | auto stopTime = std::chrono::high_resolution_clock::now(); 577 | CJDLogger::Logger.fmtLog( 578 | "This took {}ms", 579 | static_cast(std::chrono::duration_cast(profile.elapsedTime()).count())); 580 | 581 | return beatmapData; 582 | } 583 | 584 | void CustomJSONData::v2::InstallHooks() { 585 | // Install hooks 586 | INSTALL_HOOK_ORIG(CJDLogger::Logger, BeatmapDataLoader_GetBeatmapDataFromSaveDataJson_v2) 587 | INSTALL_HOOK_ORIG(CJDLogger::Logger, BeatmapDataLoader_GetBeatmapDataFromSaveData_v2) 588 | INSTALL_HOOK_ORIG(CJDLogger::Logger, BeatmapSaveData_ConvertBeatmapSaveDataPreV2_5_0Inline) 589 | } 590 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "_config.hpp" 2 | 3 | #include "CustomJSONDataHooks.h" 4 | #include "CJDLogger.h" 5 | 6 | CJD_MOD_EXTERN_FUNC void setup(CModInfo* info) { 7 | *info = CustomJSONData::modInfo.to_c(); 8 | Paper::Logger::RegisterFileContextId(CJDLogger::Logger.tag); 9 | } 10 | 11 | CJD_MOD_EXTERN_FUNC void late_load() { 12 | CJDLogger::Logger.fmtLog("Installing CustomJSONData Hooks!"); 13 | 14 | CustomJSONData::InstallHooks(); 15 | 16 | CJDLogger::Logger.fmtLog("Installed CustomJSONData Hooks!"); 17 | } --------------------------------------------------------------------------------