From 5ae5bfc289699f3a06e82cb2aec14dcb4a700296 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:32:23 -0500 Subject: [PATCH] Initial commit --- .github/actions/canary-ndk/action.yml | 35 ++++++ .github/workflows/build-ndk.yml | 124 +++++++++++++++++++ .gitignore | 70 +++++++++++ .vscode/c_cpp_properties.json | 27 ++++ .vscode/launch.json | 42 +++++++ .vscode/settings.json | 121 ++++++++++++++++++ .vscode/tasks.json | 94 ++++++++++++++ CMakeLists.txt | 106 ++++++++++++++++ README.md | 11 ++ include/main.hpp | 26 ++++ mod.template.json | 15 +++ ndk-stack.ps1 | 29 +++++ qpm.json | 68 +++++++++++ qpm.shared.json | 170 ++++++++++++++++++++++++++ scripts/build.ps1 | 35 ++++++ scripts/copy.ps1 | 72 +++++++++++ scripts/createqmod.ps1 | 65 ++++++++++ scripts/pull-tombstone.ps1 | 52 ++++++++ scripts/restart-game.ps1 | 5 + scripts/start-logging.ps1 | 76 ++++++++++++ scripts/validate-modjson.ps1 | 39 ++++++ src/main.cpp | 50 ++++++++ 22 files changed, 1332 insertions(+) create mode 100644 .github/actions/canary-ndk/action.yml create mode 100644 .github/workflows/build-ndk.yml create mode 100644 .gitignore create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 include/main.hpp create mode 100644 mod.template.json create mode 100644 ndk-stack.ps1 create mode 100644 qpm.json create mode 100644 qpm.shared.json create mode 100644 scripts/build.ps1 create mode 100644 scripts/copy.ps1 create mode 100644 scripts/createqmod.ps1 create mode 100644 scripts/pull-tombstone.ps1 create mode 100644 scripts/restart-game.ps1 create mode 100644 scripts/start-logging.ps1 create mode 100644 scripts/validate-modjson.ps1 create mode 100644 src/main.cpp diff --git a/.github/actions/canary-ndk/action.yml b/.github/actions/canary-ndk/action.yml new file mode 100644 index 0000000..81f9b3a --- /dev/null +++ b/.github/actions/canary-ndk/action.yml @@ -0,0 +1,35 @@ +name: "Setup canary ndk" +description: "Sets up canary ndk" +outputs: + ndk-path: + value: ${{ steps.path.outputs.path }} + description: "Output path of the ndk" + cache-hit: + value: ${{ steps.cache.outputs.cache-hit }} + description: "Whether a cache hit occurred for the ndk" +runs: + using: "composite" + steps: + - name: NDK cache + id: cache + uses: actions/cache@v3 + with: + path: ${HOME}/android-ndk-r27-canary/ + key: ${{ runner.os }}-ndk-r27-canary + + - name: Download canary ndk + if: ${{ !steps.cache.outputs.cache-hit }} + env: + CANARY_URL: https://github.com/QuestPackageManager/ndk-canary-archive/releases/download/27.0.1/android-ndk-10883340-linux-x86_64.zip + run: wget ${CANARY_URL} -O ${HOME}/ndk.zip + shell: bash + + - name: Unzip ndk + if: ${{ !steps.cache.outputs.cache-hit }} + run: 7z x "${HOME}/ndk.zip" -o"${HOME}/" + shell: bash + + - name: Set output + id: path + shell: bash + run: echo "path=${HOME}/android-ndk-r27-canary" >> ${GITHUB_OUTPUT} diff --git a/.github/workflows/build-ndk.yml b/.github/workflows/build-ndk.yml new file mode 100644 index 0000000..0a02007 --- /dev/null +++ b/.github/workflows/build-ndk.yml @@ -0,0 +1,124 @@ +name: NDK build + +on: + workflow_dispatch: + push: + tags: + - "v*" + branches: + - 'master' + - 'dev/*' + - 'feat/*' + - 'fix/*' + paths-ignore: + - '**.yml' + - '!.github/workflows/build-ndk.yml' + - '**.json' + - '!qpm.json' + - '!qpm.shared.json' + - '!mod.template.json' + - '**.txt' + - '!CMakeLists.txt' + - '**.ps1' + - '!build.ps1' + - '!createqmod.ps1' + - '**.md' + - '.gitignore' + pull_request: + branches: master + +env: + module_id: NoWarning + qmod_name: NoWarning + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + name: Checkout + with: + submodules: true + lfs: true + + - uses: seanmiddleditch/gha-setup-ninja@v3 + + # Use canary NDK to avoid lesser known compile bugs + - name: Setup canary NDK + id: setup-ndk + uses: ./.github/actions/canary-ndk + + - name: Create ndkpath.txt + run: | + echo ${{ steps.setup-ndk.outputs.ndk-path }} > ${GITHUB_WORKSPACE}/ndkpath.txt + cat ${GITHUB_WORKSPACE}/ndkpath.txt + + # get version from pushed tag + - name: Extract version + if: startsWith(github.ref, 'refs/tags/v') + id: version + run: | + echo "TAG=${GITHUB_REF#refs/tags/}" >> ${GITHUB_OUTPUT} + echo "VERSION=${GITHUB_REF#refs/tags/v}" >> ${GITHUB_OUTPUT} + + # if we don't have a tag, don't do anything special + - name: Setup qpm + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + uses: Fernthedev/qpm-action@main + with: + workflow_token: ${{ secrets.GITHUB_TOKEN }} + restore: true + cache: true + publish: false + + - name: Build & Create Qmod + run: | + cd ${GITHUB_WORKSPACE} + qpm s qmod + + - name: Get Library Name + id: libname + run: | + cd ./build/ + pattern="lib${module_id}*.so" + files=( $pattern ) + echo "NAME=${files[0]}" >> ${GITHUB_OUTPUT} + + - name: Rename debug artifact + run: mv "./build/debug/${{ steps.libname.outputs.NAME }}" "./build/debug_${{ steps.libname.outputs.NAME }}" + + - name: Upload non-debug artifact + uses: actions/upload-artifact@v2 + with: + name: ${{ steps.libname.outputs.NAME }} + path: ./build/${{ steps.libname.outputs.NAME }} + if-no-files-found: error + + - name: Upload debug artifact + uses: actions/upload-artifact@v2 + with: + name: debug_${{ steps.libname.outputs.NAME }} + path: ./build/debug_${{ steps.libname.outputs.NAME }} + if-no-files-found: error + + - name: Upload qmod artifact + uses: actions/upload-artifact@v2 + with: + name: ${{env.qmod_name}}.qmod + path: ./${{ env.qmod_name }}.qmod + if-no-files-found: error + + # if we had a tag, we should make a release + - name: Upload release artifacts + if: startsWith(github.ref, 'refs/tags/v') + id: upload_file_release + uses: softprops/action-gh-release@v0.1.15 + with: + tag_name: ${{ github.event.inputs.version }} + files: | + ./build/${{ steps.libname.outputs.NAME }} + ./build/debug_${{ steps.libname.outputs.NAME }} + ./${{ env.qmod_name }}.qmod + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9269277 --- /dev/null +++ b/.gitignore @@ -0,0 +1,70 @@ +# Ignore clangd cache +.cache/ + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# VSCode config stuff +!.vscode/c_cpp_properties.json +!.vscode/tasks.json +!.vscode/settings.json + +# Jetbrains IDEs +.idea/ + +# NDK stuff +out/ +[Ll]ib/ +[Ll]ibs/ +[Oo]bj/ +[Oo]bjs/ +ndkpath.txt +*.zip +*.txt +*.log +Android.mk.backup + +# QPM stuff +[Ee]xtern/ +*.qmod +mod.json +qpm_defines.cmake +![Cc][Mm]ake[Ll]ists.txt + +# CMake stuff +[Bb]uild/ +cmake-build-*/ +extern.cmake + +# QMOD Schema +mod.json.schema \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..0803a2f --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,27 @@ +{ + "configurations": [ + { + "defines": [ + "MOD_ID=\"NoWarning\"", + "VERSION=\"0.1.0\"", + "__GNUC__", + "__aarch64__" + ], + "includePath": [ + "${workspaceFolder}/extern/includes/libil2cpp/il2cpp/libil2cpp", + "${workspaceFolder}/extern/includes/codegen/include", + "${workspaceFolder}/extern/includes", + "${workspaceFolder}/include", + "${workspaceFolder}/shared", + "${workspaceFolder}", + "${default}" + ], + "name": "Quest", + "cStandard": "c11", + "cppStandard": "c++20", + "intelliSenseMode": "clang-x64", + "compileCommands": "${workspaceFolder}/build/compile_commands.json" + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1813709 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,42 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Beat Saber", + "type": "fb-lldb", + "request": "launch", + "preLaunchTask": "Powershell Build and Copy", + "android": { + "application": { + "package": "com.beatgames.beatsaber", + "activity": "com.unity3d.player.UnityPlayerActivity" + }, + "lldbConfig": { + "sourceMaps": [], + "librarySearchPaths": [ + "${workspaceFolder}/build/debug/", + "${workspaceFolder}/extern/libs/" + ] + } + } + }, + { + "name": "Attach to running Beat Saber Instance", + "type": "fb-lldb", + "request": "attach", + "android": { + "application": { + "package": "com.beatgames.beatsaber", + "activity": "com.unity3d.player.UnityPlayerActivity" + }, + "lldbConfig": { + "sourceMaps": [], + "librarySearchPaths": [ + "${workspaceFolder}/build/debug/", + "${workspaceFolder}/extern/libs/", + ] + } + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f521685 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,121 @@ +{ + "files.associations": { + "iosfwd": "cpp", + "__config": "cpp", + "__nullptr": "cpp", + "thread": "cpp", + "any": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "optional": "cpp", + "queue": "cpp", + "set": "cpp", + "stack": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "variant": "cpp", + "vector": "cpp", + "__bit_reference": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__functional_base": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "cfenv": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "csetjmp": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "exception": "cpp", + "coroutine": "cpp", + "propagate_const": "cpp", + "forward_list": "cpp", + "fstream": "cpp", + "functional": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "locale": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "ostream": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "scoped_allocator": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "strstream": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "utility": "cpp", + "valarray": "cpp", + "xstring": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "concepts": "cpp", + "filesystem": "cpp", + "shared_mutex": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstddef": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp", + "format": "cpp", + "ranges": "cpp", + "stop_token": "cpp", + "__verbose_abort": "cpp" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1a599e2 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,94 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "NDK Build", + "detail": "Builds the library using ndk-build.cmd", + "type": "shell", + "command": "ndk-build", + "windows": { + "command": "ndk-build.cmd" + }, + "args": [ + "NDK_PROJECT_PATH=.", + "APP_BUILD_SCRIPT=./Android.mk", + "NDK_APPLICATION_MK=./Application.mk" + ], + "group": "build", + "options": { + "env": {} + } + }, + { + "label": "Powershell Build", + "detail": "Builds the library using Powershell (recommended)", + "type": "shell", + "command": "./scripts/build.ps1", + "windows": { + "command": "./scripts/build.ps1" + }, + "group": { + "kind": "build", + "isDefault": true + }, + "options": { + "env": {} + } + }, + { + "label": "Powershell Build and Copy", + "detail": "Builds and copies the library to the Quest using ADB and force-quits Beat Saber", + "type": "shell", + "command": "./scripts/copy.ps1", + "windows": { + "command": "./scripts/copy.ps1" + }, + "group": "build", + "options": { + "env": {} + } + }, + { + "label": "QMOD Build", + "detail": "Builds a .qmod to be installed into BMBF or QuestPatcher", + "type": "shell", + "command": "./scripts/createqmod.ps1", + "windows": { + "command": "./scripts/createqmod.ps1" + }, + "args": [], + "group": "build", + "options": { + "env": {} + } + }, + { + "label": "Start logging", + "detail": "Begin logging from the Quest to the console", + "type": "shell", + "command": "./scripts/start-logging.ps1", + "windows": { + "command": "./scripts/start-logging.ps1" + } + }, + { + "label": "Start logging to file", + "detail": "Begin logging from the Quest to the console and saving output to a file 'logcat.log'", + "type": "shell", + "command": "./scripts/start-logging.ps1 --file", + "windows": { + "command": "./scripts/start-logging.ps1 --file" + } + }, + { + "label": "Restart Beat Saber", + "detail": "Force-quits and restarts Beat Saber on the Quest", + "type": "shell", + "command": "./scripts/restart-game.ps1", + "windows": { + "command": "./scripts/restart-game.ps1" + }, + "problemMatcher": [] + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..df3f910 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,106 @@ +# include some defines automatically made by qpm +include(qpm_defines.cmake) + +# override mod id +set(MOD_ID "NoWarning") + +# Enable link time optimization +# In my experience, this can be highly unstable but it nets a huge size optimization and likely performance +# However, the instability was seen using Android.mk/ndk-build builds. With Ninja + CMake, this problem seems to have been solved. +# As always, test thoroughly +# - Fern +# set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + +cmake_minimum_required(VERSION 3.21) +project(${COMPILE_ID}) + +# export compile commands for significantly better intellisense +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# c++ standard +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED 20) + +# define that stores the actual source directory +set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) +set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +# compile options used +add_compile_options(-frtti -fexceptions) +add_compile_options(-O3) +# compile definitions used +add_compile_definitions(VERSION=\"${MOD_VERSION}\") +add_compile_definitions(MOD_ID=\"${MOD_ID}\") + +# recursively get all src files +RECURSE_FILES(cpp_file_list ${SOURCE_DIR}/*.cpp) +RECURSE_FILES(c_file_list ${SOURCE_DIR}/*.c) + +RECURSE_FILES(inline_hook_c ${EXTERN_DIR}/includes/beatsaber-hook/shared/inline-hook/*.c) +RECURSE_FILES(inline_hook_cpp ${EXTERN_DIR}/includes/beatsaber-hook/shared/inline-hook/*.cpp) + +# add all src files to compile +add_library( + ${COMPILE_ID} + SHARED + ${cpp_file_list} + ${c_file_list} + ${inline_hook_c} + ${inline_hook_cpp} +) + +target_include_directories(${COMPILE_ID} PRIVATE .) + +# add src dir as include dir +target_include_directories(${COMPILE_ID} PRIVATE ${SOURCE_DIR}) +# add include dir as include dir +target_include_directories(${COMPILE_ID} PRIVATE ${INCLUDE_DIR}) +# add shared dir as include dir +target_include_directories(${COMPILE_ID} PUBLIC ${SHARED_DIR}) +# codegen includes +target_include_directories(${COMPILE_ID} PRIVATE ${EXTERN_DIR}/includes/${CODEGEN_ID}/include) + +target_link_libraries(${COMPILE_ID} PRIVATE -llog) +# add extern stuff like libs and other includes +include(extern.cmake) + +add_custom_command(TARGET ${COMPILE_ID} POST_BUILD + COMMAND ${CMAKE_STRIP} -d --strip-all + "lib${COMPILE_ID}.so" -o "stripped_lib${COMPILE_ID}.so" + COMMENT "Strip debug symbols done on final binary.") + +add_custom_command(TARGET ${COMPILE_ID} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory debug + COMMENT "Make directory for debug symbols" + ) + +add_custom_command(TARGET ${COMPILE_ID} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rename lib${COMPILE_ID}.so debug/lib${COMPILE_ID}.so + COMMENT "Rename the lib to debug_ since it has debug symbols" + ) + +# strip debug symbols from the .so and all dependencies +add_custom_command(TARGET ${COMPILE_ID} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rename stripped_lib${COMPILE_ID}.so lib${COMPILE_ID}.so + COMMENT "Rename the stripped lib to regular" + ) + foreach(so_file ${so_list}) + cmake_path(GET so_file FILENAME file) + + add_custom_command(TARGET ${COMPILE_ID} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${so_file} debug/${file} + COMMENT "Copy so files for ndk stack" + ) + + add_custom_command(TARGET ${COMPILE_ID} POST_BUILD + COMMAND ${CMAKE_STRIP} -g -S -d --strip-all ${so_file} -o ${file} + COMMENT "Strip debug symbols from the dependencies") + endforeach() + + foreach(a_file ${a_list}) + cmake_path(GET a_file FILENAME file) + + add_custom_command(TARGET ${COMPILE_ID} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${a_file} debug/${file} + COMMENT "Copy a files for ndk stack") + endforeach() diff --git a/README.md b/README.md new file mode 100644 index 0000000..6465975 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# NoWarning +Skips the health and safety warning. + +Use `qpm-rust s build` to build +Same goes for `qpm-rust s copy` and `qpm-rust s qmod` + +## Credits + +* [zoller27osu](https://github.com/zoller27osu), [Sc2ad](https://github.com/Sc2ad) and [jakibaki](https://github.com/jakibaki) - [beatsaber-hook](https://github.com/sc2ad/beatsaber-hook) +* [raftario](https://github.com/raftario) +* [Lauriethefish](https://github.com/Lauriethefish), [danrouse](https://github.com/danrouse) and [Bobby Shmurner](https://github.com/BobbyShmurner) for [this template](https://github.com/Lauriethefish/quest-mod-template) diff --git a/include/main.hpp b/include/main.hpp new file mode 100644 index 0000000..9a1a67b --- /dev/null +++ b/include/main.hpp @@ -0,0 +1,26 @@ +#pragma once + +// Include the modloader header, which allows us to tell the modloader which mod this is, and the version etc. +#include "scotland2/shared/modloader.h" + +// beatsaber-hook is a modding framework that lets us call functions and fetch field values from in the game +// It also allows creating objects, configuration, and importantly, hooking methods to modify their values +#include "beatsaber-hook/shared/utils/hooking.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-utils.hpp" +#include "beatsaber-hook/shared/utils/logging.hpp" +#include "beatsaber-hook/shared/utils/typedefs.h" + +/// @brief Stores the ID and version of our mod, and is sent to the modloader upon startup +modloader::ModInfo modInfo{MOD_ID, VERSION, 0}; + +/// @brief A logger, useful for printing debug messages +/// @return +static constexpr auto Logger = Paper::ConstLoggerContext(MOD_ID); + +#define MOD_EXPORT __attribute__((visibility("default"))) +#ifdef __cplusplus +#define MOD_EXPORT_FUNC extern "C" MOD_EXPORT +#else +#define MOD_EXPORT_FUNC MOD_EXPORT +#endif diff --git a/mod.template.json b/mod.template.json new file mode 100644 index 0000000..123796e --- /dev/null +++ b/mod.template.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://raw.githubusercontent.com/Lauriethefish/QuestPatcher.QMod/main/QuestPatcher.QMod/Resources/qmod.schema.json", + "_QPVersion": "0.1.1", + "name": "${mod_name}", + "id": "${mod_id}", + "author": "DanTheMan827", + "version": "0.1.0", + "packageId": "com.beatgames.beatsaber", + "packageVersion": "1.35.0_8016709773", + "description": "Skips the health and safety warning.", + "dependencies": [], + "modFiles": ["${binary}"], + "libraryFiles": [], + "fileCopies": [] +} diff --git a/ndk-stack.ps1 b/ndk-stack.ps1 new file mode 100644 index 0000000..ac30cea --- /dev/null +++ b/ndk-stack.ps1 @@ -0,0 +1,29 @@ +Param( + [Parameter(Mandatory=$false)] + [String] $logName = "log.log", + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"NDK-Stack`" - Processes a tombstone using the debug .so to find file locations" + Write-Output "`n-- Arguments --`n" + + Write-Output "LogName `t`t The file name of the tombstone to process" + + exit +} + +if (Test-Path "./ndkpath.txt") { + $NDKPath = Get-Content ./ndkpath.txt +} else { + $NDKPath = $ENV:ANDROID_NDK_HOME +} + +$stackScript = "$NDKPath/ndk-stack" +if (-not ($PSVersionTable.PSEdition -eq "Core")) { + $stackScript += ".cmd" +} + +Get-Content $logName | & $stackScript -sym ./build/debug/ > "$($logName)_processed.log" diff --git a/qpm.json b/qpm.json new file mode 100644 index 0000000..38aa584 --- /dev/null +++ b/qpm.json @@ -0,0 +1,68 @@ +{ + "version": "0.2.0", + "sharedDir": "shared", + "dependenciesDir": "extern", + "info": { + "name": "NoWarning", + "id": "NoWarning", + "version": "0.1.0", + "url": null, + "additionalData": { + "overrideSoName": "libNoWarning.so", + "cmake": true + } + }, + "workspace": { + "scripts": { + "build": [ + "pwsh ./scripts/build.ps1" + ], + "copy": [ + "pwsh ./scripts/copy.ps1" + ], + "logcat": [ + "pwsh ./scripts/start-logging.ps1" + ], + "qmod": [ + "pwsh ./scripts/build.ps1", + "pwsh ./scripts/createqmod.ps1" + ], + "restart": [ + "pwsh ./scripts/restart-game.ps1" + ], + "stack": [ + "pwsh ./scripts/ndk-stack.ps1" + ], + "tomb": [ + "pwsh ./scripts/pull-tombstone.ps1" + ] + } + }, + "dependencies": [ + { + "id": "beatsaber-hook", + "versionRange": "^5.1.2", + "additionalData": {} + }, + { + "id": "paper", + "versionRange": "^3.0.0", + "additionalData": { + "private": true + } + }, + { + "id": "bs-cordl", + "versionRange": "3500.*", + "additionalData": {} + }, + { + "id": "scotland2", + "versionRange": "^0.1.0", + "additionalData": { + "includeQmod": false, + "private": true + } + } + ] +} diff --git a/qpm.shared.json b/qpm.shared.json new file mode 100644 index 0000000..ebafb7a --- /dev/null +++ b/qpm.shared.json @@ -0,0 +1,170 @@ +{ + "config": { + "version": "0.2.0", + "sharedDir": "shared", + "dependenciesDir": "extern", + "info": { + "name": "NoWarning", + "id": "NoWarning", + "version": "0.1.0", + "url": null, + "additionalData": { + "overrideSoName": "libNoWarning.so", + "cmake": true + } + }, + "workspace": { + "scripts": { + "build": [ + "pwsh ./scripts/build.ps1" + ], + "copy": [ + "pwsh ./scripts/copy.ps1" + ], + "logcat": [ + "pwsh ./scripts/start-logging.ps1" + ], + "qmod": [ + "pwsh ./scripts/build.ps1", + "pwsh ./scripts/createqmod.ps1" + ], + "restart": [ + "pwsh ./scripts/restart-game.ps1" + ], + "stack": [ + "pwsh ./scripts/ndk-stack.ps1" + ], + "tomb": [ + "pwsh ./scripts/pull-tombstone.ps1" + ] + } + }, + "dependencies": [ + { + "id": "beatsaber-hook", + "versionRange": "^5.1.2", + "additionalData": {} + }, + { + "id": "paper", + "versionRange": "^3.0.0", + "additionalData": { + "private": true + } + }, + { + "id": "bs-cordl", + "versionRange": "3500.*", + "additionalData": {} + }, + { + "id": "scotland2", + "versionRange": "^0.1.0", + "additionalData": { + "includeQmod": false, + "private": true + } + } + ] + }, + "restoredDependencies": [ + { + "dependency": { + "id": "bs-cordl", + "versionRange": "=3500.0.0", + "additionalData": { + "headersOnly": true, + "branchName": "version/v3500_0_0", + "compileOptions": { + "includePaths": [ + "include" + ], + "cppFeatures": [], + "cppFlags": [ + "-DNEED_UNSAFE_CSHARP", + "-fdeclspec", + "-DUNITY_2021", + "-DHAS_CODEGEN" + ] + } + } + }, + "version": "3500.0.0" + }, + { + "dependency": { + "id": "paper", + "versionRange": "=3.6.1", + "additionalData": { + "soLink": "https://github.com/Fernthedev/paperlog/releases/download/v3.6.1/libpaperlog.so", + "debugSoLink": "https://github.com/Fernthedev/paperlog/releases/download/v3.6.1/debug_libpaperlog.so", + "overrideSoName": "libpaperlog.so", + "modLink": "https://github.com/Fernthedev/paperlog/releases/download/v3.6.1/paperlog.qmod", + "branchName": "version/v3_6_1", + "compileOptions": { + "systemIncludes": [ + "shared/utfcpp/source" + ] + }, + "cmake": false + } + }, + "version": "3.6.1" + }, + { + "dependency": { + "id": "beatsaber-hook", + "versionRange": "=5.1.2", + "additionalData": { + "soLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v5.1.2/libbeatsaber-hook_5_1_2.so", + "debugSoLink": "https://github.com/QuestPackageManager/beatsaber-hook/releases/download/v5.1.2/debug_libbeatsaber-hook_5_1_2.so", + "branchName": "version/v5_1_2", + "cmake": true + } + }, + "version": "5.1.2" + }, + { + "dependency": { + "id": "libil2cpp", + "versionRange": "=0.3.1", + "additionalData": { + "headersOnly": true + } + }, + "version": "0.3.1" + }, + { + "dependency": { + "id": "scotland2", + "versionRange": "=0.1.4", + "additionalData": { + "soLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.4/libsl2.so", + "debugSoLink": "https://github.com/sc2ad/scotland2/releases/download/v0.1.4/debug_libsl2.so", + "overrideSoName": "libsl2.so", + "branchName": "version/v0_1_4" + } + }, + "version": "0.1.4" + }, + { + "dependency": { + "id": "fmt", + "versionRange": "=10.0.0", + "additionalData": { + "headersOnly": true, + "branchName": "version/v10_0_0", + "compileOptions": { + "systemIncludes": [ + "fmt/include/" + ], + "cppFlags": [ + "-DFMT_HEADER_ONLY" + ] + } + } + }, + "version": "10.0.0" + } + ] +} \ No newline at end of file diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 0000000..a5b9c53 --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,35 @@ +Param( + [Parameter(Mandatory=$false)] + [Switch] $clean, + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"Build`" - Copiles your mod into a `".so`" or a `".a`" library" + Write-Output "`n-- Arguments --`n" + + Write-Output "-Clean `t`t Deletes the `"build`" folder, so that the entire library is rebuilt" + + exit +} + +# if user specified clean, remove all build files +if ($clean.IsPresent) { + if (Test-Path -Path "build") { + remove-item build -R + } +} + + +if (($clean.IsPresent) -or (-not (Test-Path -Path "build"))) { + new-item -Path build -ItemType Directory +} + +if (-not (Test-Path -Path "extern")) { + & qpm restore +} + +& cmake -G "Ninja" -DCMAKE_BUILD_TYPE="RelWithDebInfo" -B build +& cmake --build ./build diff --git a/scripts/copy.ps1 b/scripts/copy.ps1 new file mode 100644 index 0000000..9e8fa5e --- /dev/null +++ b/scripts/copy.ps1 @@ -0,0 +1,72 @@ +Param( + [Parameter(Mandatory=$false)] + [Switch] $clean, + + [Parameter(Mandatory=$false)] + [Switch] $log, + + [Parameter(Mandatory=$false)] + [Switch] $useDebug, + + [Parameter(Mandatory=$false)] + [Switch] $self, + + [Parameter(Mandatory=$false)] + [Switch] $all, + + [Parameter(Mandatory=$false)] + [String] $custom="", + + [Parameter(Mandatory=$false)] + [String] $file="", + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"Copy`" - Builds and copies your mod to your quest, and also starts Beat Saber with optional logging" + Write-Output "`n-- Arguments --`n" + + Write-Output "-Clean `t`t Performs a clean build (equvilant to running `"build -clean`")" + Write-Output "-UseDebug `t Copies the debug version of the mod to your quest" + Write-Output "-Log `t`t Logs Beat Saber using the `"Start-Logging`" command" + + Write-Output "`n-- Logging Arguments --`n" + + & $PSScriptRoot/start-logging.ps1 -help -excludeHeader + + exit +} + +& $PSScriptRoot/build.ps1 -clean:$clean + +if ($LASTEXITCODE -ne 0) { + Write-Output "Failed to build, exiting..." + exit $LASTEXITCODE +} + +& $PSScriptRoot/validate-modjson.ps1 +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +$modJson = Get-Content "./mod.json" -Raw | ConvertFrom-Json + +$modFiles = $modJson.modFiles + +foreach ($fileName in $modFiles) { + if ($useDebug -eq $true) { + & adb push build/debug/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/early_mods/$fileName + } else { + & adb push build/$fileName /sdcard/ModData/com.beatgames.beatsaber/Modloader/early_mods/$fileName + } + + & adb shell chmod 666 /sdcard/ModData/com.beatgames.beatsaber/Modloader/early_mods/$fileName +} + +& $PSScriptRoot/restart-game.ps1 + +if ($log -eq $true) { + & adb logcat -c + & $PSScriptRoot/start-logging.ps1 -self:$self -all:$all -custom:$custom -file:$file +} diff --git a/scripts/createqmod.ps1 b/scripts/createqmod.ps1 new file mode 100644 index 0000000..d7072a6 --- /dev/null +++ b/scripts/createqmod.ps1 @@ -0,0 +1,65 @@ +Param( + [Parameter(Mandatory=$false)] + [String] $qmodName="", + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"createqmod`" - Creates a .qmod file with your compiled libraries and mod.json." + Write-Output "`n-- Arguments --`n" + + Write-Output "-QmodName `t The file name of your qmod" + + exit +} + +$mod = "./mod.json" + +& $PSScriptRoot/validate-modjson.ps1 +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} +$modJson = Get-Content $mod -Raw | ConvertFrom-Json + +if ($qmodName -eq "") { + $qmodName = $modJson.name +} + +$filelist = @($mod) + +$cover = "./" + $modJson.coverImage +if ((-not ($cover -eq "./")) -and (Test-Path $cover)) { + $filelist += ,$cover +} + +foreach ($mod in $modJson.modFiles) { + $path = "./build/" + $mod + if (-not (Test-Path $path)) { + $path = "./extern/libs/" + $mod + } + if (-not (Test-Path $path)) { + Write-Output "Error: could not find dependency: $path" + exit 1 + } + $filelist += $path +} + +foreach ($lib in $modJson.libraryFiles) { + $path = "./build/" + $lib + if (-not (Test-Path $path)) { + $path = "./extern/libs/" + $lib + } + if (-not (Test-Path $path)) { + Write-Output "Error: could not find dependency: $path" + exit 1 + } + $filelist += $path +} + +$zip = $qmodName + ".zip" +$qmod = $qmodName + ".qmod" + +Compress-Archive -Path $filelist -DestinationPath $zip -Update +Move-Item $zip $qmod -Force diff --git a/scripts/pull-tombstone.ps1 b/scripts/pull-tombstone.ps1 new file mode 100644 index 0000000..10fbc24 --- /dev/null +++ b/scripts/pull-tombstone.ps1 @@ -0,0 +1,52 @@ +Param( + [Parameter(Mandatory=$false)] + [String] $fileName = "RecentCrash.log", + + [Parameter(Mandatory=$false)] + [Switch] $analyze, + + [Parameter(Mandatory=$false)] + [Switch] $help +) + +if ($help -eq $true) { + Write-Output "`"Pull-Tombstone`" - Finds and pulls the most recent tombstone from your quest, optionally analyzing it with ndk-stack" + Write-Output "`n-- Arguments --`n" + + Write-Output "-FileName `t The name for the output file, defaulting to RecentCrash.log" + Write-Output "-Analyze `t Runs ndk-stack on the file after pulling" + + exit +} + +$global:currentDate = get-date +$global:recentDate = $Null +$global:recentTombstone = $Null + +for ($i = 0; $i -lt 3; $i++) { + $stats = & adb shell stat /sdcard/Android/data/com.beatgames.beatsaber/files/tombstone_0$i + $date = (Select-String -Input $stats -Pattern "(?<=Modify: )\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?=.\d{9})").Matches.Value + if([string]::IsNullOrEmpty($date)) { + Write-Output "Failed to pull tombstone, exiting..." + exit 1; + } + $dateObj = [datetime]::ParseExact($date, "yyyy-MM-dd HH:mm:ss", $Null) + $difference = [math]::Round(($currentDate - $dateObj).TotalMinutes) + if ($difference -eq 1) { + Write-Output "Found tombstone_0$i $difference minute ago" + } else { + Write-Output "Found tombstone_0$i $difference minutes ago" + } + if (-not $recentDate -or $recentDate -lt $dateObj) { + $recentDate = $dateObj + $recentTombstone = $i + } +} + +Write-Output "Latest tombstone was tombstone_0$recentTombstone" + +& adb pull /sdcard/Android/data/com.beatgames.beatsaber/files/tombstone_0$recentTombstone $fileName + +if ($analyze) { + & $PSScriptRoot/ndk-stack.ps1 -logName:$fileName +} diff --git a/scripts/restart-game.ps1 b/scripts/restart-game.ps1 new file mode 100644 index 0000000..382ae44 --- /dev/null +++ b/scripts/restart-game.ps1 @@ -0,0 +1,5 @@ +adb shell am force-stop com.beatgames.beatsaber +adb shell am start com.beatgames.beatsaber/com.unity3d.player.UnityPlayerActivity + +& adb logcat -c +& $PSScriptRoot/start-logging.ps1 diff --git a/scripts/start-logging.ps1 b/scripts/start-logging.ps1 new file mode 100644 index 0000000..d30984f --- /dev/null +++ b/scripts/start-logging.ps1 @@ -0,0 +1,76 @@ +Param( + [Parameter(Mandatory=$false)] + [Switch] $self, + + [Parameter(Mandatory=$false)] + [Switch] $all, + + [Parameter(Mandatory=$false)] + [String] $custom="", + + [Parameter(Mandatory=$false)] + [String] $file="", + + [Parameter(Mandatory=$false)] + [Switch] $help, + + [Parameter(Mandatory=$false)] + [Switch] $excludeHeader +) + +if ($help -eq $true) { + if ($excludeHeader -eq $false) { + Write-Output "`"Start-Logging`" - Logs Beat Saber using `"adb logcat`"" + Write-Output "`n-- Arguments --`n" + } + + Write-Output "-Self `t`t Only Logs your mod and Crashes" + Write-Output "-All `t`t Logs everything, including logs made by the Quest itself" + Write-Output "-Custom `t Specify a specific logging pattern, e.g `"custom-types|questui`"" + Write-Output "`t`t NOTE: The paterent `"AndriodRuntime|CRASH`" is always appended to a custom pattern" + Write-Output "-File `t`t Saves the output of the log to the file name given" + + exit +} + +$bspid = adb shell pidof com.beatgames.beatsaber +$command = "adb logcat " + +if ($all -eq $false) { + $loops = 0 + while ([string]::IsNullOrEmpty($bspid) -and $loops -lt 3) { + Start-Sleep -Milliseconds 100 + $bspid = adb shell pidof com.beatgames.beatsaber + $loops += 1 + } + + if ([string]::IsNullOrEmpty($bspid)) { + Write-Output "Could not connect to adb, exiting..." + exit 1 + } + + $command += "--pid $bspid" +} + +if ($all -eq $false) { + $pattern = "(" + if ($self -eq $true) { + $modID = (Get-Content "./mod.json" -Raw | ConvertFrom-Json).id + $pattern += "$modID|" + } + if (![string]::IsNullOrEmpty($custom)) { + $pattern += "$custom|" + } + if ($pattern -eq "(") { + $pattern = "(QuestHook|modloader|" + } + $pattern += "AndroidRuntime|CRASH)" + $command += " | Select-String -pattern `"$pattern`"" +} + +if (![string]::IsNullOrEmpty($file)) { + $command += " | Out-File -FilePath $PSScriptRoot\$file" +} + +Write-Output "Logging using Command `"$command`"" +Invoke-Expression $command diff --git a/scripts/validate-modjson.ps1 b/scripts/validate-modjson.ps1 new file mode 100644 index 0000000..8eabd3c --- /dev/null +++ b/scripts/validate-modjson.ps1 @@ -0,0 +1,39 @@ +$mod = "./mod.json" +$modTemplate = Get-Item "./mod.template.json" +$modJson = Get-Item $mod + + +if (-not (Test-Path -Path $mod) -or $modTemplate.LastWriteTime -gt $modJson.LastWriteTime) { + if (Test-Path -Path ".\mod.template.json") { + & qpm qmod build + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } + else { + Write-Output "Error: mod.json and mod.template.json were not present" + exit 1 + } +} + +$psVersion = $PSVersionTable.PSVersion.Major +if ($psVersion -ge 6) { + $schemaUrl = "https://raw.githubusercontent.com/Lauriethefish/QuestPatcher.QMod/main/QuestPatcher.QMod/Resources/qmod.schema.json" + Invoke-WebRequest $schemaUrl -OutFile ./mod.schema.json + + $schema = "./mod.schema.json" + $modJsonRaw = Get-Content $mod -Raw + $modSchemaRaw = Get-Content $schema -Raw + + Remove-Item $schema + + Write-Output "Validating mod.json..." + if (-not ($modJsonRaw | Test-Json -Schema $modSchemaRaw)) { + Write-Output "Error: mod.json is not valid" + exit 1 + } +} +else { + Write-Output "Could not validate mod.json with schema: powershell version was too low (< 6)" +} +exit diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ac19789 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,50 @@ +#pragma region Includes +#include "main.hpp" + +// GlobalNamespace +#include "GlobalNamespace/HealthWarningFlowCoordinator.hpp" +#include "GlobalNamespace/PlayerAgreements.hpp" +#include "GlobalNamespace/PlayerData.hpp" +#include "GlobalNamespace/PlayerDataModel.hpp" +#pragma endregion + +#pragma region Usings +using namespace GlobalNamespace; +#pragma endregion + +#pragma region Hook definitions +MAKE_HOOK_MATCH(HealthWarningFlowCoordinator_DidActivate, &HealthWarningFlowCoordinator::DidActivate, void, HealthWarningFlowCoordinator* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { + HealthWarningFlowCoordinator_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); + + auto flag1 = self->____playerDataModel->get_playerData()->get_playerAgreements()->AgreedToEula(); + auto flag2 = self->____playerDataModel->get_playerData()->get_playerAgreements()->AgreedToPrivacyPolicy(); + + if (flag1 && flag2) { + self->HandleHealthWarningViewControllerDidFinish(); + } +} +#pragma endregion + +#pragma region Mod setup +/// @brief Called at the early stages of game loading +/// @param info +/// @return +MOD_EXPORT_FUNC void setup(CModInfo& info) { + info.id = MOD_ID; + info.version = VERSION; + + Logger.info("Completed setup!"); +} + +/// @brief Called later on in the game loading - a good time to install function hooks +/// @return +MOD_EXPORT_FUNC void late_load() { + il2cpp_functions::Init(); + + Logger.info("Installing hooks..."); + + INSTALL_HOOK(Logger, HealthWarningFlowCoordinator_DidActivate); + + Logger.info("Installed all hooks!"); +} +#pragma endregion