├── .devcontainer └── devcontainer.json ├── .github └── workflows │ ├── Pre-Release.yml │ ├── Release.yml │ └── buildMod.yml ├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── Cover.jpg ├── ProfileMod.ps1 ├── README.md ├── build.ps1 ├── buildQMOD.ps1 ├── clockmod.json ├── copy.ps1 ├── debugstart.bat ├── include ├── ClockModConfig.hpp ├── ClockValues.hpp ├── ClockViewController.hpp ├── RainbowClock.hpp └── main.hpp ├── mod.template.json ├── qpm.json ├── qpm.shared.json ├── restart-game.ps1 ├── rl-notes.md ├── shared ├── ClockUpdater.hpp └── ColorUtility.hpp ├── src ├── ClockUpdater.cpp ├── ClockViewContoller.cpp └── main.cpp ├── start-logging.ps1 └── validate-modjson.ps1 /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "QPM", 3 | "image": "ghcr.io/dantheman827/qpm-docker:master", 4 | "features": { 5 | "ghcr.io/devcontainers/features/common-utils:2": { 6 | "username": "codespace", 7 | "userUid": "1000", 8 | "userGid": "1000", 9 | "installZsh": false, 10 | "installOhMyZsh": false, 11 | "installOhMyZshConfig": false, 12 | "upgradePackages": false 13 | } 14 | }, 15 | "remoteUser": "codespace", 16 | "containerUser": "codespace", 17 | "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", 18 | "postCreateCommand": "qpm ndk resolve -d && qpm restore" 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/Pre-Release.yml: -------------------------------------------------------------------------------- 1 | name: Pre-Release Mod 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | module_id: ClockMod 8 | prebuildnumber: 0 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | name: Checkout 17 | with: 18 | submodules: true 19 | lfs: true 20 | 21 | - uses: seanmiddleditch/gha-setup-ninja@v3 22 | 23 | - name: Get the tag name 24 | run: echo "version=${GITHUB_REF/refs\/tags\//}-rc.${prebuildnumber}" >> $GITHUB_ENV 25 | 26 | - name: Get BSVersion and Version 27 | shell: pwsh 28 | run: | 29 | echo "BSVersion=$((Get-Content ./mod.template.json -Raw | ConvertFrom-Json).packageVersion)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 30 | #echo "version=$((Get-Content ./qpm.shared.json -Raw | ConvertFrom-Json).config.info.version.Split('-')[0])-rc.$($env:prebuildnumber)" 31 | 32 | - name: Create ndkpath.txt 33 | run: | 34 | echo "$ANDROID_NDK_LATEST_HOME" > ${GITHUB_WORKSPACE}/ndkpath.txt 35 | 36 | - name: Get QPM 37 | if: steps.cache-qpm.outputs.cache-hit != 'true' 38 | uses: dawidd6/action-download-artifact@v2 39 | with: 40 | github_token: ${{secrets.GITHUB_TOKEN}} 41 | workflow: cargo-build.yml 42 | name: linux-qpm 43 | path: QPM 44 | repo: QuestPackageManager/QPM.CLI 45 | 46 | - name: QPM Collapse 47 | run: | 48 | chmod +x ./QPM/qpm 49 | ./QPM/qpm collapse 50 | 51 | - name: QPM Dependencies Cache 52 | id: cache-qpm-deps 53 | uses: actions/cache@v2 54 | env: 55 | cache-name: cache-qpm-deps 56 | with: 57 | path: /home/runner/.local/share/QPM-RS/cache 58 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('qpm.json') }} 59 | restore-keys: | 60 | ${{ runner.os }}-${{ env.cache-name }}- 61 | ${{ runner.os }}-${{ env.cache-name }} 62 | 63 | - name: QPM Set Version & Restore 64 | run: | 65 | ./QPM/qpm package edit --version ${{ env.version }} 66 | ./QPM/qpm restore 67 | 68 | - name: Check dependency Folders 69 | run: | 70 | echo "Checking extern includes" 71 | ls -lh ${GITHUB_WORKSPACE}/extern/includes 72 | echo "" 73 | echo "Checking libs" 74 | ls -lh ${GITHUB_WORKSPACE}/extern/libs 75 | echo "" 76 | echo "Checking QPM/cache Folder" 77 | ls -lh $HOME/.local/share/QPM-RS/cache 78 | echo "" 79 | 80 | - name: Build 81 | run: | 82 | cd ${GITHUB_WORKSPACE} 83 | pwsh -Command ./build.ps1 -actions 84 | 85 | - name: Get Library Name 86 | id: libname 87 | run: | 88 | cd ./build/ 89 | pattern="lib${module_id}*.so" 90 | files=( $pattern ) 91 | echo "NAME=${files[0]}" >> $GITHUB_OUTPUT 92 | 93 | - name: Package QMOD 94 | run: | 95 | cd ${GITHUB_WORKSPACE} 96 | ./QPM/qpm qmod zip -i ./build/ -i ./extern/libs/ ${module_id}_${version}.qmod 97 | 98 | - name: Pre-Release 99 | uses: softprops/action-gh-release@v1 100 | with: 101 | name: ${{ env.module_id }} ${{ env.version }} for Beat Saber ${{ env.BSVersion }} 102 | files: | 103 | ./${{ env.module_id }}_${{ env.version }}.qmod 104 | body_path: ./pre-rl-notes.md 105 | generate_release_notes: true 106 | prerelease: true 107 | draft: true -------------------------------------------------------------------------------- /.github/workflows/Release.yml: -------------------------------------------------------------------------------- 1 | name: Release Mod 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | # Sequence of patterns matched against refs/tags 7 | tags: 8 | - '*' # Push events with tag 9 | 10 | env: 11 | module_id: ClockMod 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 | - name: Get the tag name 25 | run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 26 | 27 | - uses: seanmiddleditch/gha-setup-ninja@v5 28 | 29 | - name: Get QPM 30 | uses: Fernthedev/qpm-action@v1 31 | with: 32 | resolve_ndk: true 33 | #required 34 | workflow_token: ${{secrets.GITHUB_TOKEN}} 35 | restore: true # will run restore on download 36 | cache: true #will cache dependencies 37 | version: '${{ env.version }}' 38 | #qpm_qmod: '${{ env.module_id }}_${{ env.qmodversion }}.qmod' 39 | 40 | - name: QPM Collapse 41 | run: | 42 | qpm collapse 43 | 44 | - name: Check dependency Folders 45 | run: | 46 | echo "Checking extern includes" 47 | ls -lh ${GITHUB_WORKSPACE}/extern/includes 48 | echo "" 49 | echo "Checking libs" 50 | ls -lh ${GITHUB_WORKSPACE}/extern/libs 51 | echo "" 52 | echo "Checking QPM/cache Folder" 53 | ls -lh $HOME/.local/share/QPM-RS/cache 54 | echo "" 55 | 56 | - name: Get BSVersion & Update mod.template.json 57 | shell: pwsh 58 | run: | 59 | $BSVersion = Get-Content ./extern/includes/bs-cordl/include/version.txt 60 | echo "BSVersion=$($BSVersion)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 61 | $modTemplate = Get-Content ./mod.template.json -Raw | ConvertFrom-Json 62 | $modTemplate.packageVersion = $BSVersion 63 | $modTemplate | ConvertTo-Json | Out-File -FilePath ./mod.template.json -Encoding utf8 64 | 65 | # - name: Temp Fix QPM extern.cmake issue 66 | # shell: pwsh 67 | # run: | 68 | # $cmakeFilePath = "./extern.cmake" 69 | # $externcmake = Get-Content $cmakeFilePath -Raw 70 | # $partToRemove = 'add_library(${COMPILE_ID} SHARED ${EXTERN_DIR}/includes/bs-cordl/version.txt)' 71 | # $externcmake -replace [regex]::Escape($partToRemove), '' | Set-Content $cmakeFilePath 72 | 73 | - name: Build 74 | run: | 75 | cd ${GITHUB_WORKSPACE} 76 | pwsh -Command ./build.ps1 -actions 77 | cp ./build/debug/lib${{ env.module_id }}.so ./build/debug_lib${{ env.module_id }}.so 78 | 79 | - name: Get Library Name 80 | id: libname 81 | run: | 82 | cd ./build/ 83 | pattern="lib${module_id}*.so" 84 | files=( $pattern ) 85 | echo "NAME=${files[0]}" >> $GITHUB_OUTPUT 86 | 87 | - name: Package QMOD 88 | run: | 89 | cd ${GITHUB_WORKSPACE} 90 | qpm qmod zip -i ./build/ -i ./extern/libs/ ${module_id}_${version}.qmod 91 | 92 | - name: Release 93 | uses: softprops/action-gh-release@v2 94 | with: 95 | name: ${{ env.module_id }} ${{ env.version }} for Beat Saber ${{ env.BSVersion }} 96 | files: | 97 | ./${{ env.module_id }}_${{ env.version }}.qmod 98 | ./build/lib${{ env.module_id }}.so 99 | ./build/debug_lib${{ env.module_id }}.so 100 | body_path: ./rl-notes.md 101 | draft: true 102 | -------------------------------------------------------------------------------- /.github/workflows/buildMod.yml: -------------------------------------------------------------------------------- 1 | name: NDK build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main, master, dev ] 7 | paths-ignore: 8 | - '**.yml' 9 | - '!.github/workflows/buildMod.yml' 10 | - '**.json' 11 | - '!qpm.json' 12 | - '**.ps1' 13 | - '!build.ps1' 14 | - '!buildQMOD.ps1' 15 | - '**.md' 16 | - '.gitignore' 17 | - '**.ogg' 18 | - '**.zip' 19 | pull_request: 20 | branches: [ main, dev ] 21 | 22 | env: 23 | module_id: ClockMod 24 | 25 | jobs: 26 | build: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | name: Checkout 32 | with: 33 | submodules: true 34 | lfs: true 35 | 36 | - name: Get Version 37 | shell: pwsh 38 | run: | 39 | $branchParts = '${{ github.ref_name }}'.Split('/'); $branchMain = $branchParts[0]; if ($branchParts[0] -match "^\d+$") { $branchMain = 'pr'; $branchSub = "$($branchParts[0])." } elseif ($branchParts.Length -eq 2) { $branchSub = "$($branchParts[1].Replace('.', '-'))." }; echo "version=$((Get-Content ./qpm.shared.json -Raw | ConvertFrom-Json).config.info.version.Split('-')[0])-$($branchMain).${{ github.run_number }}+$($branchSub)ra${{ github.run_attempt }}.$($env:GITHUB_SHA.Substring(0, 7))" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 40 | 41 | 42 | - uses: seanmiddleditch/gha-setup-ninja@v5 43 | 44 | - name: Get QPM 45 | uses: Fernthedev/qpm-action@v1 46 | with: 47 | resolve_ndk: true 48 | #required 49 | workflow_token: ${{secrets.GITHUB_TOKEN}} 50 | restore: true # will run restore on download 51 | cache: true #will cache dependencies 52 | version: '${{ env.version }}' 53 | #qpm_qmod: '${{ env.module_id }}_${{ env.qmodversion }}.qmod' 54 | 55 | - name: QPM Collapse 56 | run: | 57 | qpm collapse 58 | 59 | - name: Check dependency Folders 60 | run: | 61 | echo "Checking extern includes" 62 | ls -lh ${GITHUB_WORKSPACE}/extern/includes 63 | echo "" 64 | echo "Checking libs" 65 | ls -lh ${GITHUB_WORKSPACE}/extern/libs 66 | echo "" 67 | echo "Checking QPM/cache Folder" 68 | ls -lh $HOME/.local/share/QPM-RS/cache 69 | echo "" 70 | 71 | - name: Get BSVersion & Update mod.template.json 72 | shell: pwsh 73 | run: | 74 | $BSVersion = Get-Content ./extern/includes/bs-cordl/include/version.txt 75 | echo "BSVersion=$($BSVersion)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 76 | $modTemplate = Get-Content ./mod.template.json -Raw | ConvertFrom-Json 77 | $modTemplate.packageVersion = $BSVersion 78 | $modTemplate | ConvertTo-Json | Out-File -FilePath ./mod.template.json -Encoding utf8 79 | 80 | # - name: Temp Fix QPM extern.cmake issue 81 | # shell: pwsh 82 | # run: | 83 | # $cmakeFilePath = "./extern.cmake" 84 | # $externcmake = Get-Content $cmakeFilePath -Raw 85 | # $partToRemove = 'add_library(${COMPILE_ID} SHARED ${EXTERN_DIR}/includes/bs-cordl/version.txt)' 86 | # $externcmake -replace [regex]::Escape($partToRemove), '' | Set-Content $cmakeFilePath 87 | # # $qpmjson = Get-Content ./qpm.json -Raw | ConvertFrom-Json 88 | # # foreach ($dependency in $qpmjson.dependencies) { if ($dependency.id -eq "bs-cordl") { $dependency.additionalData.PSObject.Properties.Remove("extraFiles") } } 89 | # # $qpmjson | ConvertTo-Json -Depth 5 | Out-File -FilePath ./qpm.json -Encoding utf8 90 | # # qpm restore 91 | 92 | - name: Build 93 | run: | 94 | cd ${GITHUB_WORKSPACE} 95 | pwsh -Command ./build.ps1 -actions 96 | 97 | - name: Get Library Name 98 | id: libname 99 | run: | 100 | cd ./build/ 101 | pattern="lib${module_id}*.so" 102 | files=( $pattern ) 103 | echo "NAME=${files[0]}" >> $GITHUB_OUTPUT 104 | 105 | - name: Package QMOD 106 | run: | 107 | cd ${GITHUB_WORKSPACE} 108 | qpm qmod zip -i ./build/ -i ./extern/libs/ ${module_id}_${version}.qmod 109 | 110 | 111 | - name: Upload non-debug artifact 112 | uses: actions/upload-artifact@v4 113 | with: 114 | name: ${{ steps.libname.outputs.NAME }} 115 | path: ./build/${{ steps.libname.outputs.NAME }} 116 | if-no-files-found: error 117 | 118 | 119 | - name: Upload debug artifact 120 | uses: actions/upload-artifact@v4 121 | with: 122 | name: debug_${{ steps.libname.outputs.NAME }} 123 | path: ./build/debug/${{ steps.libname.outputs.NAME }} 124 | if-no-files-found: error 125 | 126 | 127 | - name: Upload QMOD 128 | uses: actions/upload-artifact@v4 129 | with: 130 | name: ${{ env.module_id }}-(UNZIP-for-QMOD).qmod 131 | path: ./${{ env.module_id }}_${{ env.version }}.qmod 132 | if-no-files-found: warn 133 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # VSCode config stuff 35 | !.vscode/c_cpp_properties.json 36 | !.vscode/tasks.json 37 | 38 | # Visual Stuido Stuff 39 | .vs/ 40 | 41 | # NDK stuff 42 | out/ 43 | [Ll]ib/ 44 | [Ll]ibs/ 45 | [Oo]bj/ 46 | [Oo]bjs/ 47 | ndkpath.txt 48 | *.log 49 | *.zip 50 | extern/ 51 | Android.mk.backup 52 | *.qmod 53 | *.prolog 54 | *.cmake 55 | /CMakeSettings.json 56 | /build 57 | .cache -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "defines": [ 5 | "VERSION=\"0.1.0\"", 6 | "__GNUC__", 7 | "__aarch64__" 8 | ], 9 | "includePath": [ 10 | "${workspaceFolder}/**", 11 | "${workspaceFolder}/shared", 12 | "${workspaceFolder}/include", 13 | "${workspaceFolder}/extern/**", 14 | "${workspaceFolder}/extern/libil2cpp/il2cpp/libil2cpp", 15 | "C:/androidndk/android-ndk-r21d-windows-x86_64/android-ndk-r21d/**" 16 | ], 17 | "name": "Quest", 18 | "cStandard": "c11", 19 | "cppStandard": "c++20", 20 | "intelliSenseMode": "clang-x64" 21 | } 22 | ], 23 | "version": 4 24 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "iosfwd": "cpp", 4 | "__config": "cpp", 5 | "__nullptr": "cpp", 6 | "thread": "cpp", 7 | "any": "cpp", 8 | "deque": "cpp", 9 | "list": "cpp", 10 | "map": "cpp", 11 | "optional": "cpp", 12 | "queue": "cpp", 13 | "set": "cpp", 14 | "stack": "cpp", 15 | "unordered_map": "cpp", 16 | "unordered_set": "cpp", 17 | "variant": "cpp", 18 | "vector": "cpp", 19 | "__bit_reference": "cpp", 20 | "__debug": "cpp", 21 | "__errc": "cpp", 22 | "__functional_base": "cpp", 23 | "__hash_table": "cpp", 24 | "__locale": "cpp", 25 | "__mutex_base": "cpp", 26 | "__node_handle": "cpp", 27 | "__split_buffer": "cpp", 28 | "__string": "cpp", 29 | "__threading_support": "cpp", 30 | "__tree": "cpp", 31 | "__tuple": "cpp", 32 | "algorithm": "cpp", 33 | "array": "cpp", 34 | "atomic": "cpp", 35 | "bit": "cpp", 36 | "bitset": "cpp", 37 | "cctype": "cpp", 38 | "cfenv": "cpp", 39 | "charconv": "cpp", 40 | "chrono": "cpp", 41 | "cinttypes": "cpp", 42 | "clocale": "cpp", 43 | "cmath": "cpp", 44 | "codecvt": "cpp", 45 | "compare": "cpp", 46 | "complex": "cpp", 47 | "condition_variable": "cpp", 48 | "csetjmp": "cpp", 49 | "csignal": "cpp", 50 | "cstdarg": "cpp", 51 | "cstddef": "cpp", 52 | "cstdint": "cpp", 53 | "cstdio": "cpp", 54 | "cstdlib": "cpp", 55 | "cstring": "cpp", 56 | "ctime": "cpp", 57 | "cwchar": "cpp", 58 | "cwctype": "cpp", 59 | "exception": "cpp", 60 | "coroutine": "cpp", 61 | "propagate_const": "cpp", 62 | "forward_list": "cpp", 63 | "fstream": "cpp", 64 | "functional": "cpp", 65 | "future": "cpp", 66 | "initializer_list": "cpp", 67 | "iomanip": "cpp", 68 | "ios": "cpp", 69 | "iostream": "cpp", 70 | "istream": "cpp", 71 | "iterator": "cpp", 72 | "limits": "cpp", 73 | "locale": "cpp", 74 | "memory": "cpp", 75 | "mutex": "cpp", 76 | "new": "cpp", 77 | "numeric": "cpp", 78 | "ostream": "cpp", 79 | "random": "cpp", 80 | "ratio": "cpp", 81 | "regex": "cpp", 82 | "scoped_allocator": "cpp", 83 | "span": "cpp", 84 | "sstream": "cpp", 85 | "stdexcept": "cpp", 86 | "streambuf": "cpp", 87 | "string": "cpp", 88 | "string_view": "cpp", 89 | "strstream": "cpp", 90 | "system_error": "cpp", 91 | "tuple": "cpp", 92 | "type_traits": "cpp", 93 | "typeindex": "cpp", 94 | "typeinfo": "cpp", 95 | "utility": "cpp", 96 | "valarray": "cpp", 97 | "xstring": "cpp", 98 | "xlocale": "cpp", 99 | "xlocbuf": "cpp", 100 | "concepts": "cpp", 101 | "filesystem": "cpp", 102 | "shared_mutex": "cpp", 103 | "xfacet": "cpp", 104 | "xhash": "cpp", 105 | "xiosbase": "cpp", 106 | "xlocinfo": "cpp", 107 | "xlocmes": "cpp", 108 | "xlocmon": "cpp", 109 | "xlocnum": "cpp", 110 | "xloctime": "cpp", 111 | "xmemory": "cpp", 112 | "xstddef": "cpp", 113 | "xtr1common": "cpp", 114 | "xtree": "cpp", 115 | "xutility": "cpp", 116 | "format": "cpp" 117 | }, 118 | "C_Cpp.errorSquiggles": "Enabled", 119 | "json.schemas": [ 120 | { 121 | "fileMatch": ["/qpm.json"], 122 | "url": "https://raw.githubusercontent.com/QuestPackageManager/QPM.Package/refs/heads/main/qpm.schema.json" 123 | }, 124 | { 125 | "fileMatch": ["/qpm.shared.json"], 126 | "url": "https://raw.githubusercontent.com/QuestPackageManager/QPM.Package/refs/heads/main/qpm.shared.schema.json" 127 | } 128 | ] 129 | } 130 | -------------------------------------------------------------------------------- /.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": "BMBF Build", 49 | "detail": "Builds a .zip to be uploaded into BMBF", 50 | "type": "shell", 51 | "command": "./buildBMBF.ps1", 52 | "windows": { 53 | "command": "./buildBMBF.ps1" 54 | }, 55 | "args": [], 56 | "group": "build", 57 | "options": { 58 | "env": {} 59 | } 60 | }, 61 | { 62 | "label": "Start logging", 63 | "detail": "Records a log to log.txt using adb logcat", 64 | "type": "shell", 65 | "command": "./startlogging.bat" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # include some defines automatically made by qpm 2 | include(qpm_defines.cmake) 3 | 4 | # Enable link time optimization 5 | # In my experience, this can be highly unstable but it nets a huge size optimization and likely performance 6 | # However, the instability was seen using Android.mk/ndk-build builds. With Ninja + CMake, this problem seems to have been solved. 7 | # As always, test thoroughly 8 | # - Fern 9 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 10 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 11 | 12 | cmake_minimum_required(VERSION 3.21) 13 | project(${COMPILE_ID}) 14 | 15 | # c++ standard 16 | set(CMAKE_CXX_STANDARD 20) 17 | set(CMAKE_CXX_STANDARD_REQUIRED 20) 18 | 19 | # define that stores the actual source directory 20 | set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) 21 | set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) 22 | 23 | # stop symbol leaking 24 | #add_link_options(-Wl,--exclude-libs,ALL) 25 | 26 | # compile options used 27 | add_compile_options(-frtti -fexceptions -fvisibility=hidden) 28 | add_compile_options(-O3) 29 | # compile definitions used 30 | add_compile_definitions(VERSION=\"${MOD_VERSION}\") 31 | add_compile_definitions(MOD_ID=\"${MOD_ID}\") 32 | add_compile_definitions(VERSION_LONG=0) 33 | add_compile_definitions(CORDL_RUNTIME_FIELD_NULL_CHECKS) 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 | # add all src files to compile 40 | add_library( 41 | ${COMPILE_ID} 42 | SHARED 43 | ${cpp_file_list} 44 | ${c_file_list} 45 | ) 46 | 47 | target_include_directories(${COMPILE_ID} PRIVATE .) 48 | 49 | # add src dir as include dir 50 | target_include_directories(${COMPILE_ID} PRIVATE ${SOURCE_DIR}) 51 | # add include dir as include dir 52 | target_include_directories(${COMPILE_ID} PRIVATE ${INCLUDE_DIR}) 53 | # add shared dir as include dir 54 | target_include_directories(${COMPILE_ID} PUBLIC ${SHARED_DIR}) 55 | # codegen includes 56 | target_include_directories(${COMPILE_ID} PRIVATE ${EXTERN_DIR}/includes/${CODEGEN_ID}/include) 57 | 58 | target_link_libraries(${COMPILE_ID} PRIVATE -llog) 59 | # add extern stuff like libs and other includes 60 | include(extern.cmake) 61 | 62 | # beatsaber hook inline hook 63 | RECURSE_FILES(src_inline_hook_beatsaber_hook_local_extra_c ${EXTERN_DIR}/includes/beatsaber-hook/shared/inline-hook/*.c) 64 | RECURSE_FILES(src_inline_hook_beatsaber_hook_local_extra_cpp ${EXTERN_DIR}/includes/beatsaber-hook/shared/inline-hook/*.cpp) 65 | target_sources(${COMPILE_ID} PRIVATE ${src_inline_hook_beatsaber_hook_local_extra_c}) 66 | target_sources(${COMPILE_ID} PRIVATE ${src_inline_hook_beatsaber_hook_local_extra_cpp}) 67 | 68 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 69 | COMMAND ${CMAKE_STRIP} -d --strip-all 70 | "lib${COMPILE_ID}.so" -o "stripped_lib${COMPILE_ID}.so" 71 | COMMENT "Strip debug symbols done on final binary.") 72 | 73 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 74 | COMMAND ${CMAKE_COMMAND} -E make_directory debug 75 | COMMENT "Make directory for debug symbols" 76 | ) 77 | 78 | add_custom_command(TARGET ${COMPILE_ID} POST_BUILD 79 | COMMAND ${CMAKE_COMMAND} -E rename lib${COMPILE_ID}.so debug/lib${COMPILE_ID}.so 80 | COMMENT "Rename the lib to debug_ since it has debug symbols" 81 | ) 82 | 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 | ) -------------------------------------------------------------------------------- /Cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EnderdracheLP/ClockMod/4af6fa8cf97c3156ec24c2e1c7116f0d3a9ea25a/Cover.jpg -------------------------------------------------------------------------------- /ProfileMod.ps1: -------------------------------------------------------------------------------- 1 | $NDKPath = Get-Content $PSScriptRoot/ndkpath.txt 2 | 3 | python $NDKPath/simpleperf/app_profiler.py -p com.beatgames.beatsaber -lib .\obj\local\arm64-v8a\ --record_options=--exit-with-parent 4 | python $NDKPath/simpleperf/report_html.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clock Mod 2 | [![NDK build Latest](https://github.com/EnderdracheLP/ClockMod/actions/workflows/buildMod.yml/badge.svg)](https://github.com/EnderdracheLP/ClockMod/actions/workflows/buildMod.yml) 3 | 4 | Displays the time and optionally the battery percentage in the game. 5 | 6 | ## Credits 7 | 8 | For creating the original Mod 9 | * [ChillGunner](https://github.com/ChillGunner) - [ClockMod](https://github.com/ChillGunner/ClockMod) 10 | 11 | Credits 12 | 13 | * [zoller27osu](https://github.com/zoller27osu), [Sc2ad](https://github.com/Sc2ad) and [jakibaki](https://github.com/jakibaki) - [beatsaber-hook](https://github.com/sc2ad/beatsaber-hook) 14 | * [raftario](https://github.com/raftario) 15 | * [Lauriethefish](https://github.com/Lauriethefish) and [danrouse](https://github.com/danrouse) for [this template](https://github.com/Lauriethefish/quest-mod-template) 16 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | Param ( 2 | [Parameter(Mandatory=$false, HelpMessage="The version the mod should be compiled with")][Alias("ver")][string]$Version, # Currently not used 3 | [Parameter(Mandatory=$false, HelpMessage="Switch to create a clean compilation")][Alias("rebuild")][Switch]$clean, 4 | [Parameter(Mandatory=$false, HelpMessage="To create a release build")][Alias("publish")][Switch]$release, 5 | [Parameter(Mandatory=$false, HelpMessage="To create a github actions build, assumes specific Environment variables are set")][Alias("github-build")][Switch]$actions, 6 | [Parameter(Mandatory=$false, HelpMessage="To create a debug build (Does not strip other libs)")][Alias("debug-build")][Switch]$debugbuild # Not implemented yet 7 | ) 8 | 9 | 10 | $NDKPath = Get-Content $PSScriptRoot/ndkpath.txt 11 | 12 | # Legacy arg check, might remove 13 | for ($i = 0; $i -le $args.Length-1; $i++) { 14 | echo "Arg $($i) is $($args[$i])" 15 | if ($args[$i] -eq "--actions") { $actions = $true } 16 | elseif ($args[$i] -eq "--release") { $release = $true } 17 | } 18 | 19 | 20 | if ($args.Count -eq 0 -or $actions -ne $true) { 21 | $qpmshared = "./qpm.shared.json" 22 | $qpmsharedJson = Get-Content $qpmshared -Raw | ConvertFrom-Json 23 | $ModID = $qpmsharedJson.config.info.id 24 | $VERSION = $qpmsharedJson.config.info.version.replace("-Dev", "") 25 | if ([string]::IsNullOrEmpty($VERSION)) { 26 | $VERSION = "0.0.1" 27 | } 28 | if ($release -ne $true) { 29 | $VERSION += "-Dev" 30 | } 31 | } 32 | 33 | 34 | if ($actions -eq $true) { 35 | $ModID = $env:module_id 36 | $VERSION = $env:version 37 | if ([string]::IsNullOrEmpty($VERSION)) { 38 | $qpmsharedJson = Get-Content $qpmshared -Raw | ConvertFrom-Json 39 | $VERSION = $qpmsharedJson.config.info.version.replace("-Dev", "") 40 | } 41 | } else { 42 | & qpm package edit --version $VERSION 43 | # Temp Fix for qpm adding version.txt as library in extern.cmake 44 | #$cmakeFilePath = "./extern.cmake"; $externcmake = Get-Content $cmakeFilePath -Raw; $externcmake -replace [regex]::Escape('add_library(${COMPILE_ID} SHARED ${EXTERN_DIR}/includes/bs-cordl/version.txt)'), '' | Set-Content $cmakeFilePath 45 | } 46 | 47 | if ((Test-Path "./extern/includes/beatsaber-hook/shared/inline-hook/And64InlineHook.cpp", "./extern/includes/beatsaber-hook/shared/inline-hook/inlineHook.c", "./extern/includes/beatsaber-hook/shared/inline-hook/relocate.c") -contains $false) { 48 | Write-Host "Critical: Missing inline-hook" 49 | if (!(Test-Path "./extern/includes/beatsaber-hook/shared/inline-hook/And64InlineHook.cpp")) { 50 | Write-Host "./extern/includes/beatsaber-hook/shared/inline-hook/And64InlineHook.cpp" 51 | } 52 | if (!(Test-Path "./extern/includes/beatsaber-hook/shared/inline-hook/inlineHook.c")) { 53 | Write-Host "./extern/includes/beatsaber-hook/shared/inline-hook/inlineHook.c" 54 | } 55 | if (!(Test-Path "./extern/includes/beatsaber-hook/inline-hook/shared/relocate.c")) { 56 | Write-Host "./extern/includes/beatsaber-hook/shared/inline-hook/relocate.c" 57 | } 58 | Write-Host "Task Failed" 59 | exit 1; 60 | } 61 | Write-Output "Building mod with ModID: $ModID version: $VERSION" 62 | 63 | if ($clean.IsPresent) 64 | { 65 | if (Test-Path -Path "build") 66 | { 67 | remove-item build -R 68 | } 69 | } 70 | 71 | if (($clean.IsPresent) -or (-not (Test-Path -Path "build"))) 72 | { 73 | $out = new-item -Path build -ItemType Directory 74 | } 75 | 76 | Set-Location build 77 | & cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" ../ 78 | & cmake --build . -j 6 79 | $ExitCode = $LastExitCode 80 | Set-Location .. 81 | exit $ExitCode 82 | Write-Output Done -------------------------------------------------------------------------------- /buildQMOD.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory=$false, HelpMessage="The name the output qmod file should have")][String] $qmodname="ClockMod", 3 | 4 | [Parameter(Mandatory=$false, HelpMessage="Switch to create a clean compilation")] 5 | [Alias("rebuild")] 6 | [Switch] $clean, 7 | 8 | [Parameter(Mandatory=$false, HelpMessage="Switch to create a release build")] 9 | [Switch] $release, 10 | 11 | [Parameter(Mandatory=$false, HelpMessage="Prints the help instructions")] 12 | [Switch] $help, 13 | 14 | [Parameter(Mandatory=$false, HelpMessage="Tells the script to not compile and only package the existing files")] 15 | [Alias("actions", "pack")] 16 | [Switch] $package 17 | ) 18 | 19 | # Builds a .qmod file for loading with QP or MBF 20 | 21 | 22 | if ($help -eq $true) { 23 | echo "`"BuildQmod `" - Copiles your mod into a `".so`" or a `".a`" library" 24 | echo "`n-- Parameters --`n" 25 | echo "qmodName `t The file name of your qmod" 26 | 27 | echo "`n-- Arguments --`n" 28 | 29 | echo "-clean `t`t Performs a clean build on both your library and the qmod" 30 | echo "-help `t`t Prints this" 31 | echo "-package `t Only packages existing files, without recompiling`n" 32 | 33 | exit 34 | } 35 | 36 | if ($qmodName -eq "") 37 | { 38 | echo "Give a proper qmod name and try again" 39 | exit 40 | } 41 | 42 | if (($args.Count -eq 0 -or $dev -eq $true) -And $package -eq $false) { 43 | echo "Packaging QMod $qmodName" 44 | & $PSScriptRoot/build.ps1 -clean:$clean --release:$release 45 | 46 | if ($LASTEXITCODE -ne 0) { 47 | echo "Failed to build, exiting..." 48 | exit $LASTEXITCODE 49 | } 50 | 51 | # Update BS Version in mod.template.json using bs-cordl version.txt 52 | $modTemplate = Get-Content "./mod.template.json" -Raw | ConvertFrom-Json 53 | $bsversion = Get-Content "./extern/includes/bs-cordl/include/version.txt" 54 | if (-not [string]::IsNullOrWhitespace($bsversion)) { 55 | Write-Output "Setting Package Version to $bsversion" 56 | $modTemplate.packageVersion = $bsversion 57 | $modTemplate | ConvertTo-Json -Depth 10 | Set-Content "./mod.template.json" 58 | } else { 59 | Write-Error "Missing bs-cordl version.txt" 60 | } 61 | } 62 | 63 | $qpmshared = "./qpm.shared.json" 64 | $qpmsharedJson = Get-Content $qpmshared -Raw | ConvertFrom-Json 65 | 66 | # if ([string]::IsNullOrEmpty($env:version)) { 67 | # $qmodVersion = $qpmsharedJson.config.info.version 68 | # $qmodName += "_v$qmodVersion" 69 | # echo "qmodName set to $qmodName" 70 | # } 71 | 72 | 73 | if ($package -eq $true -And $env:version.Contains('-Dev')) { 74 | $qmodName = "$($env:module_id)_$($env:version)" 75 | echo "Actions: Packaging QMod $qmodName" 76 | } elseif ($package -eq $true) { 77 | $qmodName = "$($env:module_id)" 78 | echo "Actions: Packaging QMod $qmodName" 79 | } else { 80 | $qmodName += "_$($qpmsharedJson.config.info.version)" 81 | } 82 | 83 | $qmod = $qmodName + ".qmod" 84 | 85 | & qpm qmod zip -i ./build/ -i ./extern/libs/ $qmod 86 | 87 | echo "Task Completed" 88 | -------------------------------------------------------------------------------- /clockmod.json: -------------------------------------------------------------------------------- 1 | { 2 | "Show During Song": true, 3 | "24/12 Toggle": false, 4 | "Show Seconds": true, 5 | "Show Battery Percentage": true, 6 | "Rainbowify it": true, 7 | "Font Size": 3.5, 8 | "In-Menu Clock Position (Top/Bottom)": false, 9 | "Clock Color": { 10 | "r": 255.0, 11 | "g": 255.0, 12 | "b": 255.0, 13 | "a": 255.0 14 | } 15 | } -------------------------------------------------------------------------------- /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 | $modJson = Get-Content "./mod.json" -Raw | ConvertFrom-Json 54 | 55 | foreach ($fileName in $modJson.modFiles) { 56 | if ($useDebug -eq $true) { 57 | & adb push build/debug/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/early_mods/$fileName 58 | } else { 59 | & adb push build/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/early_mods/$fileName 60 | } 61 | } 62 | 63 | foreach ($fileName in $modJson.lateModFiles) { 64 | if ($useDebug -eq $true) { 65 | & adb push build/debug/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/mods/$fileName 66 | } else { 67 | & adb push build/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/mods/$fileName 68 | } 69 | } 70 | 71 | & $PSScriptRoot/restart-game.ps1 72 | 73 | if ($log -eq $true) { 74 | & adb logcat -c 75 | & $PSScriptRoot/start-logging.ps1 -self:$self -all:$all -custom:$custom -file:$file 76 | } -------------------------------------------------------------------------------- /debugstart.bat: -------------------------------------------------------------------------------- 1 | "C:\Users\%username%\AppData\Roaming\SideQuest\platform-tools\adb.exe" shell am force-stop com.beatgames.beatsaber 2 | 3 | powershell ./build.ps1 4 | 5 | "C:\Users\%username%\AppData\Roaming\SideQuest\platform-tools\adb.exe" push libs/arm64-v8a/libclockmod.so /sdcard/Android/data/com.beatgames.beatsaber/files/mods/libclockmod.so 6 | 7 | "C:\Users\%username%\AppData\Roaming\SideQuest\platform-tools\adb.exe" shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity 8 | 9 | echo Done 10 | -------------------------------------------------------------------------------- /include/ClockModConfig.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config-utils/shared/config-utils.hpp" 3 | 4 | DECLARE_CONFIG(ModConfig) { 5 | 6 | CONFIG_VALUE(InSong, bool, "Show During Song", true, "If the Clock should be shown while playing a beatmap."); 7 | CONFIG_VALUE(InReplay, bool, "Show During Replay", true, "If the Clock should be shown while playing a replay."); 8 | CONFIG_VALUE(TwelveToggle, bool, "24/12 Toggle", false, "If time should be in 12 or 24 hour format."); 9 | CONFIG_VALUE(SecToggle, bool, "Show Seconds", false, "If seconds should be displayed."); 10 | CONFIG_VALUE(BattToggle, bool, "Show Battery Percentage", false, "Displays Battery percentage next to the clock."); 11 | CONFIG_VALUE(RainbowClock, bool, "Rainbowify it", false, "Makes the Clock beautiful."); 12 | CONFIG_VALUE(FontSize, float, "Font Size", 3.5, "Changes the Font Size of the Clock (Default: 3.5)"); 13 | CONFIG_VALUE(ClockPosition, bool, "Clock Position (Top/Bottom)", false, "If the Clock should be at the top or the bottom"); 14 | 15 | // CONFIG_VALUE(ClockXOffset, double, "Clock X Offset", 0); 16 | // CONFIG_VALUE(ClockYOffset, double, "Clock Y Offset", 0); 17 | // CONFIG_VALUE(ClockZOffset, double, "Clock Z Offset", 0); 18 | CONFIG_VALUE(ClockColor, UnityEngine::Color, "Clock Color", UnityEngine::Color(1.0f, 1.0f, 1.0f, 1.0f), "The color of the clock text."); 19 | }; 20 | -------------------------------------------------------------------------------- /include/ClockValues.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "UnityEngine/Vector3.hpp" 3 | #include "UnityEngine/UI/VerticalLayoutGroup.hpp" 4 | 5 | // This containts all the ClockPositions 6 | struct ClockPos_t { 7 | // Clock InMenu Positions Top X Y Z 8 | // UnityEngine::Vector3 MenuPosTop = UnityEngine::Vector3(0.0f, -1.7f, 5.0f); 9 | UnityEngine::Vector3 MenuPosTop = UnityEngine::Vector3(0.0f, -1.4f, 5.0f); 10 | UnityEngine::Vector3 MenuPosBottom = UnityEngine::Vector3(0.0f, -1.26f, 0.0f); 11 | 12 | // Clock InNormalSong Positions Top 13 | UnityEngine::Vector3 NormalSongPosTop = UnityEngine::Vector3(0.0f, -2.5f, 7.5f ); // 0, -2, 8 // 0, -1.7, 5.6 14 | float NormalSongScaleTop = 1; 15 | UnityEngine::Vector3 NormalSongRotationTop = UnityEngine::Vector3(-10.0f, 0.0f, 0.0f ); 16 | // Clock InNormalSong Positions Bottom 17 | UnityEngine::Vector3 NormalSongPosDown = UnityEngine::Vector3(0.0f, -1.4f, 1.3f ); // 0, -2.5, 6.65 // 0, -4.45, 2 // 0.0f, -3.5f, 6.65f 18 | float NormalSongScaleDown = 0.5f; 19 | UnityEngine::Vector3 NormalSongRotationDown = UnityEngine::Vector3(32.0f, 0, 0 ); // 45, 0, 0 20 | 21 | 22 | // Clock In360/90 Song Positions Top 23 | UnityEngine::Vector3 RotateSongPosTop = UnityEngine::Vector3(0, 0.5f, 10 ); 24 | float RotateSongScaleTop = 1; // Scale of the Clock in 360/90 Songs Bottom 25 | UnityEngine::Vector3 RotateSongRotationTop = UnityEngine::Vector3(0, 0, 0 ); 26 | //float RotateSongRotationTopX = 20; // Rotation of the Clock in 360/90 Songs Top 27 | //Clock in360/90 Song Positions/Scale/Rotations Bottom 28 | UnityEngine::Vector3 RotateSongPosDown = UnityEngine::Vector3(0, -2.15f, 3 ); // Position of the Clock in 360/90 Songs Bottom 29 | float RotateSongScaleDown = 0.6f; // Old Scale 0.8 30 | UnityEngine::Vector3 RotateSongRotationDown = UnityEngine::Vector3(35, 0, 0 ); // Rotation of the Clock in 360/90 Songs Bottom 31 | //float RotateSongRotationDownX = 20; // Rotation of the Clock in 360/90 Songs Bottom 32 | 33 | UnityEngine::Vector3 SongPausePosDown = UnityEngine::Vector3(0, -4.45f, 2 ); 34 | bool ap1 = false; // This should be set to false by default unless we're testing it 35 | }; 36 | extern ClockPos_t ClockPos; -------------------------------------------------------------------------------- /include/ClockViewController.hpp: -------------------------------------------------------------------------------- 1 | #include "HMUI/CurvedTextMeshPro.hpp" 2 | #include "HMUI/ViewController.hpp" 3 | #include "bsml/shared/BSML/Components/ModalColorPicker.hpp" 4 | #include "custom-types/shared/coroutine.hpp" 5 | #include "custom-types/shared/macros.hpp" 6 | 7 | DECLARE_CLASS_CODEGEN(ClockMod, ClockViewController, HMUI::ViewController) { 8 | 9 | DECLARE_OVERRIDE_METHOD(void, DidActivate, il2cpp_utils::FindMethodUnsafe("HMUI", "ViewController", "DidActivate", 3), bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling); 10 | DECLARE_OVERRIDE_METHOD(void, DidDeactivate, il2cpp_utils::FindMethodUnsafe("HMUI", "ViewController", "DidDeactivate", 2), bool removedFromHierarchy, bool systemScreenDisabling); 11 | 12 | DECLARE_INSTANCE_FIELD(BSML::ModalColorPicker*, ColorPicker); 13 | DECLARE_INSTANCE_FIELD(HMUI::CurvedTextMeshPro*, TimeInfo); 14 | 15 | custom_types::Helpers::Coroutine UpdateTimeText(); 16 | bool SettingsOpen; 17 | }; 18 | -------------------------------------------------------------------------------- /include/RainbowClock.hpp: -------------------------------------------------------------------------------- 1 | // /* 2 | // Rainbow Clock thanks to Redbrumbler 3 | #pragma once 4 | #include 5 | #include 6 | 7 | namespace ClockMod { 8 | class RainbowClock 9 | { 10 | public: 11 | static std::string rainbowify(std::string); 12 | static void textRainbowifier(std::string); 13 | static inline int rainbowIndex = rand() % 12; 14 | private: 15 | static inline const std::vector colors = { 16 | "#ff6060", 17 | "#ffa060", 18 | "#ffff60", 19 | "#a0ff60", 20 | "#60ff60", 21 | "#60ffa0", 22 | "#60ffff", 23 | "#60a0ff", 24 | "#6060ff", 25 | "#a060ff", 26 | "#ff60ff", 27 | "#ff60a0" 28 | }; 29 | }; 30 | } 31 | // */ -------------------------------------------------------------------------------- /include/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Include the modloader header, which allows us to tell the modloader which mod this is, and the version etc. 4 | #include "paper2_scotland2/shared/logger.hpp" 5 | 6 | // Define these functions here so that we can easily read configuration and log information from other files 7 | Paper::ConstLoggerContext<9UL> logger(); 8 | 9 | // Defining values here, so I can use them wherever the fuck I want to. 10 | struct Config_t { 11 | bool InMPLobby = false; // Checks if in MPLobby 12 | bool InMP = false; // Checks if in MP 13 | float ClockY = -1.7; 14 | float ClockZ = 3.85; 15 | bool IsInSong = false; // Checks if in a Song 16 | // bool InSettings = false; 17 | bool InRotationMap = false; // Checks if in a 360/90 Map 18 | bool noTextAndHUD = false; 19 | }; 20 | extern Config_t Config; 21 | #define MOD_EXPORT extern "C" __attribute__((visibility("default"))) 22 | 23 | // extern bool InMPLobby; 24 | -------------------------------------------------------------------------------- /mod.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/Lauriethefish/QuestPatcher.QMod/main/QuestPatcher.QMod/Resources/qmod.schema.json", 3 | "_QPVersion": "0.1.1", 4 | "name": "${mod_name}", 5 | "id": "${mod_id}", 6 | "author": "BoopetyDoopety, EnderdracheLP", 7 | "modloader": "Scotland2", 8 | "version": "${version}", 9 | "packageId": "com.beatgames.beatsaber", 10 | "packageVersion": "1.40.4_5283", 11 | "description": "Displays the time in game. Alternatively also the Battery Percentage", 12 | "coverImage": "Cover.jpg", 13 | "dependencies": [], 14 | "modFiles": [], 15 | "lateModFiles": [ 16 | "${binary}" 17 | ], 18 | "libraryFiles": [], 19 | "fileCopies": [], 20 | "copyExtensions": [] 21 | } 22 | -------------------------------------------------------------------------------- /qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/QuestPackageManager/QPM.Package/refs/heads/main/qpm.schema.json", 3 | "version": "0.4.0", 4 | "sharedDir": "shared", 5 | "dependenciesDir": "extern", 6 | "info": { 7 | "name": "Clock Mod", 8 | "id": "ClockMod", 9 | "version": "1.11.0-Dev", 10 | "url": null, 11 | "additionalData": { 12 | "overrideSoName": "libClockMod.so", 13 | "cmake": true 14 | } 15 | }, 16 | "workspace": { 17 | "scripts": {}, 18 | "ndk": "^27.2.12479018", 19 | "qmodIncludeDirs": [], 20 | "qmodIncludeFiles": [], 21 | "qmodOutput": null 22 | }, 23 | "dependencies": [ 24 | { 25 | "id": "beatsaber-hook", 26 | "versionRange": "^6.4.1", 27 | "additionalData": {} 28 | }, 29 | { 30 | "id": "bs-cordl", 31 | "versionRange": "4004.*", 32 | "additionalData": {} 33 | }, 34 | { 35 | "id": "bsml", 36 | "versionRange": "^0.4.51", 37 | "additionalData": {} 38 | }, 39 | { 40 | "id": "conditional-dependencies", 41 | "versionRange": "^0.3.0", 42 | "additionalData": {} 43 | }, 44 | { 45 | "id": "config-utils", 46 | "versionRange": "^2.0.2", 47 | "additionalData": {} 48 | }, 49 | { 50 | "id": "custom-types", 51 | "versionRange": "^0.18.2", 52 | "additionalData": {} 53 | }, 54 | { 55 | "id": "paper2_scotland2", 56 | "versionRange": "^4.6.1", 57 | "additionalData": {} 58 | }, 59 | { 60 | "id": "scotland2", 61 | "versionRange": "^0.1.6", 62 | "additionalData": { 63 | "includeQmod": false, 64 | "private": true 65 | } 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /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": "Clock Mod", 9 | "id": "ClockMod", 10 | "version": "1.11.0-Dev", 11 | "url": null, 12 | "additionalData": { 13 | "overrideSoName": "libClockMod.so", 14 | "cmake": true 15 | } 16 | }, 17 | "workspace": { 18 | "scripts": {}, 19 | "ndk": "^27.2.12479018", 20 | "qmodIncludeDirs": [], 21 | "qmodIncludeFiles": [], 22 | "qmodOutput": null 23 | }, 24 | "dependencies": [ 25 | { 26 | "id": "beatsaber-hook", 27 | "versionRange": "^6.4.1", 28 | "additionalData": {} 29 | }, 30 | { 31 | "id": "bs-cordl", 32 | "versionRange": "4004.*", 33 | "additionalData": {} 34 | }, 35 | { 36 | "id": "bsml", 37 | "versionRange": "^0.4.51", 38 | "additionalData": {} 39 | }, 40 | { 41 | "id": "conditional-dependencies", 42 | "versionRange": "^0.3.0", 43 | "additionalData": {} 44 | }, 45 | { 46 | "id": "config-utils", 47 | "versionRange": "^2.0.2", 48 | "additionalData": {} 49 | }, 50 | { 51 | "id": "custom-types", 52 | "versionRange": "^0.18.2", 53 | "additionalData": {} 54 | }, 55 | { 56 | "id": "paper2_scotland2", 57 | "versionRange": "^4.6.1", 58 | "additionalData": {} 59 | }, 60 | { 61 | "id": "scotland2", 62 | "versionRange": "^0.1.6", 63 | "additionalData": { 64 | "includeQmod": false, 65 | "private": true 66 | } 67 | } 68 | ] 69 | }, 70 | "restoredDependencies": [ 71 | { 72 | "dependency": { 73 | "id": "bsml", 74 | "versionRange": "=0.4.51", 75 | "additionalData": { 76 | "soLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.51/libbsml.so", 77 | "debugSoLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.51/debug_libbsml.so", 78 | "overrideSoName": "libbsml.so", 79 | "modLink": "https://github.com/bsq-ports/Quest-BSML/releases/download/v0.4.51/BSML.qmod", 80 | "branchName": "version/v0_4_51", 81 | "cmake": true 82 | } 83 | }, 84 | "version": "0.4.51" 85 | }, 86 | { 87 | "dependency": { 88 | "id": "paper2_scotland2", 89 | "versionRange": "=4.6.1", 90 | "additionalData": { 91 | "soLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.1/libpaper2_scotland2.so", 92 | "overrideSoName": "libpaper2_scotland2.so", 93 | "modLink": "https://github.com/Fernthedev/paperlog/releases/download/v4.6.1/paper2_scotland2.qmod", 94 | "branchName": "version/v4_6_1", 95 | "compileOptions": { 96 | "systemIncludes": [ 97 | "shared/utfcpp/source" 98 | ] 99 | }, 100 | "cmake": false 101 | } 102 | }, 103 | "version": "4.6.1" 104 | }, 105 | { 106 | "dependency": { 107 | "id": "config-utils", 108 | "versionRange": "=2.0.2", 109 | "additionalData": { 110 | "headersOnly": true, 111 | "soLink": "https://github.com/darknight1050/config-utils/releases/download/v2.0.2/libconfig-utils_test.so", 112 | "overrideSoName": "libconfig-utils_test.so", 113 | "branchName": "version/v2_0_2", 114 | "cmake": true 115 | } 116 | }, 117 | "version": "2.0.2" 118 | }, 119 | { 120 | "dependency": { 121 | "id": "custom-types", 122 | "versionRange": "=0.18.2", 123 | "additionalData": { 124 | "soLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/libcustom-types.so", 125 | "debugSoLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/debug_libcustom-types.so", 126 | "overrideSoName": "libcustom-types.so", 127 | "modLink": "https://github.com/QuestPackageManager/Il2CppQuestTypePatching/releases/download/v0.18.2/CustomTypes.qmod", 128 | "branchName": "version/v0_18_2", 129 | "compileOptions": { 130 | "cppFlags": [ 131 | "-Wno-invalid-offsetof" 132 | ] 133 | }, 134 | "cmake": true 135 | } 136 | }, 137 | "version": "0.18.2" 138 | }, 139 | { 140 | "dependency": { 141 | "id": "libil2cpp", 142 | "versionRange": "=0.4.0", 143 | "additionalData": { 144 | "headersOnly": true, 145 | "compileOptions": { 146 | "systemIncludes": [ 147 | "il2cpp/external/baselib/Include", 148 | "il2cpp/external/baselib/Platforms/Android/Include" 149 | ] 150 | } 151 | } 152 | }, 153 | "version": "0.4.0" 154 | }, 155 | { 156 | "dependency": { 157 | "id": "rapidjson-macros", 158 | "versionRange": "=2.1.0", 159 | "additionalData": { 160 | "headersOnly": true, 161 | "branchName": "version/v2_1_0", 162 | "cmake": false 163 | } 164 | }, 165 | "version": "2.1.0" 166 | }, 167 | { 168 | "dependency": { 169 | "id": "bs-cordl", 170 | "versionRange": "=4004.0.0", 171 | "additionalData": { 172 | "headersOnly": true, 173 | "branchName": "version/v4004_0_0", 174 | "compileOptions": { 175 | "includePaths": [ 176 | "include" 177 | ], 178 | "cppFeatures": [], 179 | "cppFlags": [ 180 | "-DNEED_UNSAFE_CSHARP", 181 | "-fdeclspec", 182 | "-DUNITY_2021", 183 | "-DHAS_CODEGEN", 184 | "-Wno-invalid-offsetof" 185 | ] 186 | } 187 | } 188 | }, 189 | "version": "4004.0.0" 190 | }, 191 | { 192 | "dependency": { 193 | "id": "conditional-dependencies", 194 | "versionRange": "=0.3.0", 195 | "additionalData": { 196 | "headersOnly": true, 197 | "branchName": "version/v0_3_0", 198 | "cmake": false 199 | } 200 | }, 201 | "version": "0.3.0" 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": "scotland2", 221 | "versionRange": "=0.1.6", 222 | "additionalData": { 223 | "soLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.6/libsl2.so", 224 | "debugSoLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.6/debug_libsl2.so", 225 | "overrideSoName": "libsl2.so", 226 | "branchName": "version/v0_1_6" 227 | } 228 | }, 229 | "version": "0.1.6" 230 | }, 231 | { 232 | "dependency": { 233 | "id": "tinyxml2", 234 | "versionRange": "=10.0.0", 235 | "additionalData": { 236 | "soLink": "https://github.com/MillzyDev/NDK-tinyxml2/releases/download/v10.0.0/libtinyxml2.so", 237 | "debugSoLink": "https://github.com/MillzyDev/NDK-tinyxml2/releases/download/v10.0.0/debug_libtinyxml2.so", 238 | "overrideSoName": "libtinyxml2.so", 239 | "modLink": "https://github.com/MillzyDev/NDK-tinyxml2/releases/download/v10.0.0/tinyxml2.qmod", 240 | "branchName": "version/v10_0_0", 241 | "cmake": true 242 | } 243 | }, 244 | "version": "10.0.0" 245 | }, 246 | { 247 | "dependency": { 248 | "id": "fmt", 249 | "versionRange": "=11.0.2", 250 | "additionalData": { 251 | "headersOnly": true, 252 | "branchName": "version/v11_0_2", 253 | "compileOptions": { 254 | "systemIncludes": [ 255 | "fmt/include/" 256 | ], 257 | "cppFlags": [ 258 | "-DFMT_HEADER_ONLY" 259 | ] 260 | } 261 | } 262 | }, 263 | "version": "11.0.2" 264 | } 265 | ] 266 | } -------------------------------------------------------------------------------- /restart-game.ps1: -------------------------------------------------------------------------------- 1 | adb shell am force-stop com.beatgames.beatsaber 2 | adb shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity -------------------------------------------------------------------------------- /rl-notes.md: -------------------------------------------------------------------------------- 1 | **Features:** 2 | - Option to switch between 12/24 Hour format. 3 | - Option to display seconds. 4 | - Option to hide clock in song, "No Text And HUD" will do the same. 5 | - Option to change font size (default: 3.5). 6 | - Option to show Headset Battery charge Indicator, will change color according to charge, off by default. 7 | - Option to Rainbowify the Clock. 8 | - Option to change Clock Color, (unless rainbowify is on). 9 | - Option to choose between the Clock being at the Top or the Bottom. 10 | - Option to hide the Clock in replays. 11 | 12 | __**Changelog:**__ 13 | - Update to 1.40.4_5283 14 | - Option to hide clock in a replay 15 | - Automatically hide the clock when rendering a replay 16 | -------------------------------------------------------------------------------- /shared/ClockUpdater.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TMPro/TextMeshProUGUI.hpp" // Text Stuff. 3 | #include "UnityEngine/GameObject.hpp" // For GameObject Stuff 4 | #include "UnityEngine/Gradient.hpp" 5 | #include "UnityEngine/MonoBehaviour.hpp" 6 | #include "UnityEngine/Transform.hpp" // For Transform stuff 7 | #include "custom-types/shared/macros.hpp" 8 | #include 9 | #include 10 | 11 | using namespace UnityEngine; 12 | 13 | DECLARE_CLASS_CODEGEN(ClockMod, ClockUpdater, UnityEngine::MonoBehaviour) { 14 | 15 | DECLARE_INSTANCE_METHOD(void, Start); 16 | DECLARE_INSTANCE_METHOD(void, Update); 17 | 18 | private: 19 | DECLARE_INSTANCE_FIELD(TMPro::TextMeshProUGUI*, text); 20 | DECLARE_INSTANCE_FIELD(UnityEngine::Transform*, clockParent); 21 | DECLARE_INSTANCE_FIELD(UnityEngine::Gradient*, batteryGradient); 22 | double time_counter = 0; 23 | 24 | clock_t this_time = clock(); 25 | clock_t last_time = this_time; 26 | 27 | time_t rawtime; 28 | struct tm* timeinfo; 29 | 30 | static ClockUpdater* instance; 31 | 32 | std::string _message; 33 | int messageShowing = 0; 34 | 35 | public: 36 | const time_t getRawTime() const; 37 | struct tm* getTimeInfo(); 38 | struct tm* getTimeInfoUTC(); 39 | TMPro::TextMeshProUGUI* getTextMesh(); 40 | void SetColor(UnityEngine::Color color); 41 | static ClockUpdater* getInstance(); 42 | static std::string getTimeFormat(); 43 | void ShowMessage(std::string message, int duration = 4); 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /shared/ColorUtility.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "UnityEngine/Color.hpp" 3 | #include "UnityEngine/Color32.hpp" 4 | 5 | using namespace UnityEngine; 6 | 7 | namespace ClockMod { 8 | class ColorUtility { 9 | ColorUtility() = delete; 10 | public: 11 | static constexpr uint8_t conv(float colorVal) { 12 | return std::clamp(static_cast(round(colorVal * 255.0f)), 0, UINT8_MAX); 13 | } 14 | 15 | static std::string ToHtmlStringRGB(Color color) { 16 | Color32 color2(0, conv(color.r), conv(color.g), conv(color.b), 1); 17 | return fmt::format("#{0:02x}{1:02x}{2:02x}", color2.r, color2.g, color2.b); 18 | } 19 | 20 | }; 21 | } -------------------------------------------------------------------------------- /src/ClockUpdater.cpp: -------------------------------------------------------------------------------- 1 | #include "main.hpp" // Well the main.hpp what else, this wouldn't be the same without. 2 | 3 | #include "ClockModConfig.hpp" // Just the Config 4 | #include "ClockUpdater.hpp" // For the ClockUpdater, like the stuff you see down there 5 | #include "ClockValues.hpp" 6 | #include "ColorUtility.hpp" 7 | #include "RainbowClock.hpp" // Where the magic stuff is that makes the Clock Rainbowy (is that actually a word?) 8 | 9 | #include "GlobalNamespace/OVRPlugin.hpp" // Where I get the Battery Percentage from as float 10 | #include "UnityEngine/BatteryStatus.hpp" 11 | #include "UnityEngine/GradientColorKey.hpp" 12 | #include "UnityEngine/SystemInfo.hpp" 13 | 14 | using namespace UnityEngine; 15 | using namespace TMPro; 16 | 17 | #if !defined(DEFINE_TYPE) 18 | #error Custom-types macro missing, make sure you have ran: 'qpm restore' and that you have a compatible version of custom-types 19 | #endif 20 | 21 | DEFINE_TYPE(ClockMod, ClockUpdater); 22 | 23 | namespace ClockMod { 24 | std::string ClockUpdater::getTimeFormat() { 25 | std::string TFormat; 26 | int switch_int = (int)getModConfig().TwelveToggle.GetValue() + ((int)getModConfig().SecToggle.GetValue() * 2); 27 | switch (switch_int) { 28 | case 0: 29 | TFormat = "%OH:%OM"; // Time in 24 Hour Format without Seconds 30 | break; 31 | case 1: 32 | TFormat = "%Ol:%OM %p"; // Time in 12 Hour Format without Seconds 33 | break; 34 | case 2: 35 | TFormat = "%OH:%OM:%OS"; // Time in 24 Hour Format with Seconds 36 | break; 37 | case 3: 38 | TFormat = "%Ol:%OM:%OS %p"; // Time in 12 Hour Format with Seconds 39 | break; 40 | default: 41 | logger().warn("Using default formatting!"); 42 | TFormat = "%EX"; // Time in 24 Hour Format without Seconds 43 | break; 44 | } 45 | return TFormat; 46 | } 47 | 48 | ClockUpdater* ClockUpdater::instance; 49 | // Function for getting time. Checks config Settings for 12/24 Hour time and if Show Seconds is toggled on or off. 50 | std::string getTimeString(struct tm* timeinfo) { 51 | char time[20]; 52 | strftime(time, sizeof(time), ClockUpdater::getTimeFormat().c_str(), timeinfo); 53 | return time; 54 | } 55 | 56 | // New Battery Percentage Formatting 57 | std::string getBatteryString(float level, UnityEngine::BatteryStatus status, ClockMod::ClockUpdater* instance) 58 | { 59 | std::string percent = fmt::format("{}%", (int)(level * 100)); 60 | if (!(instance && instance->batteryGradient)) { 61 | instance->batteryGradient = UnityEngine::Gradient::New_ctor(); 62 | instance->batteryGradient->set_colorKeys( 63 | { 64 | // Red 65 | UnityEngine::GradientColorKey({1, 0, 0, 1}, 0.0f), 66 | // Orange 67 | UnityEngine::GradientColorKey({1, 0.53 , 0, 1}, 0.35f), 68 | // Yellow 69 | UnityEngine::GradientColorKey({1, 0.84, 0, 1}, 0.5f), 70 | // YellowGreen 71 | UnityEngine::GradientColorKey({0.6f, 0.8f , 0.14, 1}, 0.75f), 72 | // Green 73 | UnityEngine::GradientColorKey({0, 1, 0, 1}, 1.0f) 74 | } 75 | ); 76 | } 77 | 78 | if (status == UnityEngine::BatteryStatus::Charging || 79 | status == UnityEngine::BatteryStatus::Full || 80 | (status != UnityEngine::BatteryStatus::NotCharging && status != UnityEngine::BatteryStatus::Discharging)) { 81 | // return fmt::format("%s", percent.c_str()); 82 | return fmt::format("{}", percent.c_str()); 83 | } 84 | UnityEngine::Color color = instance->batteryGradient->Evaluate(level); 85 | std::string colorStr = ClockMod::ColorUtility::ToHtmlStringRGB(color); 86 | return fmt::format("{}", colorStr.c_str(), percent.c_str()); 87 | } 88 | /* 89 | std::string RainbowClock::rainbowify(std::string input) { 90 | for(char& c : input) { 91 | std::string result 92 | result += fmt::format("%c", colors[rainbowIndex].c_str(), c); 93 | rainbowIndex++; 94 | rainbowIndex %= colors.size(); 95 | } 96 | return result; 97 | } 98 | */ 99 | 100 | // /* 101 | // RGB Clock gives more FPS, Gamers will love that. 102 | std::string RainbowClock::rainbowify(std::string input) 103 | { 104 | std::string result; 105 | 106 | for (auto c : input) 107 | { 108 | result += fmt::format("{}", colors[rainbowIndex].c_str(), c); 109 | rainbowIndex++; 110 | rainbowIndex %= colors.size(); 111 | } 112 | int addValue = (colors.size() - 1) - input.length(); 113 | if (input.length() < 10) { 114 | rainbowIndex += addValue; 115 | if (rainbowIndex > (colors.size() - 1)) rainbowIndex -= colors.size(); 116 | } 117 | return result; 118 | } 119 | // */ 120 | 121 | // Function for updating the Clock Position, temp 122 | void SetClockPos(UnityEngine::Transform* ClockParent, TMPro::TextMeshProUGUI* text, UnityEngine::Vector3 Pos, UnityEngine::Vector3 Angle, UnityEngine::Vector3 Scale) { 123 | ClockParent->set_position(Pos); 124 | text->get_transform()->set_localEulerAngles(Angle); 125 | text->get_transform()->set_localScale(Scale); 126 | } 127 | 128 | #define NUM_SECONDS 0.25 129 | 130 | void ClockUpdater::Start() { 131 | //JNIEnv* javaEnv = Modloader::getJni(); 132 | 133 | //jclass clazz = javaEnv->FindClass("java/text/SimpleDateFormat"); 134 | //jmethodID dateMethod = javaEnv->GetMethodID(clazz, "SimpleDateFormat"); 135 | 136 | 137 | text = get_gameObject()->GetComponent(); 138 | clockParent = get_transform()->GetParent(); 139 | getModConfig().ClockColor.AddChangeEvent([this](UnityEngine::Color color) { 140 | if (text) 141 | text->set_color(color); 142 | } 143 | ); 144 | getModConfig().FontSize.AddChangeEvent([this](float value) { 145 | if (text) 146 | text->set_fontSize(value); 147 | } 148 | ); 149 | if (text) { 150 | text->set_color(getModConfig().ClockColor.GetValue()); // Sets the clocks color, will only color in the "-" if rainbowifier is enabled. 151 | text->set_fontSize(getModConfig().FontSize.GetValue()); 152 | // text->set_alignment(TMPro::TextAlignmentOptions::Center); 153 | } 154 | instance = this; 155 | } 156 | 157 | // Updates the Clock. 158 | void ClockUpdater::Update() { 159 | 160 | 161 | // Temp Code for updating Position. 162 | if (Config.IsInSong == false && Config.InMPLobby == false) { 163 | // TODO: Get Position Offset working. Trying to set the Position offset here, messes with the 360/90 Map stuff. 164 | //if (Config.IsInSong == false) { 165 | // Checks if the clock should be at the Top or Bottom 166 | if (getModConfig().ClockPosition.GetValue()) { 167 | // If set to be at the Bottom do this. 168 | auto Pos = ClockPos.MenuPosBottom; 169 | auto Angle = UnityEngine::Vector3(60, 0, 0); 170 | auto Scale = UnityEngine::Vector3(0.6, 0.6, 0.6); 171 | SetClockPos(clockParent, text, Pos, Angle, Scale); 172 | } 173 | else { 174 | // Otherwise it will do this. 175 | // auto Pos = UnityEngine::Vector3(0, -1.7, 5.0); 176 | auto Pos = ClockPos.MenuPosTop; 177 | auto Angle = UnityEngine::Vector3(-10, 0, 0); 178 | auto Scale = UnityEngine::Vector3(1, 1, 1); 179 | SetClockPos(clockParent, text, Pos, Angle, Scale); 180 | } 181 | } 182 | else { // If in MP Lobby or a Song, unset all this. 183 | //clockParent->set_position(UnityEngine::Vector3(0, 0, 0)); 184 | text->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 185 | text->get_transform()->set_localScale(UnityEngine::Vector3(1, 1, 1)); 186 | } 187 | 188 | this_time = clock(); 189 | 190 | time_counter += (double)(this_time - last_time); 191 | 192 | last_time = this_time; 193 | 194 | if (!(time_counter > (double)(NUM_SECONDS * CLOCKS_PER_SEC))) 195 | { 196 | return; 197 | } 198 | time_counter = 0; 199 | 200 | if (getModConfig().InSong.GetValue() || !Config.noTextAndHUD) { 201 | time(&rawtime); 202 | timeinfo = localtime(&rawtime); 203 | //auto clockParent = get_transform(); 204 | 205 | if (timeinfo && timeinfo->tm_mon == 0 && timeinfo->tm_mday == 1 && timeinfo->tm_hour == 0 && timeinfo->tm_min == 0 && timeinfo->tm_sec == 0 && messageShowing == 0) 206 | { 207 | this->ShowMessage("Happy New Year!", 10); 208 | } 209 | 210 | ClockPos.ap1 = (timeinfo && timeinfo->tm_mon == 3 && timeinfo->tm_mday == 1); 211 | 212 | std::string clockresult; 213 | 214 | if (_message.empty()) { 215 | // Gets the time using the function at the top. 216 | clockresult = getTimeString((struct tm*)timeinfo); 217 | } 218 | else { 219 | clockresult = _message; 220 | } 221 | 222 | // Checks, if the clock is set to rainbowify 223 | if (getModConfig().RainbowClock.GetValue()) { 224 | clockresult = RainbowClock::rainbowify(clockresult); 225 | //text->set_color(UnityEngine::Color::HSVToRGB(std::fmod(UnityEngine::Time::get_time() * 0.35f, 1), 1, 1)); 226 | } 227 | 228 | 229 | // Checks, the below condition and if it retunrs true, gets the current Battery Percentage Level and adds it to the clockresult variable. 230 | if (getModConfig().BattToggle.GetValue() && _message.empty()) { 231 | // Gets the Battery Percentage as float 1.00, multiplies it by 100, and then uses the getBatteryString function defined above to format the percentage. 232 | // auto battery = getBatteryString((int)(GlobalNamespace::OVRPlugin::OVRP_1_1_0::ovrp_GetSystemBatteryLevel() * 100)); 233 | auto battery = getBatteryString(UnityEngine::SystemInfo::get_batteryLevel(), UnityEngine::SystemInfo::get_batteryStatus(), this); 234 | 235 | // static float testPercentage = 1.0f; 236 | // auto battery = getBatteryString(testPercentage, this); 237 | // if (testPercentage > 0.0f) testPercentage += -0.01f; 238 | // else testPercentage = 1.0f; 239 | 240 | clockresult += " - "; // Adds - to it with spaces, before and after the - . 241 | clockresult += battery; // Here is where the Battery gets added to the tandb string. 242 | } 243 | 244 | if (!_message.empty() && messageShowing > 0) messageShowing--; 245 | else _message.clear(); 246 | 247 | 248 | // This is where the Text and Clock Position is set. 249 | text->set_text(StringW(clockresult.c_str())); // This sets the Text 250 | //} 251 | } 252 | } 253 | 254 | const time_t ClockUpdater::getRawTime() const { 255 | return rawtime; 256 | } 257 | 258 | struct tm* ClockUpdater::getTimeInfo() { 259 | time_t time = rawtime; 260 | return localtime(&time); 261 | } 262 | 263 | struct tm* ClockUpdater::getTimeInfoUTC() { 264 | time_t time = rawtime; 265 | return gmtime(&time); 266 | } 267 | 268 | TMPro::TextMeshProUGUI* ClockUpdater::getTextMesh() { 269 | return text; 270 | } 271 | 272 | void ClockUpdater::SetColor(UnityEngine::Color color) { 273 | if (text){ 274 | text->set_color(color); 275 | } 276 | } 277 | 278 | ClockUpdater* ClockUpdater::getInstance() { 279 | return instance; 280 | } 281 | 282 | void ClockUpdater::ShowMessage(std::string message, int duration) { 283 | this->messageShowing = duration; 284 | this->_message = message; 285 | } 286 | } -------------------------------------------------------------------------------- /src/ClockViewContoller.cpp: -------------------------------------------------------------------------------- 1 | #include "ClockModConfig.hpp" 2 | #include "ClockUpdater.hpp" 3 | #include "ClockViewController.hpp" 4 | using namespace ClockMod; 5 | 6 | #include "bsml/shared/BSML-Lite/Creation/Buttons.hpp" 7 | #include "bsml/shared/BSML-Lite/Creation/Settings.hpp" 8 | #include "bsml/shared/BSML-Lite/Creation/Text.hpp" 9 | #include "bsml/shared/BSML/Components/ModalColorPicker.hpp" 10 | #include "custom-types/shared/coroutine.hpp" 11 | #include "custom-types/shared/macros.hpp" 12 | 13 | #include "HMUI/CurvedTextMeshPro.hpp" 14 | #include "HMUI/ScrollView.hpp" 15 | #include "HMUI/Touchable.hpp" 16 | #include "HMUI/ViewController.hpp" 17 | #include "System/Collections/IEnumerator.hpp" 18 | #include "TMPro/FontStyles.hpp" 19 | #include "TMPro/TextMeshProUGUI.hpp" // Added for color change 20 | 21 | // Unity engine stuff, very important! 22 | #include "UnityEngine/Canvas.hpp" 23 | #include "UnityEngine/Color.hpp" 24 | #include "UnityEngine/GameObject.hpp" 25 | #include "UnityEngine/WaitForSecondsRealtime.hpp" 26 | 27 | 28 | 29 | using namespace UnityEngine; 30 | using namespace HMUI; 31 | 32 | #if !defined(DEFINE_TYPE) 33 | #error Custom-types macro missing, make sure you have ran: 'qpm restore' and that you have a compatible version of custom-types 34 | #endif 35 | 36 | DEFINE_TYPE(ClockMod, ClockViewController); 37 | 38 | 39 | namespace ClockMod { 40 | 41 | custom_types::Helpers::Coroutine ClockViewController::UpdateTimeText() { 42 | while (SettingsOpen) { 43 | char timeInformation[45]; 44 | strftime(timeInformation, sizeof(timeInformation), "Your Timezone - %Z UTC offset - %z", ClockUpdater::getInstance()->getTimeInfo()); 45 | char UTCtime[40]; 46 | strftime(UTCtime, sizeof(UTCtime), std::string("\r\n Current Time in UTC - " + ClockUpdater::getTimeFormat()).c_str(), ClockUpdater::getInstance()->getTimeInfoUTC()); 47 | //strftime(UTCtime, sizeof(UTCtime), std::string("\r\n Current Time in UTC - " + ClockUpdater::getTimeFormat()).c_str(), gmtime(ClockUpdater::getInstance()->getRawTime())); 48 | if (TimeInfo && SettingsOpen) 49 | TimeInfo->set_text(std::string(timeInformation) + UTCtime); 50 | co_yield reinterpret_cast(UnityEngine::WaitForSecondsRealtime::New_ctor(0.1)); 51 | } 52 | co_return; 53 | } 54 | 55 | 56 | void ClockViewController::DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { 57 | SettingsOpen = true; 58 | if (firstActivation) { 59 | // Config.InSettings = true; 60 | get_gameObject()->AddComponent(); 61 | GameObject* container = BSML::Lite::CreateScrollableSettingsContainer(get_transform()); 62 | Transform* parent = container->get_transform(); 63 | 64 | ClockUpdater* instance = ClockMod::ClockUpdater::getInstance(); 65 | if (instance) { 66 | char timeInformation[45]; 67 | std::string timeFormat = "Your Timezone - %Z\nUTC offset - %z"; 68 | strftime(timeInformation, sizeof(timeInformation), timeFormat.c_str(), instance->getTimeInfo()); 69 | // We have to specify sizeDelta here otherwise things will overlap 70 | TimeInfo = BSML::Lite::CreateText(parent, std::string(timeInformation), TMPro::FontStyles::Normal, {0,0}, {0,15}); 71 | // TimeInfo = BSML::Lite::CreateText(parent, std::string(timeInformation), TMPro::FontStyles::Normal); 72 | 73 | ColorPicker = BSML::Lite::CreateColorPickerModal(parent, getModConfig().ClockColor.GetName(), getModConfig().ClockColor.GetValue(), 74 | [instance](UnityEngine::Color value) { 75 | getModConfig().ClockColor.SetValue(value); 76 | }, 77 | [instance] { 78 | instance->SetColor(getModConfig().ClockColor.GetValue()); 79 | }, 80 | [instance](UnityEngine::Color value) { 81 | instance->SetColor(value); 82 | } 83 | ); 84 | } 85 | 86 | AddConfigValueToggle(parent, getModConfig().InSong); 87 | AddConfigValueToggle(parent, getModConfig().InReplay); 88 | AddConfigValueToggle(parent, getModConfig().TwelveToggle); 89 | AddConfigValueToggle(parent, getModConfig().SecToggle); 90 | AddConfigValueToggle(parent, getModConfig().BattToggle); 91 | AddConfigValueToggle(parent, getModConfig().RainbowClock); 92 | AddConfigValueIncrementFloat(parent, getModConfig().FontSize, 1.0f, 0.1f, 1.0f, 5.0f); 93 | AddConfigValueToggle(parent, getModConfig().ClockPosition); 94 | 95 | // BeatSaberUI::AddHoverHint(AddConfigValueIncrementFloat(parent, getModConfig().ClockXOffset, 1, 0.1f, -10.0f, 10.0f)->get_gameObject(), "Offsets the X (Left/Right) Position of the Clock"); 96 | // BeatSaberUI::AddHoverHint(AddConfigValueIncrementFloat(parent, getModConfig().ClockYOffset, 1, 0.1f, -10.0f, 10.0f)->get_gameObject(), "Offsets the Y (Up/Down) Position of the Clock"); 97 | // BeatSaberUI::AddHoverHint(AddConfigValueIncrementFloat(parent, getModConfig().ClockZOffset, 1, 0.1f, -10.0f, 10.0f)->get_gameObject(), "Offsets the Z (Forward/Backward) Position of the Clock"); 98 | 99 | //BeatSaberUI::CreateColorPicker(parent, getModConfig().ClockColor.GetName(), getModConfig().ClockColor.GetValue(), 100 | // [](UnityEngine::Color value, GlobalNamespace::ColorChangeUIEventType eventType) { 101 | // getModConfig().ClockColor.SetValue(value); 102 | // }); 103 | 104 | if (ColorPicker) 105 | BSML::Lite::CreateUIButton(parent, "Change ClockColor", [this] { 106 | ColorPicker->modalView->Show(); 107 | } 108 | ); 109 | 110 | } 111 | StartCoroutine(custom_types::Helpers::CoroutineHelper::New(UpdateTimeText())); 112 | } 113 | void ClockViewController::DidDeactivate(bool removedFromHierarchy, bool systemScreenDisabling) { 114 | SettingsOpen = false; 115 | StopAllCoroutines(); 116 | //Config.InSettings = false; 117 | } 118 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "GlobalNamespace/AudioTimeSyncController.hpp" 2 | #include "GlobalNamespace/BeatmapCharacteristicSO.hpp" // --------------------- For checking the characteristic 360/90. 3 | #include "GlobalNamespace/BeatmapKey.hpp" 4 | #include "GlobalNamespace/CampaignFlowCoordinator.hpp" // --------------------- For setting clock in Campaign back to normal 5 | #include "GlobalNamespace/FlyingGameHUDRotation.hpp" // ----------------------- Take rotation from this instead lol 6 | #include "GlobalNamespace/LobbySetupViewController.hpp" // -------------------- NEW: For 1.16.4 7 | #include "GlobalNamespace/MainMenuViewController.hpp" 8 | #include "GlobalNamespace/MultiplayerLevelScenesTransitionSetupDataSO.hpp" // - For checking it it's an MP Song 9 | #include "GlobalNamespace/MultiplayerLobbyController.hpp" 10 | #include "GlobalNamespace/MultiplayerResultsViewController.hpp" 11 | #include "GlobalNamespace/OVRPlugin.hpp" 12 | #include "GlobalNamespace/PartyFreePlayFlowCoordinator.hpp" 13 | #include "GlobalNamespace/PauseMenuManager.hpp" 14 | #include "GlobalNamespace/PlayerData.hpp" // ---------------------------------- For checking if noTextandHUDs is enabled 15 | #include "GlobalNamespace/PlayerDataModel.hpp" // ----------------------------- For checking if noTextandHUDs is enabled 16 | #include "GlobalNamespace/PlayerSpecificSettings.hpp" 17 | #include "GlobalNamespace/PlayerSpecificSettings.hpp" // ---------------------- For checking if noTextandHUDs is enabled 18 | #include "GlobalNamespace/SoloFreePlayFlowCoordinator.hpp" 19 | #include "GlobalNamespace/StandardLevelScenesTransitionSetupDataSO.hpp" // ---- For checking if it's a 360/90 Map 20 | using namespace GlobalNamespace; 21 | 22 | #include "TMPro/FontStyles.hpp" 23 | #include "TMPro/TMP_Text.hpp" 24 | #include "TMPro/TextMeshPro.hpp" 25 | #include "TMPro/TextMeshProUGUI.hpp" 26 | using namespace TMPro; 27 | 28 | #include "UnityEngine/Canvas.hpp" 29 | #include "UnityEngine/CanvasRenderer.hpp" 30 | #include "UnityEngine/CanvasRenderer.hpp" 31 | #include "UnityEngine/GameObject.hpp" 32 | #include "UnityEngine/MonoBehaviour.hpp" 33 | #include "UnityEngine/Quaternion.hpp" 34 | #include "UnityEngine/RenderMode.hpp" 35 | #include "UnityEngine/SceneManagement/Scene.hpp" 36 | #include "UnityEngine/TextAnchor.hpp" 37 | #include "UnityEngine/Vector2.hpp" 38 | #include "UnityEngine/Vector3.hpp" 39 | using namespace UnityEngine; 40 | 41 | #include "UnityEngine/UI/CanvasScaler.hpp" 42 | #include "UnityEngine/UI/LayoutElement.hpp" 43 | using namespace UnityEngine::UI; 44 | 45 | #include "HMUI/CurvedCanvasSettings.hpp" 46 | using namespace HMUI; 47 | 48 | #include "beatsaber-hook/shared/utils/hooking.hpp" 49 | #include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" 50 | #include "bsml/shared/BSML-Lite/Creation/Layout.hpp" 51 | #include "bsml/shared/BSML-Lite/Creation/Text.hpp" 52 | #include "bsml/shared/BSML.hpp" 53 | #include "conditional-dependencies/shared/main.hpp" 54 | #include "custom-types/shared/register.hpp" 55 | #include "scotland2/shared/loader.hpp" 56 | 57 | #include "ClockModConfig.hpp" 58 | #include "ClockUpdater.hpp" 59 | #include "ClockValues.hpp" 60 | #include "ClockViewController.hpp" 61 | #include "main.hpp" 62 | using namespace ClockMod; 63 | 64 | 65 | 66 | 67 | // Clock Default Position and other stuff stored there 68 | Config_t Config; 69 | 70 | // Clock Positions 71 | ClockPos_t ClockPos; 72 | 73 | #if !defined(MAKE_HOOK_MATCH) 74 | #error No supported HOOK macro found, make sure that you have ran: 'qpm-rust restore' and that you have a compatible version of bs-hook 75 | #endif 76 | 77 | inline modloader::ModInfo modInfo = {MOD_ID, VERSION, 0}; // Stores the ID and version of our mod, and is sent to the modloader upon startup 78 | 79 | // Returns a logger, useful for printing debug messages 80 | Paper::ConstLoggerContext<9UL> logger() { 81 | static auto fastContext = Paper::Logger::WithContext<"ClockMod">(); 82 | return fastContext; 83 | } 84 | 85 | float RotateSongX; 86 | 87 | bool ClockModInit; 88 | 89 | UnityEngine::Canvas* canvas; 90 | UnityEngine::UI::VerticalLayoutGroup* layout; 91 | 92 | // Function that sets the ClockPosition for Multiplayer Lobbies. 93 | void MPLobbyClockPos(float MLobbyVCPosY) { 94 | layout->get_transform()->set_position(UnityEngine::Vector3(0, MLobbyVCPosY-1, 1.75)); 95 | layout->get_transform()->set_localScale(UnityEngine::Vector3(0.35, 0.35, 0.35)); 96 | layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(UnityEngine::Vector3(0, 0, 0)); 97 | } 98 | 99 | MAKE_HOOK_MATCH(MainMenuViewController_DidActivate, &MainMenuViewController::DidActivate, void, MainMenuViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) 100 | { 101 | MainMenuViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); 102 | 103 | if (firstActivation && ClockModInit) { 104 | auto canvas_object = UnityEngine::GameObject::New_ctor("ClockCanvas"); 105 | canvas = canvas_object->AddComponent(); 106 | auto canvas_scaler = canvas_object->AddComponent(); 107 | auto canvas_renderer = canvas_object->AddComponent(); 108 | 109 | //canvas_object->AddComponent()->ApplyBackgroundWithAlpha(il2cpp_utils::createcsstr("round-rect-panel"), 0.75f); 110 | 111 | // For reference on adding Button to clock 112 | // UnityEngine::UI::Button* button = QuestUI::BeatSaberUI::CreateUIButton(rectTransform, "", "SettingsButton", il2cpp_utils::MakeDelegate(classof(UnityEngine::Events::UnityAction*), (Il2CppObject*)nullptr, +[](Il2CppObject* obj, UnityEngine::UI::Button* button){})); 113 | //button->get_onClick()->AddListener(il2cpp_utils::MakeDelegate(classof(UnityEngine::Events::UnityAction*), (Il2CppObject*)nullptr, OnClockButtonClick)); 114 | // 115 | // button->get_onClick()->AddListener(il2cpp_utils::MakeDelegate(classof(UnityEngine::Events::UnityAction*), (Il2CppObject*)nullptr, OnClockButtonClick)); 116 | 117 | canvas_object->AddComponent(); 118 | canvas_object->get_transform()->set_rotation(UnityEngine::Quaternion(0, 0, 0, 1)); 119 | canvas_object->get_transform()->set_position(UnityEngine::Vector3(0, 0.5, 0)); // 0, 0.5, 3 120 | canvas_object->get_transform()->set_localScale(UnityEngine::Vector3(0.1, 0.1, 0.1)); 121 | 122 | // This makes sure, the Clock isn't deleted when the scene changes. 123 | Object::DontDestroyOnLoad(canvas_object); 124 | 125 | // Makes the clock visible for every camera in the scene, if there ever should be more than one. 126 | canvas->set_renderMode(UnityEngine::RenderMode::WorldSpace); 127 | 128 | layout = BSML::Lite::CreateVerticalLayoutGroup(canvas_object->get_transform()); 129 | //layout = CreateVerticalLayoutGroup(canvas_object->get_transform()); 130 | auto clock_text = BSML::Lite::CreateText(layout->get_rectTransform(), "", TMPro::FontStyles::Normal); 131 | //auto clock_text = CreateText(layout->get_rectTransform(), ""); 132 | 133 | layout->GetComponent()->set_minWidth(7); 134 | layout->GetComponent()->set_minHeight(80); 135 | layout->set_childAlignment(UnityEngine::TextAnchor::MiddleCenter); 136 | 137 | clock_text->get_gameObject()->AddComponent(); 138 | 139 | // Check all Mod Config Values 140 | 141 | //if (getModConfig().InSong.GetValue()) { logger().debug("InSong true"); } else { logger().debug("InSong false"); } 142 | //if (getModConfig().SecToggle.GetValue()) { logger().debug("SecToggle true"); } else { logger().debug("SecToggle false"); } 143 | //if (getModConfig().FontSize.GetValue()) { logger().debug("FontSize true value %s", getModConfig().FontSize.GetValue()); } else { logger().debug("FontSize false"); } 144 | //if (getModConfig().BattToggle.GetValue()) { logger().debug("BattToggle true"); } else { logger().debug("BattToggle false"); } 145 | //if (getModConfig().TwelveToggle.GetValue()) { logger().debug("TwelveToggle true"); } else { logger().debug("TwelveToggle false"); } 146 | //if (getModConfig().RainbowClock.GetValue()) { logger().debug("RainbowClock true"); } else { logger().debug("RainbowClock false"); } 147 | ////if (getModConfig().ClockColor.GetValue()) { logger().debug("ClockColor true"); } else { logger().debug("ClockColor false"); } 148 | //if (getModConfig().ClockPosition.GetValue()) { logger().debug("ClockPosition bottom"); } else { logger().debug("ClockPosition top"); } 149 | ClockModInit = false; 150 | } 151 | Config.IsInSong = false; 152 | Config.InMPLobby = false; 153 | // Resets Rotation/Position/Scale 154 | layout->get_transform()->set_position(ClockPos.MenuPosTop); 155 | layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(UnityEngine::Vector3(0, 0, 0)); 156 | layout->get_transform()->set_localScale(UnityEngine::Vector3(1, 1, 1)); 157 | layout->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 158 | // Enables the Clock, I mean what did you expect? 159 | canvas->get_gameObject()->SetActive(true); 160 | Config.InMP = false; 161 | } 162 | 163 | // Function for updating the Clock Position 164 | void SetClockPos(UnityEngine::Vector3 Pos, UnityEngine::Vector3 Angle, float ScaleFloat) { 165 | layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(UnityEngine::Vector3(0, 0, 0)); 166 | layout->get_transform()->GetParent()->set_position(UnityEngine::Vector3(0, 0.5, 0)); 167 | layout->get_transform()->set_position(Pos); 168 | if(Config.InRotationMap) layout->get_transform()->set_localEulerAngles(Angle); else layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(Angle); 169 | layout->get_transform()->set_localScale(UnityEngine::Vector3(ScaleFloat, ScaleFloat, ScaleFloat)); 170 | } 171 | 172 | // TODO: Use a different hook, this one is too small and causes issues 173 | MAKE_HOOK_MATCH(AudioTimeSyncController_StartSong, &AudioTimeSyncController::StartSong, void, AudioTimeSyncController* self, float startTimeOffset) { 174 | // Instance of PlayerDataModel the noTextAndHUDs variable specifically 175 | Config.noTextAndHUD = UnityEngine::Object::FindObjectOfType()->playerData->get_playerSpecificSettings()->noTextsAndHuds; 176 | 177 | //float LayoutClockPosX = layout->get_transform()->get_position().x; 178 | //float LayoutClockPosY = layout->get_transform()->get_position().y; 179 | //float LayoutClockPosZ = layout->get_transform()->get_position().z; 180 | //float LayoutClockRotX = layout->get_transform()->get_eulerAngles().x; 181 | //logger().debug("ClockPos before X %f", LayoutClockPosX); 182 | //logger().debug("ClockPos before Y %f", LayoutClockPosY); 183 | //logger().debug("ClockPos before Z %f", LayoutClockPosZ); 184 | //logger().debug("ClockRot before X %f", LayoutClockRotX); 185 | 186 | Config.InMPLobby = false; 187 | Config.IsInSong = true; 188 | 189 | AudioTimeSyncController_StartSong(self, startTimeOffset); 190 | 191 | 192 | bool isInReplay; 193 | static std::optional isInReplayFunc = CondDeps::Find("replay", "IsInReplay"); 194 | if (isInReplayFunc.has_value()) 195 | { 196 | static std::optional isInRenderFunc = CondDeps::Find("replay", "IsInRender "); 197 | bool isInRender = false; 198 | isInReplay = isInReplayFunc.value()(); 199 | if (isInRenderFunc.has_value()) 200 | isInRender = isInRenderFunc.value()(); 201 | logger().info("Found replay mod, check in replay: {}, check in render: {}", isInReplay, isInRender); 202 | Config.noTextAndHUD |= (isInReplay && !getModConfig().InReplay.GetValue()) || isInRender; 203 | } 204 | 205 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 206 | canvas->get_gameObject()->SetActive(false); 207 | logger().info("SetActive false"); 208 | } 209 | // TODO: Tweak values 210 | if (/*Config.InMP ||*/ Config.InRotationMap) { // Checks if in a map with RotationEvents (360/90) or in a MP Song. 211 | if (getModConfig().ClockPosition.GetValue()) { // Then checks if the clock is set to be at the Top or the Bottom 212 | SetClockPos(ClockPos.RotateSongPosDown, ClockPos.RotateSongRotationDown, ClockPos.RotateSongScaleDown); 213 | logger().info("SetPos 360/MP Bottom"); 214 | } 215 | else { 216 | logger().info("SetPos 360/MP Top"); 217 | SetClockPos(ClockPos.RotateSongPosTop, ClockPos.RotateSongRotationTop, ClockPos.RotateSongScaleTop); 218 | 219 | //LayoutClockPosX = layout->get_transform()->get_position().x; 220 | //LayoutClockPosY = layout->get_transform()->get_position().y; 221 | //LayoutClockPosZ = layout->get_transform()->get_position().z; 222 | //logger().debug("ClockPos X %f", LayoutClockPosX); 223 | //logger().debug("ClockPos Y %f", LayoutClockPosY); 224 | //logger().debug("ClockPos Z %f", LayoutClockPosZ); 225 | } 226 | } 227 | else if (getModConfig().ClockPosition.GetValue()) { // When in a normal Map do this. 228 | // If set to be at the Bottom do this. 229 | //auto Pos = UnityEngine::Vector3(0, -4.45, 2); 230 | //auto Angle = UnityEngine::Vector3(45, 0, 0); 231 | //auto Scale = UnityEngine::Vector3(ClockPos.NormalSongScaleDown, ClockPos.NormalSongScaleDown, ClockPos.NormalSongScaleDown); 232 | SetClockPos(ClockPos.NormalSongPosDown, ClockPos.NormalSongRotationDown, ClockPos.NormalSongScaleDown); 233 | //LayoutClockPosX = layout->get_transform()->get_position().x; 234 | //LayoutClockPosY = layout->get_transform()->get_position().y; 235 | //LayoutClockPosZ = layout->get_transform()->get_position().z; 236 | //LayoutClockRotX = layout->get_transform()->get_eulerAngles().x; 237 | //logger().debug("ClockPos after X %f", LayoutClockPosX); 238 | //logger().debug("ClockPos after Y %f", LayoutClockPosY); 239 | //logger().debug("ClockPos after Z %f", LayoutClockPosZ); 240 | //logger().debug("ClockRot after X %f", LayoutClockRotX); 241 | logger().info("In Normal Map set to Bottom"); 242 | } 243 | else { 244 | // Otherwise it will do this. 245 | //auto Pos = UnityEngine::Vector3(0, -1.7, 5.6); 246 | //auto Angle = UnityEngine::Vector3(-10, 0, 0); 247 | //auto Scale = UnityEngine::Vector3(1, 1, 1); 248 | auto pos = ClockPos.NormalSongPosTop; 249 | if (isInReplay) pos.y += 0.4f; 250 | SetClockPos(pos, ClockPos.NormalSongRotationTop, ClockPos.NormalSongScaleTop); 251 | logger().info("In Normal Map set to Top"); 252 | } 253 | } 254 | 255 | //MAKE_HOOK_MATCH(SceneManager_Internal_ActiveSceneChanged, &UnityEngine::SceneManagement::SceneManager::Internal_ActiveSceneChanged, void, UnityEngine::SceneManagement::Scene prevScene, UnityEngine::SceneManagement::Scene nextScene) { 256 | // SceneManager_Internal_ActiveSceneChanged(prevScene, nextScene); 257 | // if (nextScene.IsValid()) { 258 | // std::string sceneName = to_utf8(csstrtostr(nextScene.get_name())); 259 | // logger().info("Scene found: %s", sceneName.data()); 260 | // if (sceneName.find("GameCore") != std::string::npos) { 261 | // // Instance of PlayerDataModel the noTextAndHUDs variable specifically 262 | // Config.noTextAndHUD = UnityEngine::Object::FindObjectOfType()->playerData->playerSpecificSettings->noTextsAndHuds; 263 | // 264 | // float LayoutClockPosX = layout->get_transform()->get_position().x; 265 | // float LayoutClockPosY = layout->get_transform()->get_position().y; 266 | // float LayoutClockPosZ = layout->get_transform()->get_position().z; 267 | // float LayoutClockRotX = layout->get_transform()->get_eulerAngles().x; 268 | // logger().debug("ClockPos before X %f", LayoutClockPosX); 269 | // logger().debug("ClockPos before Y %f", LayoutClockPosY); 270 | // logger().debug("ClockPos before Z %f", LayoutClockPosZ); 271 | // logger().debug("ClockRot before X %f", LayoutClockRotX); 272 | // 273 | // Config.InMPLobby = false; 274 | // Config.IsInSong = true; 275 | // 276 | // if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 277 | // canvas->get_gameObject()->SetActive(false); 278 | // logger().info("SetActive false"); 279 | // } 280 | // // TODO: Tweak values 281 | // if (/*Config.InMP ||*/ Config.InRotationMap) { // Checks if in a map with RotationEvents (360/90) or in a MP Song. 282 | // if (getModConfig().ClockPosition.GetValue()) { // Then checks if the clock is set to be at the Top or the Bottom 283 | // SetClockPos(ClockPos.RotateSongPosDown, ClockPos.RotateSongRotationDown, ClockPos.RotateSongScaleDown); 284 | // logger().debug("SetPos 360/MP Bottom"); 285 | // } 286 | // else { 287 | // logger().debug("SetPos 360/MP Top"); 288 | // SetClockPos(ClockPos.RotateSongPosTop, ClockPos.RotateSongRotationTop, ClockPos.RotateSongScaleTop); 289 | // 290 | // LayoutClockPosX = layout->get_transform()->get_position().x; 291 | // LayoutClockPosY = layout->get_transform()->get_position().y; 292 | // LayoutClockPosZ = layout->get_transform()->get_position().z; 293 | // logger().debug("ClockPos X %f", LayoutClockPosX); 294 | // logger().debug("ClockPos Y %f", LayoutClockPosY); 295 | // logger().debug("ClockPos Z %f", LayoutClockPosZ); 296 | // } 297 | // } 298 | // else if (getModConfig().ClockPosition.GetValue()) { // When in a normal Map do this. 299 | // // If set to be at the Bottom do this. 300 | // //auto Pos = UnityEngine::Vector3(0, -4.45, 2); 301 | // //auto Angle = UnityEngine::Vector3(45, 0, 0); 302 | // //auto Scale = UnityEngine::Vector3(ClockPos.NormalSongScaleDown, ClockPos.NormalSongScaleDown, ClockPos.NormalSongScaleDown); 303 | // SetClockPos(ClockPos.NormalSongPosDown, ClockPos.NormalSongRotationDown, ClockPos.NormalSongScaleDown); 304 | // LayoutClockPosX = layout->get_transform()->get_position().x; 305 | // LayoutClockPosY = layout->get_transform()->get_position().y; 306 | // LayoutClockPosZ = layout->get_transform()->get_position().z; 307 | // LayoutClockRotX = layout->get_transform()->get_eulerAngles().x; 308 | // logger().debug("ClockPos after X %f", LayoutClockPosX); 309 | // logger().debug("ClockPos after Y %f", LayoutClockPosY); 310 | // logger().debug("ClockPos after Z %f", LayoutClockPosZ); 311 | // logger().debug("ClockRot after X %f", LayoutClockRotX); 312 | // //logger().debug("In Normal Map set to Bottom"); 313 | // } 314 | // else { 315 | // // Otherwise it will do this. 316 | // //auto Pos = UnityEngine::Vector3(0, -1.7, 5.6); 317 | // //auto Angle = UnityEngine::Vector3(-10, 0, 0); 318 | // //auto Scale = UnityEngine::Vector3(1, 1, 1); 319 | // SetClockPos(ClockPos.NormalSongPosTop, ClockPos.NormalSongRotationTop, ClockPos.NormalSongScaleTop); 320 | // //logger().debug("In Normal Map set to Top"); 321 | // } 322 | // } 323 | // } 324 | //} 325 | 326 | //MAKE_HOOK_MATCH(AudioTimeSyncController_StopSong, &AudioTimeSyncController::StopSong, void, AudioTimeSyncController* self) { 327 | // AudioTimeSyncController_StopSong(self); 328 | // Config.IsInSong = false; 329 | // layout->get_transform()->set_position(UnityEngine::Vector3(0, Config.ClockY, Config.ClockZ)); 330 | //} 331 | 332 | MAKE_HOOK_MATCH(CampaignFlowCoordinator_DidActivate, &CampaignFlowCoordinator::DidActivate, void, CampaignFlowCoordinator* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { 333 | CampaignFlowCoordinator_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); 334 | 335 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 336 | canvas->get_gameObject()->SetActive(true); 337 | logger().info("SetActive true Campaign"); 338 | } 339 | 340 | Config.IsInSong = false; 341 | Config.InRotationMap = false; 342 | 343 | //layout->get_transform()->set_rotation(UnityEngine::Quaternion(0, 0, 0, 1)); 344 | layout->get_transform()->set_position(ClockPos.MenuPosTop); 345 | layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(UnityEngine::Vector3(0, 0, 0)); 346 | layout->get_transform()->set_localScale(UnityEngine::Vector3(1, 1, 1)); 347 | layout->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 348 | } 349 | 350 | MAKE_HOOK_MATCH(PartyFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate, &PartyFreePlayFlowCoordinator::SinglePlayerLevelSelectionFlowCoordinatorDidActivate, void, PartyFreePlayFlowCoordinator* self, bool firstActivation, bool addedToHierarchy) { 351 | PartyFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate(self, firstActivation, addedToHierarchy); 352 | 353 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 354 | canvas->get_gameObject()->SetActive(true); 355 | logger().info("SetActive true PartyFreePlay"); 356 | } 357 | 358 | Config.IsInSong = false; 359 | Config.InRotationMap = false; 360 | 361 | //layout->get_transform()->set_rotation(UnityEngine::Quaternion(0, 0, 0, 1)); 362 | layout->get_transform()->set_position(ClockPos.MenuPosTop); 363 | layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(UnityEngine::Vector3(0, 0, 0)); 364 | layout->get_transform()->set_localScale(UnityEngine::Vector3(1, 1, 1)); 365 | layout->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 366 | } 367 | 368 | MAKE_HOOK_MATCH(SoloFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate, &SoloFreePlayFlowCoordinator::SinglePlayerLevelSelectionFlowCoordinatorDidActivate, void, SoloFreePlayFlowCoordinator* self, bool firstActivation, bool addedToHierarchy) { 369 | SoloFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate(self, firstActivation, addedToHierarchy); 370 | 371 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 372 | canvas->get_gameObject()->SetActive(true); 373 | logger().info("SetActive true SoloFreePlay"); 374 | } 375 | 376 | Config.IsInSong = false; 377 | Config.InRotationMap = false; 378 | // layout->get_transform()->set_position(UnityEngine::Vector3(0, Config.ClockY, Config.ClockZ)); 379 | //layout->get_transform()->(UnityEngine::Quaternion(0, 0, 0, 1)); 380 | layout->get_transform()->set_position(ClockPos.MenuPosTop); 381 | layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(UnityEngine::Vector3(0, 0, 0)); 382 | layout->get_transform()->set_localScale(UnityEngine::Vector3(1, 1, 1)); 383 | layout->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 384 | } 385 | 386 | MAKE_HOOK_MATCH(PauseMenuManager_ShowMenu, &PauseMenuManager::ShowMenu, void, PauseMenuManager* self) { 387 | PauseMenuManager_ShowMenu(self); 388 | 389 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 390 | canvas->get_gameObject()->SetActive(true); 391 | logger().info("SetActive true PauseMenu"); 392 | } 393 | } 394 | 395 | MAKE_HOOK_MATCH(PauseMenuManager_StartResumeAnimation, &PauseMenuManager::StartResumeAnimation, void, PauseMenuManager* self) { 396 | PauseMenuManager_StartResumeAnimation(self); 397 | 398 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 399 | canvas->get_gameObject()->SetActive(false); 400 | logger().info("SetActive false PauseMenu"); 401 | } 402 | } 403 | 404 | // TODO: Use the below information wisely 405 | // Check the beatmap characteristic and set the Config.InRotationMap to true if it requires 360 Movement. 406 | MAKE_HOOK_MATCH(StandardLevelScenesTransitionSetupDataSO_InitAndSetupScenes, &StandardLevelScenesTransitionSetupDataSO::InitAndSetupScenes, void, StandardLevelScenesTransitionSetupDataSO* self, PlayerSpecificSettings* playerSpecificSettings, StringW backButtonText, bool startPaused) { 407 | StandardLevelScenesTransitionSetupDataSO_InitAndSetupScenes(self, playerSpecificSettings, backButtonText, startPaused); 408 | 409 | if (self->beatmapKey.beatmapCharacteristic->containsRotationEvents) { 410 | logger().debug("Loaded 360/90 Map"); 411 | Config.InRotationMap = true; 412 | } 413 | else { 414 | logger().debug("Loaded Normal Map"); 415 | Config.InRotationMap = false; 416 | } 417 | } 418 | 419 | MAKE_HOOK_MATCH(FlyingGameHUDRotation_FixedUpdate, &FlyingGameHUDRotation::FixedUpdate, void, GlobalNamespace::FlyingGameHUDRotation* self) { 420 | layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(UnityEngine::Vector3(0, self->_yAngle, 0)); 421 | FlyingGameHUDRotation_FixedUpdate(self); 422 | } 423 | 424 | 425 | MAKE_HOOK_MATCH(LobbySetupViewController_DidActivate, &LobbySetupViewController::DidActivate, void, LobbySetupViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { 426 | 427 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 428 | canvas->get_gameObject()->SetActive(true); 429 | logger().info("SetActive true Multiplayer Lobby"); 430 | } 431 | layout->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 432 | 433 | auto MLobbyVCPosY = self->get_transform()->get_position().y; 434 | Config.InMPLobby = true; 435 | Config.IsInSong = false; 436 | Config.InMP = true; 437 | Config.InRotationMap = false; 438 | // logger().debug("%g", MLobbyVCPosY); 439 | MPLobbyClockPos(MLobbyVCPosY); 440 | if (Config.InMP == true) { logger().debug("CLobby, InMP is True"); } 441 | 442 | LobbySetupViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); 443 | } 444 | 445 | MAKE_HOOK_MATCH(MultiplayerResultsViewController_DidActivate, &MultiplayerResultsViewController::DidActivate, void, MultiplayerResultsViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { 446 | MultiplayerResultsViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); 447 | 448 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 449 | canvas->get_gameObject()->SetActive(true); 450 | logger().info("SetActive true MPResultsVC"); 451 | } 452 | layout->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 453 | 454 | auto MLobbyVCPosY = self->get_transform()->get_position().y; 455 | Config.InMPLobby = true; 456 | Config.IsInSong = false; 457 | Config.InMP = true; 458 | Config.InRotationMap = false; 459 | // logger().debug("%g", MLobbyVCPosY); 460 | MPLobbyClockPos(MLobbyVCPosY); 461 | if (Config.InMP) { logger().debug("MRVC, InMP is True"); } 462 | } 463 | 464 | MAKE_HOOK_MATCH(MultiplayerResultsViewController_Init, &MultiplayerResultsViewController::Init, void, MultiplayerResultsViewController* self, MultiplayerResultsData* multiplayerResultsData, BeatmapKey beatmapKey, bool showBackToLobbyButton, bool showBackToMenuButton) 465 | { 466 | MultiplayerResultsViewController_Init(self, multiplayerResultsData, beatmapKey, showBackToLobbyButton, showBackToMenuButton); 467 | if (!getModConfig().InSong.GetValue() || Config.noTextAndHUD) { 468 | canvas->get_gameObject()->SetActive(true); 469 | logger().info("SetActive true MPResultsVC"); 470 | } 471 | layout->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 472 | 473 | auto MLobbyVCPosY = self->get_transform()->get_position().y; 474 | Config.InMPLobby = true; 475 | Config.IsInSong = false; 476 | Config.InMP = true; 477 | Config.InRotationMap = false; 478 | MPLobbyClockPos(MLobbyVCPosY); 479 | if (Config.InMP) { logger().debug("MRVC, InMP is True"); } 480 | } 481 | MAKE_HOOK_MATCH(MultiplayerLobbyController_DeactivateMultiplayerLobby, &MultiplayerLobbyController::DeactivateMultiplayerLobby, void, MultiplayerLobbyController* self) { 482 | MultiplayerLobbyController_DeactivateMultiplayerLobby(self); 483 | 484 | Config.InMPLobby = false; 485 | Config.IsInSong = false; 486 | //layout->get_transform()->set_rotation(UnityEngine::Quaternion(0, 0, 0, 1)); 487 | layout->get_transform()->set_position(ClockPos.MenuPosTop); 488 | layout->get_gameObject()->get_transform()->GetParent()->set_eulerAngles(UnityEngine::Vector3(0, 0, 0)); 489 | layout->get_transform()->set_localScale(UnityEngine::Vector3(1, 1, 1)); 490 | layout->get_transform()->set_localEulerAngles(UnityEngine::Vector3(0, 0, 0)); 491 | } 492 | 493 | #include 494 | #include "GlobalNamespace/ResultsViewController.hpp" 495 | #include "GlobalNamespace/LevelCompletionResults.hpp" 496 | MAKE_HOOK_MATCH(ResultsViewController_DidActivate, &ResultsViewController::DidActivate, void, ResultsViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { 497 | ResultsViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); 498 | std::vector failTexts({ "Get Better", "fail", "lol", "learn2play", "no skills", "get skills", "loser", "hit bloq", "no comment", "you failed", "can you even play" }); 499 | if (ClockPos.ap1 && self->_levelCompletionResults->levelEndStateType == LevelCompletionResults::LevelEndStateType::Failed) { 500 | ClockUpdater* clockUpdater = ClockUpdater::getInstance(); 501 | if (clockUpdater) { 502 | std::random_device random_device; 503 | std::mt19937 engine{ random_device() }; 504 | std::uniform_int_distribution dist(0, failTexts.size() - 1); 505 | clockUpdater->ShowMessage(failTexts.at(dist(engine))); 506 | } 507 | } 508 | } 509 | 510 | 511 | // Called at the early stages of game loading 512 | MOD_EXPORT void setup(CModInfo *info) { 513 | *info = modInfo.to_c(); 514 | 515 | getModConfig().Init(modInfo); 516 | 517 | Paper::Logger::RegisterFileContextId(logger().tag); 518 | 519 | ClockModInit = true; 520 | 521 | logger().info("Completed ClockMod setup!"); 522 | } 523 | 524 | // Called later on in the game loading - a good time to install function hooks 525 | MOD_EXPORT void late_load() { 526 | il2cpp_functions::Init(); 527 | BSML::Init(); 528 | 529 | logger().debug("Init ModConfig..."); 530 | 531 | //Init/Load Config 532 | getModConfig().Init(modInfo); 533 | 534 | logger().debug("Init ModConfig...Done"); 535 | 536 | logger().debug("CheckTime..."); 537 | time_t rawtime; 538 | time(&rawtime); 539 | struct tm* timeinfo = localtime(&rawtime); 540 | // Months start at 0 so 0 = January while days start at 1 541 | ClockPos.ap1 = (timeinfo && timeinfo->tm_mon == 3 && timeinfo->tm_mday == 1); 542 | logger().debug("Year is: %d Day is: %d Month is: %d time is %d:%d:%d", 543 | timeinfo->tm_year, timeinfo->tm_mday, timeinfo->tm_mon + 1, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); 544 | 545 | logger().debug("CheckTime...Done"); 546 | 547 | logger().info("Installing Clockmod hooks..."); 548 | 549 | auto hkLog = logger(); 550 | 551 | custom_types::Register::AutoRegister(); 552 | BSML::Register::RegisterSettingsMenu("ClockMod"); 553 | 554 | if (timeinfo && (timeinfo->tm_mon == 2 && timeinfo->tm_mday == 31) || (timeinfo->tm_mon == 3 && timeinfo->tm_mday == 1)) { 555 | INSTALL_HOOK(hkLog, ResultsViewController_DidActivate); 556 | } 557 | 558 | INSTALL_HOOK(hkLog, MainMenuViewController_DidActivate); 559 | INSTALL_HOOK(hkLog, CampaignFlowCoordinator_DidActivate); 560 | INSTALL_HOOK(hkLog, AudioTimeSyncController_StartSong); 561 | INSTALL_HOOK(hkLog, SoloFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate); 562 | INSTALL_HOOK(hkLog, FlyingGameHUDRotation_FixedUpdate); 563 | INSTALL_HOOK(hkLog, PartyFreePlayFlowCoordinator_SinglePlayerLevelSelectionFlowCoordinatorDidActivate); 564 | INSTALL_HOOK(hkLog, PauseMenuManager_ShowMenu); 565 | INSTALL_HOOK(hkLog, PauseMenuManager_StartResumeAnimation); 566 | INSTALL_HOOK(hkLog, StandardLevelScenesTransitionSetupDataSO_InitAndSetupScenes); 567 | INSTALL_HOOK(hkLog, LobbySetupViewController_DidActivate); 568 | INSTALL_HOOK(hkLog, MultiplayerLobbyController_DeactivateMultiplayerLobby); 569 | INSTALL_HOOK(hkLog, MultiplayerResultsViewController_DidActivate); 570 | logger().info("Installed all ClockMod hooks!"); 571 | } -------------------------------------------------------------------------------- /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] $trim, 19 | 20 | [Parameter(Mandatory=$false)] 21 | [Switch] $excludeHeader 22 | ) 23 | 24 | if ($help -eq $true) { 25 | if ($excludeHeader -eq $false) { 26 | Write-Output "`"Start-Logging`" - Logs Beat Saber using `"adb logcat`"" 27 | Write-Output "`n-- Arguments --`n" 28 | } 29 | 30 | Write-Output "-Self `t`t Only logs from your mod and crashes" 31 | Write-Output "-All `t`t Logs everything, including from non Beat Saber processes" 32 | Write-Output "-Custom `t Specify a specific logging pattern, e.g `"custom-types|questui`"" 33 | Write-Output "`t`t NOTE: The paterent `"AndriodRuntime|CRASH`" is always appended to a custom pattern" 34 | Write-Output "-Trim `t Removes time, level, and mod from the start of lines`"" 35 | Write-Output "-File `t`t Saves the output of the log to the file name given" 36 | 37 | exit 38 | } 39 | 40 | $bspid = adb shell pidof com.beatgames.beatsaber 41 | $command = "adb logcat " 42 | 43 | if ($all -eq $false) { 44 | $loops = 0 45 | while ([string]::IsNullOrEmpty($bspid) -and $loops -lt 3) { 46 | Start-Sleep -Milliseconds 100 47 | $bspid = adb shell pidof com.beatgames.beatsaber 48 | $loops += 1 49 | } 50 | 51 | if ([string]::IsNullOrEmpty($bspid)) { 52 | Write-Output "Could not connect to adb, exiting..." 53 | exit 1 54 | } 55 | 56 | $command += "--pid $bspid" 57 | } 58 | 59 | if ($all -eq $false) { 60 | $pattern = "(" 61 | if ($self -eq $true) { 62 | $modID = (Get-Content "./mod.json" -Raw | ConvertFrom-Json).id 63 | $pattern += "$modID|" 64 | } 65 | if (![string]::IsNullOrEmpty($custom)) { 66 | $pattern += "$custom|" 67 | } 68 | if ($pattern -eq "(") { 69 | $pattern = "(QuestHook|modloader|" 70 | } 71 | $pattern += "AndroidRuntime|CRASH)" 72 | $command += " | Select-String -pattern `"$pattern`"" 73 | } 74 | 75 | if ($trim -eq $true) { 76 | $command += " | % {`$_ -replace `"^(?(?=.*\]:).*?\]: |.*?: )`", `"`"}" 77 | } 78 | 79 | if (![string]::IsNullOrEmpty($file)) { 80 | $command += " | Out-File -FilePath $PSScriptRoot\$file" 81 | } 82 | 83 | Write-Output "Logging using Command `"$command`"" 84 | Invoke-Expression $command -------------------------------------------------------------------------------- /validate-modjson.ps1: -------------------------------------------------------------------------------- 1 | $mod = "./mod.json" 2 | 3 | if (-not (Test-Path -Path $mod)) { 4 | if (Test-Path -Path ".\mod.template.json") { 5 | & qpm qmod build 6 | if ($LASTEXITCODE -ne 0) { 7 | exit $LASTEXITCODE 8 | } 9 | } 10 | else { 11 | Write-Output "Error: mod.json and mod.template.json were not present" 12 | exit 1 13 | } 14 | } 15 | 16 | $psVersion = $PSVersionTable.PSVersion.Major 17 | if ($psVersion -ge 6) { 18 | $schemaUrl = "https://raw.githubusercontent.com/Lauriethefish/QuestPatcher.QMod/main/QuestPatcher.QMod/Resources/qmod.schema.json" 19 | Invoke-WebRequest $schemaUrl -OutFile ./mod.schema.json 20 | 21 | $schema = "./mod.schema.json" 22 | $modJsonRaw = Get-Content $mod -Raw 23 | $modSchemaRaw = Get-Content $schema -Raw 24 | 25 | Remove-Item $schema 26 | 27 | Write-Output "Validating mod.json..." 28 | if (-not ($modJsonRaw | Test-Json -Schema $modSchemaRaw)) { 29 | Write-Output "Error: mod.json is not valid" 30 | exit 1 31 | } 32 | } 33 | else { 34 | Write-Output "Could not validate mod.json with schema: powershell version was too low (< 6)" 35 | } 36 | exit --------------------------------------------------------------------------------