├── .github └── workflows │ └── dev-release.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── cmake.toml ├── cmkr.cmake ├── scripts └── camera.lua └── src ├── Plugin.cpp ├── Plugin.hpp └── uevr ├── API.h ├── API.hpp └── Plugin.hpp /.github/workflows/dev-release.yml: -------------------------------------------------------------------------------- 1 | name: Dev Release 2 | on: [push, workflow_dispatch] 3 | env: 4 | BUILD_TYPE: Release 5 | jobs: 6 | dev-release: 7 | runs-on: windows-latest 8 | strategy: 9 | matrix: 10 | target: [sh2r] 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 14 | with: 15 | submodules: recursive 16 | fetch-depth: 0 17 | persist-credentials: false 18 | 19 | - name: Configure CMake 20 | run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 21 | 22 | - name: Build 23 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target ${{matrix.target}} 24 | 25 | - name: Compress release 26 | run: | 27 | 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/build/${{env.BUILD_TYPE}}/sh2r.dll 28 | 29 | - name: Upload artifacts 30 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 31 | with: 32 | name: ${{matrix.target}} 33 | path: ${{github.workspace}}/${{matrix.target}}.zip 34 | if-no-files-found: error 35 | -------------------------------------------------------------------------------- /.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 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | build/* 31 | .vscode/* 32 | out/* 33 | .vs/* 34 | 35 | dependencies/steelsdk/* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dependencies/glm"] 2 | path = dependencies/glm 3 | url = https://github.com/g-truc/glm 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is automatically generated from cmake.toml - DO NOT EDIT 2 | # See https://github.com/build-cpp/cmkr for more information 3 | 4 | cmake_minimum_required(VERSION 3.15) 5 | 6 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 7 | message(FATAL_ERROR "In-tree builds are not supported. Run CMake from a separate directory: cmake -B build") 8 | endif() 9 | 10 | # Regenerate CMakeLists.txt automatically in the root project 11 | set(CMKR_ROOT_PROJECT OFF) 12 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 13 | set(CMKR_ROOT_PROJECT ON) 14 | 15 | # Bootstrap cmkr 16 | include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT) 17 | if(CMKR_INCLUDE_RESULT) 18 | cmkr() 19 | endif() 20 | 21 | # Enable folder support 22 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 23 | endif() 24 | 25 | # Create a configure-time dependency on cmake.toml to improve IDE support 26 | if(CMKR_ROOT_PROJECT) 27 | configure_file(cmake.toml cmake.toml COPYONLY) 28 | endif() 29 | 30 | 31 | add_compile_options($<$:/MP>) 32 | 33 | project(sh2r-proj) 34 | 35 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") 36 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") 37 | 38 | set(ASMJIT_STATIC ON CACHE BOOL "" FORCE) 39 | 40 | if ("${CMAKE_BUILD_TYPE}" MATCHES "Release") 41 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT") 42 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT") 43 | 44 | # Statically compile runtime 45 | string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 46 | string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 47 | string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") 48 | string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 49 | 50 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded") 51 | message(NOTICE "Building in Release mode") 52 | endif() 53 | 54 | include(FetchContent) 55 | 56 | message(STATUS "Fetching bddisasm (v1.34.10)...") 57 | FetchContent_Declare(bddisasm 58 | GIT_REPOSITORY 59 | "https://github.com/bitdefender/bddisasm" 60 | GIT_TAG 61 | v1.34.10 62 | ) 63 | FetchContent_MakeAvailable(bddisasm) 64 | 65 | message(STATUS "Fetching spdlog (76fb40d95455f249bd70824ecfcae7a8f0930fa3)...") 66 | FetchContent_Declare(spdlog 67 | GIT_REPOSITORY 68 | "https://github.com/gabime/spdlog" 69 | GIT_TAG 70 | 76fb40d95455f249bd70824ecfcae7a8f0930fa3 71 | ) 72 | FetchContent_MakeAvailable(spdlog) 73 | 74 | message(STATUS "Fetching kananlib (153b177593a97042a57608b36d4253fa5e982914)...") 75 | FetchContent_Declare(kananlib 76 | GIT_REPOSITORY 77 | "https://github.com/cursey/kananlib" 78 | GIT_TAG 79 | 153b177593a97042a57608b36d4253fa5e982914 80 | ) 81 | FetchContent_MakeAvailable(kananlib) 82 | 83 | # Target glm_static 84 | set(CMKR_TARGET glm_static) 85 | set(glm_static_SOURCES "") 86 | 87 | list(APPEND glm_static_SOURCES 88 | "dependencies/glm/glm/detail/glm.cpp" 89 | ) 90 | 91 | list(APPEND glm_static_SOURCES 92 | cmake.toml 93 | ) 94 | 95 | set(CMKR_SOURCES ${glm_static_SOURCES}) 96 | add_library(glm_static STATIC) 97 | 98 | if(glm_static_SOURCES) 99 | target_sources(glm_static PRIVATE ${glm_static_SOURCES}) 100 | endif() 101 | 102 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${glm_static_SOURCES}) 103 | 104 | target_include_directories(glm_static PUBLIC 105 | "dependencies/glm" 106 | ) 107 | 108 | unset(CMKR_TARGET) 109 | unset(CMKR_SOURCES) 110 | 111 | # Target sh2r 112 | set(CMKR_TARGET sh2r) 113 | set(sh2r_SOURCES "") 114 | 115 | list(APPEND sh2r_SOURCES 116 | "src/Plugin.cpp" 117 | "src/Plugin.hpp" 118 | "src/uevr/API.hpp" 119 | "src/uevr/Plugin.hpp" 120 | "src/uevr/API.h" 121 | ) 122 | 123 | list(APPEND sh2r_SOURCES 124 | cmake.toml 125 | ) 126 | 127 | set(CMKR_SOURCES ${sh2r_SOURCES}) 128 | add_library(sh2r SHARED) 129 | 130 | if(sh2r_SOURCES) 131 | target_sources(sh2r PRIVATE ${sh2r_SOURCES}) 132 | endif() 133 | 134 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${sh2r_SOURCES}) 135 | 136 | 137 | target_compile_features(sh2r PUBLIC 138 | cxx_std_23 139 | ) 140 | 141 | target_compile_options(sh2r PUBLIC 142 | "/GS-" 143 | "/bigobj" 144 | "/EHa" 145 | "/MP" 146 | ) 147 | 148 | target_include_directories(sh2r PUBLIC 149 | "shared/" 150 | "src/" 151 | "include/" 152 | ) 153 | 154 | target_link_libraries(sh2r PUBLIC 155 | kananlib 156 | glm_static 157 | ) 158 | 159 | target_compile_definitions(sh2r PUBLIC 160 | NOMINMAX 161 | WINVER=0x0A00 162 | ) 163 | 164 | unset(CMKR_TARGET) 165 | unset(CMKR_SOURCES) 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SH2R-UEVR 2 | 3 | UEVR improvements/motion controls for SILENT HILL 2 (2024) 4 | 5 | 6 | ## Install 7 | 8 | First, make sure that you are using the very latest **NIGHTLY** version of UEVR, you can get that here: https://github.com/praydog/UEVR-nightly/releases/latest/ or using [Rai Pal](https://github.com/Raicuparta/rai-pal) 9 | 10 | Get the [latest release zip](https://github.com/praydog/SH2R-UEVR/releases/latest) and click "Import Config" in UEVR, browse to the zip and click it. 11 | 12 | If you have any previous UEVR profile for this game, you may need to delete it as it may conflict with what this is attempting to do. 13 | 14 | ## Features 15 | ### First Person 16 | * Smooth movement 17 | * Camera is corrected to fix interactions and audio direction 18 | 19 | ### Motion controls 20 | * #### Ranged weapon aiming 21 | * Two handing of rifle and shotgun 22 | * Properly projected crosshair in 3D space 23 | * #### Melee swings 24 | * Fully immersive and accurate to weapon's size, even with custom models 25 | * Contextual hit reactions 26 | * Hitting enemies in the legs has a chance to incapacitate enemies for a short time, causing them to kneel in pain 27 | * Physics impulses are accurately applied in the hit areas, causing limbs to move upon hits 28 | * #### Items 29 | * Items are attached to right hand when inspecting or receiving them (like ammo or syringes) 30 | * The map is attached to both hands to simulate holding a map in real life 31 | 32 | ### Other features 33 | * Roomscale movement 34 | * Automatic disabling of VR features in cutscenes and full body events (though still fully 6DOF and stereoscopic) 35 | 36 | Inverse kinematics has been loosely explored, but not working correctly. So body has been hidden for now, and re-enabled in cutscenes and events. 37 | 38 | ## For curious modders 39 | 40 | This uses a hybrid C++ plugin and Lua script, and should serve as a good example of adding VR functionality. 41 | -------------------------------------------------------------------------------- /cmake.toml: -------------------------------------------------------------------------------- 1 | # Reference: https://build-cpp.github.io/cmkr/cmake-toml 2 | # to build: 3 | # > cmake -B build 4 | # > cmake --build build --config Release 5 | [project] 6 | name = "sh2r-proj" 7 | cmake-before=""" 8 | add_compile_options($<$:/MP>) 9 | """ 10 | cmake-after = """ 11 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") 13 | 14 | set(ASMJIT_STATIC ON CACHE BOOL "" FORCE) 15 | 16 | if ("${CMAKE_BUILD_TYPE}" MATCHES "Release") 17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT") 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT") 19 | 20 | # Statically compile runtime 21 | string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 22 | string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 23 | string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") 24 | string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 25 | 26 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded") 27 | message(NOTICE "Building in Release mode") 28 | endif() 29 | """ 30 | 31 | # used by kananlib and safetyhook 32 | [fetch-content.bddisasm] 33 | git = "https://github.com/bitdefender/bddisasm" 34 | tag = "v1.34.10" 35 | 36 | # used by kananlib 37 | [fetch-content.spdlog] 38 | git = "https://github.com/gabime/spdlog" 39 | tag = "76fb40d95455f249bd70824ecfcae7a8f0930fa3" 40 | 41 | [target.glm_static] 42 | type = "static" 43 | sources = ["dependencies/glm/glm/**.cpp"] 44 | include-directories = ["dependencies/glm"] 45 | 46 | [fetch-content.kananlib] 47 | git = "https://github.com/cursey/kananlib" 48 | tag = "153b177593a97042a57608b36d4253fa5e982914" 49 | 50 | [target.sh2r] 51 | type = "shared" 52 | sources = ["src/**.cpp", "src/**.c"] 53 | headers = ["src/**.hpp", "src/**.h"] 54 | include-directories = [ 55 | "shared/", 56 | "src/", 57 | "include/" 58 | ] 59 | compile-options = ["/GS-", "/bigobj", "/EHa", "/MP"] 60 | compile-features = ["cxx_std_23"] 61 | compile-definitions = [] 62 | link-libraries = [ 63 | "kananlib", 64 | "glm_static" 65 | ] 66 | cmake-after = """ 67 | target_compile_definitions(sh2r PUBLIC 68 | NOMINMAX 69 | WINVER=0x0A00 70 | ) 71 | """ -------------------------------------------------------------------------------- /cmkr.cmake: -------------------------------------------------------------------------------- 1 | include_guard() 2 | 3 | # Change these defaults to point to your infrastructure if desired 4 | set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) 5 | set(CMKR_TAG "v0.2.16" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) 6 | set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) 7 | 8 | # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake 9 | if(CMAKE_SCRIPT_MODE_FILE) 10 | set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build") 11 | set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}") 12 | file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}") 13 | endif() 14 | 15 | # Set these from the command line to customize for development/debugging purposes 16 | set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable") 17 | set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation") 18 | set(CMKR_BUILD_TYPE "Debug" CACHE STRING "cmkr build configuration") 19 | mark_as_advanced(CMKR_REPO CMKR_TAG CMKR_COMMIT_HASH CMKR_EXECUTABLE CMKR_SKIP_GENERATION CMKR_BUILD_TYPE) 20 | 21 | # Disable cmkr if generation is disabled 22 | if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION) 23 | message(STATUS "[cmkr] Skipping automatic cmkr generation") 24 | unset(CMKR_BUILD_SKIP_GENERATION CACHE) 25 | macro(cmkr) 26 | endmacro() 27 | return() 28 | endif() 29 | 30 | # Disable cmkr if no cmake.toml file is found 31 | if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 32 | message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 33 | macro(cmkr) 34 | endmacro() 35 | return() 36 | endif() 37 | 38 | # Convert a Windows native path to CMake path 39 | if(CMKR_EXECUTABLE MATCHES "\\\\") 40 | string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}") 41 | set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE) 42 | unset(CMKR_EXECUTABLE_CMAKE) 43 | endif() 44 | 45 | # Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher) 46 | function(cmkr_exec) 47 | execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT) 48 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 49 | message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})") 50 | endif() 51 | endfunction() 52 | 53 | # Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment) 54 | if(WIN32) 55 | set(CMKR_EXECUTABLE_NAME "cmkr.exe") 56 | else() 57 | set(CMKR_EXECUTABLE_NAME "cmkr") 58 | endif() 59 | 60 | # Use cached cmkr if found 61 | if(DEFINED ENV{CMKR_CACHE}) 62 | set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}") 63 | string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}") 64 | if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$") 65 | set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/") 66 | endif() 67 | # Build in release mode for the cache 68 | set(CMKR_BUILD_TYPE "Release") 69 | else() 70 | set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_") 71 | endif() 72 | set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}") 73 | set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}") 74 | 75 | # Helper function to check if a string starts with a prefix 76 | # Cannot use MATCHES, see: https://github.com/build-cpp/cmkr/issues/61 77 | function(cmkr_startswith str prefix result) 78 | string(LENGTH "${prefix}" prefix_length) 79 | string(LENGTH "${str}" str_length) 80 | if(prefix_length LESS_EQUAL str_length) 81 | string(SUBSTRING "${str}" 0 ${prefix_length} str_prefix) 82 | if(prefix STREQUAL str_prefix) 83 | set("${result}" ON PARENT_SCOPE) 84 | return() 85 | endif() 86 | endif() 87 | set("${result}" OFF PARENT_SCOPE) 88 | endfunction() 89 | 90 | # Handle upgrading logic 91 | if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE) 92 | cmkr_startswith("${CMKR_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/_cmkr" CMKR_STARTSWITH_BUILD) 93 | cmkr_startswith("${CMKR_EXECUTABLE}" "${CMKR_DIRECTORY_PREFIX}" CMKR_STARTSWITH_CACHE) 94 | if(CMKR_STARTSWITH_BUILD) 95 | if(DEFINED ENV{CMKR_CACHE}) 96 | message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'") 97 | if(EXISTS "${CMKR_CACHED_EXECUTABLE}") 98 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 99 | else() 100 | unset(CMKR_EXECUTABLE CACHE) 101 | endif() 102 | else() 103 | message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 104 | unset(CMKR_EXECUTABLE CACHE) 105 | endif() 106 | elseif(DEFINED ENV{CMKR_CACHE} AND CMKR_STARTSWITH_CACHE) 107 | message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 108 | unset(CMKR_EXECUTABLE CACHE) 109 | endif() 110 | endif() 111 | 112 | if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}") 113 | message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") 114 | elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE) 115 | message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found") 116 | elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}") 117 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 118 | message(STATUS "[cmkr] Found cached cmkr: '${CMKR_EXECUTABLE}'") 119 | else() 120 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 121 | message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'") 122 | 123 | message(STATUS "[cmkr] Fetching cmkr...") 124 | if(EXISTS "${CMKR_DIRECTORY}") 125 | cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}") 126 | endif() 127 | find_package(Git QUIET REQUIRED) 128 | cmkr_exec("${GIT_EXECUTABLE}" 129 | clone 130 | --config advice.detachedHead=false 131 | --branch ${CMKR_TAG} 132 | --depth 1 133 | ${CMKR_REPO} 134 | "${CMKR_DIRECTORY}" 135 | ) 136 | if(CMKR_COMMIT_HASH) 137 | execute_process( 138 | COMMAND "${GIT_EXECUTABLE}" checkout -q "${CMKR_COMMIT_HASH}" 139 | RESULT_VARIABLE CMKR_EXEC_RESULT 140 | WORKING_DIRECTORY "${CMKR_DIRECTORY}" 141 | ) 142 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 143 | message(FATAL_ERROR "Tag '${CMKR_TAG}' hash is not '${CMKR_COMMIT_HASH}'") 144 | endif() 145 | endif() 146 | message(STATUS "[cmkr] Building cmkr (using system compiler)...") 147 | cmkr_exec("${CMAKE_COMMAND}" 148 | --no-warn-unused-cli 149 | "${CMKR_DIRECTORY}" 150 | "-B${CMKR_DIRECTORY}/build" 151 | "-DCMAKE_BUILD_TYPE=${CMKR_BUILD_TYPE}" 152 | "-DCMAKE_UNITY_BUILD=ON" 153 | "-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}" 154 | "-DCMKR_GENERATE_DOCUMENTATION=OFF" 155 | ) 156 | cmkr_exec("${CMAKE_COMMAND}" 157 | --build "${CMKR_DIRECTORY}/build" 158 | --config "${CMKR_BUILD_TYPE}" 159 | --parallel 160 | ) 161 | cmkr_exec("${CMAKE_COMMAND}" 162 | --install "${CMKR_DIRECTORY}/build" 163 | --config "${CMKR_BUILD_TYPE}" 164 | --prefix "${CMKR_DIRECTORY}" 165 | --component cmkr 166 | ) 167 | if(NOT EXISTS ${CMKR_EXECUTABLE}) 168 | message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'") 169 | endif() 170 | cmkr_exec("${CMKR_EXECUTABLE}" version) 171 | message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}") 172 | endif() 173 | execute_process(COMMAND "${CMKR_EXECUTABLE}" version 174 | RESULT_VARIABLE CMKR_EXEC_RESULT 175 | ) 176 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 177 | message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding") 178 | endif() 179 | 180 | # Use cmkr.cmake as a script 181 | if(CMAKE_SCRIPT_MODE_FILE) 182 | if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml") 183 | execute_process(COMMAND "${CMKR_EXECUTABLE}" init 184 | RESULT_VARIABLE CMKR_EXEC_RESULT 185 | ) 186 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 187 | message(FATAL_ERROR "[cmkr] Failed to bootstrap cmkr project. Please report an issue: https://github.com/build-cpp/cmkr/issues/new") 188 | else() 189 | message(STATUS "[cmkr] Modify cmake.toml and then configure using: cmake -B build") 190 | endif() 191 | else() 192 | execute_process(COMMAND "${CMKR_EXECUTABLE}" gen 193 | RESULT_VARIABLE CMKR_EXEC_RESULT 194 | ) 195 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 196 | message(FATAL_ERROR "[cmkr] Failed to generate project.") 197 | else() 198 | message(STATUS "[cmkr] Configure using: cmake -B build") 199 | endif() 200 | endif() 201 | endif() 202 | 203 | # This is the macro that contains black magic 204 | macro(cmkr) 205 | # When this macro is called from the generated file, fake some internal CMake variables 206 | get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE) 207 | if(CMKR_CURRENT_LIST_FILE) 208 | set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}") 209 | get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) 210 | endif() 211 | 212 | # File-based include guard (include_guard is not documented to work) 213 | get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD) 214 | if(NOT CMKR_INCLUDE_GUARD) 215 | set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE) 216 | 217 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE) 218 | 219 | # Generate CMakeLists.txt 220 | cmkr_exec("${CMKR_EXECUTABLE}" gen 221 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 222 | ) 223 | 224 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST) 225 | 226 | # Delete the temporary file if it was left for some reason 227 | set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt") 228 | if(EXISTS "${CMKR_TEMP_FILE}") 229 | file(REMOVE "${CMKR_TEMP_FILE}") 230 | endif() 231 | 232 | if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST) 233 | # Copy the now-generated CMakeLists.txt to CMakerLists.txt 234 | # This is done because you cannot include() a file you are currently in 235 | configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY) 236 | 237 | # Add the macro required for the hack at the start of the cmkr macro 238 | set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES 239 | CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" 240 | ) 241 | 242 | # 'Execute' the newly-generated CMakeLists.txt 243 | include("${CMKR_TEMP_FILE}") 244 | 245 | # Delete the generated file 246 | file(REMOVE "${CMKR_TEMP_FILE}") 247 | 248 | # Do not execute the rest of the original CMakeLists.txt 249 | return() 250 | endif() 251 | # Resume executing the unmodified CMakeLists.txt 252 | endif() 253 | endmacro() 254 | -------------------------------------------------------------------------------- /scripts/camera.lua: -------------------------------------------------------------------------------- 1 | local function find_required_object(name) 2 | local obj = uevr.api:find_uobject(name) 3 | if not obj then 4 | error("Cannot find " .. name) 5 | return nil 6 | end 7 | 8 | return obj 9 | end 10 | 11 | local SHCharacterPlayCameraComponent_c = find_required_object("Class /Script/SHProto.SHCharacterPlayCameraComponent") 12 | local SHGameplaySaveMenuWidget_c = find_required_object("Class /Script/SHProto.SHGameplaySaveMenuWidget") 13 | local SHMapRenderer_c = find_required_object("Class /Script/SHProto.SHMapRenderer") 14 | local SHJumpIntoHole_c = find_required_object("Class /Script/SHProto.SHJumpIntoHole") 15 | local SHTraversalLadder_c = find_required_object("Class /Script/SHProto.SHTraversalLadder") 16 | local SHItemWeaponMelee_c = find_required_object("Class /Script/SHProto.SHItemWeaponMelee") 17 | local SHItemExecutiveBase_c = find_required_object("Class /Script/SHProto.SHItemExecutiveBase") 18 | local SHCameraAnimationExecutive_c = find_required_object("Class /Script/SHProto.SHCameraAnimationExecutive") 19 | local SHAnimCombatSubcomp_c = find_required_object("Class /Script/SHProto.SHAnimCombatSubcomp") 20 | local Material_c = find_required_object("Class /Script/Engine.Material") 21 | 22 | --local SHCharAnimationInstance_c = find_required_object("AnimBlueprintGeneratedClass /Game/Game/Characters/Humans/JamesSunderland/Animation/AnimationBlueprints/CH_JamesAnimBP.CH_JamesAnimBP_C") 23 | 24 | --local AnimNode_Fabrik = find_required_object("ScriptStruct /Script/AnimGraphRuntime.AnimNode_Fabrik") 25 | --print(string.format("0x%llx", AnimNode_Fabrik:get_class_default_object():get_address())) 26 | 27 | local api = uevr.api 28 | local vr = uevr.params.vr 29 | 30 | -- Fix conflicts with other profiles 31 | if UEVR_UObjectHook.remove_all_motion_controller_states ~= nil then 32 | UEVR_UObjectHook.remove_all_motion_controller_states() 33 | end 34 | 35 | --[[local BlueprintUpdateAnimation = SHCharAnimationInstance_c:find_function("BlueprintUpdateAnimation") 36 | 37 | BlueprintUpdateAnimation:hook_ptr(nil, function(fn, obj, locals, result) 38 | obj.UseWaponIK = false 39 | end)]] 40 | 41 | local find_static_class = function(name) 42 | local c = find_required_object(name) 43 | return c:get_class_default_object() 44 | end 45 | 46 | local SHCharacterStatics = find_static_class("Class /Script/SHProto.SHCharacterStatics") 47 | local Statics = find_static_class("Class /Script/Engine.GameplayStatics") 48 | 49 | local SHCrosshairWidget_c = find_required_object("Class /Script/SHProto.SHCrosshairWidget") 50 | local hitresult_c = find_required_object("ScriptStruct /Script/Engine.HitResult") 51 | local empty_hitresult = StructObject.new(hitresult_c) 52 | local reusable_hit_result = StructObject.new(hitresult_c) 53 | 54 | local temp_vec3 = Vector3d.new(0, 0, 0) 55 | local temp_vec3f = Vector3f.new(0, 0, 0) 56 | 57 | local function find_camera_component() 58 | local components = SHCharacterPlayCameraComponent_c:get_objects_matching(false) 59 | if components == nil or #components == 0 then 60 | return nil 61 | end 62 | 63 | for _, component in ipairs(components) do 64 | if component.OwnerCharacter ~= nil then 65 | return component 66 | end 67 | end 68 | 69 | return nil 70 | end 71 | 72 | local function find_save_menu_widget() 73 | local widgets = SHGameplaySaveMenuWidget_c:get_objects_matching(false) 74 | if widgets == nil or #widgets == 0 then 75 | return nil 76 | end 77 | 78 | for _, widget in ipairs(widgets) do 79 | if widget.OwnerCharacter ~= nil then 80 | return widget 81 | end 82 | end 83 | 84 | return nil 85 | end 86 | 87 | local function is_save_menu_open() 88 | local widget = find_save_menu_widget() 89 | if widget == nil then 90 | return false 91 | end 92 | 93 | return widget:IsVisible() and widget:IsRendered() 94 | end 95 | 96 | 97 | local function find_map_renderer() 98 | local renderers = SHMapRenderer_c:get_objects_matching(false) 99 | 100 | if renderers == nil or #renderers == 0 then 101 | return nil 102 | end 103 | 104 | for _, renderer in ipairs(renderers) do 105 | if renderer.Owner ~= nil then 106 | return renderer 107 | end 108 | end 109 | 110 | return nil 111 | end 112 | 113 | local function is_map_open() 114 | local renderer = find_map_renderer() 115 | if renderer == nil then 116 | return false 117 | end 118 | 119 | return true 120 | end 121 | 122 | local function find_jump_into_hole(pawn) 123 | if pawn == nil then return nil end 124 | 125 | local holes = SHJumpIntoHole_c:get_objects_matching(false) 126 | 127 | if holes == nil or #holes == 0 then 128 | return nil 129 | end 130 | 131 | for _, hole in ipairs(holes) do 132 | local InteractingCharacter = hole.InteractingCharacter 133 | if InteractingCharacter == pawn then 134 | return hole 135 | end 136 | end 137 | 138 | return nil 139 | end 140 | 141 | local function is_jumping_into_hole(pawn) 142 | local hole = find_jump_into_hole(pawn) 143 | if hole == nil then 144 | return false 145 | end 146 | 147 | return hole:IsInInteraction() 148 | end 149 | 150 | local function find_traversable_ladder(pawn) 151 | local ladders = SHTraversalLadder_c:get_objects_matching(false) 152 | 153 | if ladders == nil or #ladders == 0 then 154 | return nil 155 | end 156 | 157 | for _, ladder in ipairs(ladders) do 158 | local interacting_character = ladder.InteractingCharacter 159 | if interacting_character == pawn then 160 | return ladder 161 | end 162 | end 163 | 164 | return nil 165 | end 166 | 167 | local function is_using_ladder(pawn) 168 | local ladder = find_traversable_ladder(pawn) 169 | if ladder == nil then 170 | return false 171 | end 172 | 173 | return ladder ~= nil 174 | end 175 | 176 | local function get_investigating_item(pawn) 177 | if pawn == nil then return nil end 178 | 179 | local items = pawn.Items 180 | if items == nil then return nil end 181 | 182 | local item_executive = items.ItemExecutive 183 | if item_executive == nil then return nil end 184 | 185 | local item_context = item_executive.ItemContext 186 | if item_context == nil then return nil end 187 | 188 | return item_context 189 | end 190 | 191 | local SHFlashlight_c = find_required_object("Class /Script/SHProto.SHFlashlight") 192 | 193 | local function get_flashlight(pawn) 194 | if pawn == nil then return nil end 195 | 196 | local items = pawn.Items 197 | if items == nil then return nil end 198 | 199 | local equipment_actors = items.EquipmentActors 200 | if equipment_actors == nil then return nil end 201 | if #equipment_actors == 0 then return nil end 202 | 203 | for _, actor in ipairs(equipment_actors) do 204 | if actor:is_a(SHFlashlight_c) then 205 | return actor 206 | end 207 | end 208 | 209 | return nil 210 | end 211 | 212 | local function attach_flashlight(pawn, should_detach) 213 | should_detach = should_detach or false 214 | local flashlight = get_flashlight(pawn) 215 | if flashlight == nil then return end 216 | 217 | local mesh = flashlight.Mesh 218 | if mesh == nil then return end 219 | 220 | if should_detach then 221 | UEVR_UObjectHook.remove_motion_controller_state(mesh) 222 | else 223 | local mesh_state = UEVR_UObjectHook.get_or_add_motion_controller_state(mesh) 224 | mesh_state:set_hand(0) -- Left hand 225 | mesh_state:set_permanent(false) 226 | mesh_state:set_rotation_offset(temp_vec3f:set(0.175, -1.665, -0.007)) 227 | mesh_state:set_location_offset(temp_vec3f:set(2.376, 5.972, 0.9)) 228 | end 229 | 230 | local light = flashlight.LightMain 231 | if light == nil then return end 232 | 233 | if should_detach then 234 | UEVR_UObjectHook.remove_motion_controller_state(light) 235 | else 236 | local light_state = UEVR_UObjectHook.get_or_add_motion_controller_state(light) 237 | light_state:set_hand(0) -- Left hand 238 | light_state:set_rotation_offset(temp_vec3f:set(0.0, -0.130, 0.0)) 239 | light_state:set_permanent(false) 240 | end 241 | 242 | local light_shaft = flashlight.Lightshaft 243 | if light_shaft == nil then return end 244 | 245 | if should_detach then 246 | UEVR_UObjectHook.remove_motion_controller_state(light_shaft) 247 | else 248 | local light_shaft_state = UEVR_UObjectHook.get_or_add_motion_controller_state(light_shaft) 249 | light_shaft_state:set_hand(0) -- Left hand 250 | light_shaft_state:set_permanent(false) 251 | light_shaft_state:set_rotation_offset(temp_vec3f:set(1.527, -1.723, -1.647)) 252 | light_shaft_state:set_location_offset(temp_vec3f:set(-2.066, -6.140, -5.218)) 253 | end 254 | end 255 | 256 | local enqueue_detach_flashlight = false 257 | 258 | local function detach_flashlight(pawn) 259 | --attach_flashlight(pawn, true) 260 | enqueue_detach_flashlight = true 261 | end 262 | 263 | local last_rot = Vector3d.new(0, 0, 0) 264 | local last_pos = Vector3d.new(0, 0, 0) 265 | 266 | local kismet_string_library = find_static_class("Class /Script/Engine.KismetStringLibrary") 267 | local kismet_math_library = find_static_class("Class /Script/Engine.KismetMathLibrary") 268 | local kismet_system_library = find_static_class("Class /Script/Engine.KismetSystemLibrary") 269 | 270 | local head_fname = kismet_string_library:Conv_StringToName("Face") 271 | local root_fname = kismet_string_library:Conv_StringToName("root") 272 | local miss_fname = kismet_string_library:Conv_StringToName("Miss") 273 | local hit_enemy_fname = kismet_string_library:Conv_StringToName("HitEnemy") 274 | local muzzle_fx_fname = kismet_string_library:Conv_StringToName("FX_muzzle") 275 | local none_fname = kismet_string_library:Conv_StringToName("None") 276 | 277 | 278 | local game_engine_class = find_required_object("Class /Script/Engine.GameEngine") 279 | local widget_component_c = find_required_object("Class /Script/UMG.WidgetComponent") 280 | local scene_component_c = find_required_object("Class /Script/Engine.SceneComponent") 281 | local motion_controller_component_c = find_required_object("Class /Script/HeadMountedDisplay.MotionControllerComponent") 282 | local actor_c = find_required_object("Class /Script/Engine.Actor") 283 | local ftransform_c = find_required_object("ScriptStruct /Script/CoreUObject.Transform") 284 | local temp_transform = StructObject.new(ftransform_c) 285 | 286 | local color_c = find_required_object("ScriptStruct /Script/CoreUObject.LinearColor") 287 | local zero_color = StructObject.new(color_c) 288 | 289 | local SHGameplayItemInvestigationWidget_c = find_required_object("Class /Script/SHProto.SHGameplayItemInvestigationWidget") 290 | 291 | local function find_item_investigation_widget() 292 | local widgets = SHGameplayItemInvestigationWidget_c:get_objects_matching(false) 293 | if widgets == nil or #widgets == 0 then 294 | return nil 295 | end 296 | 297 | for _, widget in ipairs(widgets) do 298 | if widget.OwnerCharacter ~= nil then 299 | return widget 300 | end 301 | end 302 | 303 | return nil 304 | end 305 | 306 | local crosshair_actor = nil 307 | local hmd_actor = nil -- The purpose of the HMD actor is to accurately track the HMD's world transform 308 | local left_hand_actor = nil 309 | local right_hand_actor = nil 310 | local left_hand_component = nil 311 | local right_hand_component = nil 312 | local hmd_component = nil 313 | 314 | local right_hand_widget_component = nil 315 | 316 | local LegacyCameraShake_c = find_required_object("Class /Script/GameplayCameras.LegacyCameraShake") 317 | 318 | -- Disable camera shake 1 319 | local BlueprintUpdateCameraShake = LegacyCameraShake_c:find_function("BlueprintUpdateCameraShake") 320 | 321 | if BlueprintUpdateCameraShake ~= nil then 322 | BlueprintUpdateCameraShake:set_function_flags(BlueprintUpdateCameraShake:get_function_flags() | 0x400) -- Mark as native 323 | BlueprintUpdateCameraShake:hook_ptr(function(fn, obj, locals, result) 324 | obj.ShakeScale = 0.0 325 | 326 | return false 327 | end) 328 | end 329 | 330 | -- Disable camera shake 2 331 | local ReceivePlayShake = LegacyCameraShake_c:find_function("ReceivePlayShake") 332 | 333 | if ReceivePlayShake ~= nil then 334 | ReceivePlayShake:set_function_flags(ReceivePlayShake:get_function_flags() | 0x400) -- Mark as native 335 | ReceivePlayShake:hook_ptr(function(fn, obj, locals, result) 336 | obj.ShakeScale = 0.0 337 | return false 338 | end) 339 | end 340 | 341 | local AnimNotify_MeleeAttackCheck_c = find_required_object("Class /Script/SHProto.AnimNotify_MeleeAttackCheck") 342 | local AnimNotify_AttackHit_c = find_required_object("Class /Script/Being.AnimNotify_AttackHit") 343 | local AnimNotify_MeleeAttackCheck_Notify = AnimNotify_MeleeAttackCheck_c:find_function("Received_Notify") 344 | local AnimNotify_ModifyCombatInputMode_c = find_required_object("Class /Script/SHProto.AnimNotify_ModifyCombatInputMode") 345 | local AnimNotifyEventReference_c = find_required_object("ScriptStruct /Script/Engine.AnimNotifyEventReference") 346 | local SHHitReactionSubcomponent_c = find_required_object("Class /Script/SHProto.SHHitReactionSubcomponent") 347 | local SHMeleeBaseDamage_c = find_required_object("Class /Script/SHProto.SHMeleeBaseDamage") 348 | local DamageType_c = find_required_object("Class /Script/Engine.DamageType") 349 | local SHHitReactionResult_c = find_required_object("ScriptStruct /Script/SHProto.SHHitReactionResult") 350 | local SHHitReactionConfiguration_c = find_required_object("ScriptStruct /Script/SHProto.SHHitReactionConfiguration") 351 | local SHItemWeaponMelee_c = find_required_object("Class /Script/SHProto.SHItemWeaponMelee") 352 | local reusable_hit_configuration = StructObject.new(SHHitReactionConfiguration_c) 353 | local reusable_hit_reaction_result = StructObject.new(SHHitReactionResult_c) 354 | local reusable_anim_notify_ref = StructObject.new(AnimNotifyEventReference_c) 355 | local melee_montage = nil 356 | local ANIMNOTIFY_NOTIFY_VTABLE_INDEX = 88 357 | local MELEE_WEAPON_DAMAGE_TYPE_OFFSET = 0x6D8 -- LightEffect + 0x28, might be a way of automating this 358 | local last_anim_notify_melee_obj = nil 359 | local last_anim_notify_modify_combat_input_obj = nil 360 | 361 | local is_allowing_vr_mode = false 362 | local last_roomscale_value = false 363 | local forward = nil 364 | local last_delta = 1.0 365 | local last_level = nil 366 | 367 | --[[if AnimNotify_MeleeAttackCheck_Notify then 368 | AnimNotify_MeleeAttackCheck_Notify:set_function_flags(AnimNotify_MeleeAttackCheck_Notify:get_function_flags() | 0x400) -- Mark as native 369 | AnimNotify_MeleeAttackCheck_Notify:hook_ptr( 370 | function(fn, obj, locals, result) 371 | if obj:is_a(AnimNotify_MeleeAttackCheck_c) then 372 | last_anim_notify_melee_obj = obj 373 | print(obj:get_full_name()) 374 | local meshcomp = locals.MeshComp 375 | local animation = locals.Animation 376 | 377 | print (" MeshComp: " .. meshcomp:get_full_name()) 378 | print (" Animation: " .. animation:get_full_name()) 379 | 380 | melee_montage = animation 381 | elseif obj:is_a(AnimNotify_AttackHit_c) then 382 | print("Attack hit! " .. obj:get_full_name()) 383 | end 384 | return false 385 | end, 386 | function(fn, obj, locals, result) 387 | 388 | end) 389 | end]] 390 | 391 | local AnimMontage_c = api:find_uobject("Class /Script/Engine.AnimMontage") 392 | local UClass_c = api:find_uobject("Class /Script/CoreUObject.Class") 393 | local melee_attack_name = kismet_string_library:Conv_StringToName("MeleeAttack") 394 | local triggered_melee_recently = false 395 | 396 | local melee_data = { 397 | cooldown_time = 0.0, 398 | accumulated_time = 0.0, 399 | last_tried_melee_time = 1000.0, 400 | right_hand_pos_raw = UEVR_Vector3f.new(), 401 | right_hand_q_raw = UEVR_Quaternionf.new(), 402 | right_hand_pos = Vector3f.new(0, 0, 0), 403 | last_right_hand_raw_pos = Vector3f.new(0, 0, 0), 404 | last_time_messed_with_attack_request = 0.0, 405 | last_enemy_hit_time = 0.0, 406 | root_motion_needs_reset = true, 407 | known_damage_types = {}, 408 | damage_type_dict = {}, 409 | last_weapon = nil, 410 | last_repopulate_attempt = 0.0, 411 | first = true, 412 | enemy_combo_index = 0, 413 | hit_enemy = false, 414 | } 415 | 416 | local function populate_damage_types() 417 | if #melee_data.known_damage_types > 0 then 418 | return 419 | end 420 | 421 | local damage_types = DamageType_c:get_objects_matching(true) -- Include defaults 422 | 423 | for k, v in pairs(damage_types) do 424 | --if v:as_struct() ~= nil then 425 | print("Found damage type: " .. v:get_full_name()) 426 | local c = v:get_class() 427 | table.insert(melee_data.known_damage_types, c) 428 | melee_data.damage_type_dict[c:get_fname():to_string()] = c 429 | --end 430 | end 431 | 432 | melee_data.last_repopulate_attempt = os.clock() 433 | end 434 | 435 | local function lookup_damage_type(name) 436 | populate_damage_types() 437 | 438 | local v = melee_data.damage_type_dict[name] 439 | 440 | if v ~= nil and UEVR_UObjectHook.exists(v) and v:is_a(UClass_c) and v:get_class_default_object():is_a(DamageType_c) then 441 | return v 442 | end 443 | 444 | local now = os.clock() 445 | 446 | if now - melee_data.last_repopulate_attempt > 1.0 then 447 | melee_data.known_damage_types = {} 448 | melee_data.damage_type_dict = {} 449 | populate_damage_types() 450 | end 451 | 452 | return melee_data.damage_type_dict[name] 453 | end 454 | 455 | uevr.sdk.callbacks.on_lua_event(function(event_name, event_string) 456 | if event_name == "OnMeleeTraceSuccess" then 457 | local now = os.clock() 458 | melee_data.accumulated_time = 0.0 459 | melee_data.last_tried_melee_time = 0.0 460 | 461 | if event_string == "Enemy" then 462 | if math.abs(now - melee_data.last_enemy_hit_time) > 1.0 then 463 | if melee_data.enemy_combo_index ~= 1 then 464 | print("Combo reset") 465 | end 466 | melee_data.enemy_combo_index = 0 467 | else 468 | melee_data.enemy_combo_index = ((melee_data.enemy_combo_index + 1) % 3) 469 | print("Combo index: " .. tostring(melee_data.enemy_combo_index)) 470 | end 471 | 472 | melee_data.cooldown_time = 0.5 473 | melee_data.last_enemy_hit_time = now 474 | melee_data.hit_enemy = true 475 | elseif event_string == "Glass" then 476 | melee_data.cooldown_time = 0.5 477 | else 478 | melee_data.cooldown_time = 0.033 -- Environment traces can be more frequent 479 | end 480 | 481 | melee_data.last_tried_melee_time = 1000.0 482 | 483 | if vr.is_using_controllers() then 484 | vr.trigger_haptic_vibration(0, 0.1, 0.5, 1.0, vr.get_right_joystick_source()) 485 | 486 | triggered_melee_recently = true 487 | end 488 | elseif event_name == "OnMeleeHitLeg" then 489 | if melee_data.last_weapon ~= nil then 490 | local pistol_damage = lookup_damage_type("PistolDamage_C") 491 | 492 | if pistol_damage ~= nil then 493 | -- Enable kneecapping damage (makes the enemy fall down sometimes if hit in the leg) 494 | melee_data.last_weapon:write_qword(MELEE_WEAPON_DAMAGE_TYPE_OFFSET, pistol_damage:get_address()) 495 | end 496 | end 497 | end 498 | end) 499 | 500 | uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) 501 | melee_data.last_weapon = nil 502 | 503 | if not right_hand_component or not left_hand_component then 504 | return 505 | end 506 | 507 | local now = os.clock() 508 | vr.get_pose(vr.get_right_controller_index(), melee_data.right_hand_pos_raw, melee_data.right_hand_q_raw) 509 | 510 | -- Copy without creating new userdata 511 | melee_data.right_hand_pos:set(melee_data.right_hand_pos_raw.x, melee_data.right_hand_pos_raw.y, melee_data.right_hand_pos_raw.z) 512 | 513 | if melee_data.first then 514 | melee_data.last_right_hand_raw_pos:set(melee_data.right_hand_pos.x, melee_data.right_hand_pos.y, melee_data.right_hand_pos.z) 515 | melee_data.first = false 516 | end 517 | 518 | local velocity = (melee_data.right_hand_pos - melee_data.last_right_hand_raw_pos) * (1 / delta) 519 | 520 | -- Clone without creating new userdata 521 | melee_data.last_right_hand_raw_pos.x = melee_data.right_hand_pos_raw.x 522 | melee_data.last_right_hand_raw_pos.y = melee_data.right_hand_pos_raw.y 523 | melee_data.last_right_hand_raw_pos.z = melee_data.right_hand_pos_raw.z 524 | melee_data.last_time_messed_with_attack_request = melee_data.last_time_messed_with_attack_request + delta 525 | 526 | local pawn = api:get_local_pawn(0) 527 | 528 | if pawn == nil then 529 | return 530 | end 531 | 532 | if melee_montage == nil or not UEVR_UObjectHook.exists(melee_montage) or not melee_montage:is_a(AnimMontage_c) then 533 | local montages = AnimMontage_c:get_objects_matching(false) 534 | 535 | for i, v in ipairs(montages) do 536 | --if v:get_fname():to_string():find("BreakingWall") then 537 | if v:get_fname():to_string() == "James_SideAttacks_GroundAttack_UpDown" then 538 | --if v:get_fname():to_string() == "James_StealthAttack_Hit1_v2" then 539 | melee_montage = v 540 | print("Found montage") 541 | break 542 | end 543 | end 544 | end 545 | 546 | if not last_anim_notify_melee_obj or not UEVR_UObjectHook.exists(last_anim_notify_melee_obj) or not last_anim_notify_melee_obj:is_a(AnimNotify_MeleeAttackCheck_c) then 547 | local anim_notifies = AnimNotify_MeleeAttackCheck_c:get_objects_matching(false) 548 | 549 | for i, v in ipairs(anim_notifies) do 550 | if v:get_outer() == melee_montage then 551 | last_anim_notify_melee_obj = v 552 | print("Found melee notify obj @ " .. v:get_full_name()) 553 | break 554 | end 555 | end 556 | end 557 | 558 | if not last_anim_notify_modify_combat_input_obj or not UEVR_UObjectHook.exists(last_anim_notify_modify_combat_input_obj) or not last_anim_notify_modify_combat_input_obj:is_a(AnimNotify_ModifyCombatInputMode_c) then 559 | last_anim_notify_modify_combat_input_obj = AnimNotify_ModifyCombatInputMode_c:get_class_default_object() -- Will this work? let's find out 560 | 561 | if last_anim_notify_modify_combat_input_obj then 562 | print("Got combat input notify obj") 563 | end 564 | end 565 | 566 | populate_damage_types() 567 | 568 | if melee_montage and is_allowing_vr_mode then 569 | -- no way in hell we want to use root motion for melee attacks 570 | melee_montage.bEnableRootMotionTranslation = false 571 | --[[melee_montage.bEnableRootMotionTranslation = false 572 | melee_montage.bEnableRootMotionRotation = false 573 | melee_montage.RootMotionRootLock = 0]] 574 | local mesh = pawn.Mesh 575 | local combat = pawn.Combat 576 | 577 | if combat then 578 | if combat:read_byte(0x152) == 1 then 579 | print("Combat input mode is 1") 580 | combat:write_byte(0x152, 0) -- Reset combat input mode so we don't softlock 581 | end 582 | end 583 | 584 | if mesh then 585 | local animation = pawn.Animation 586 | local anim_instance = mesh.AnimScriptInstance 587 | local weapon = anim_instance and anim_instance:GetEquippedWeapon() or nil 588 | local has_melee_weapon = weapon ~= nil and weapon:is_a(SHItemWeaponMelee_c) 589 | 590 | -- Disable root motion 591 | if anim_instance then 592 | local is_playing_melee_attack = has_melee_weapon and anim_instance["Is Playing Melee Attack"](anim_instance, {}, {}, {}, {}, {}) or false 593 | 594 | if is_playing_melee_attack or anim_instance:Montage_IsPlaying(melee_montage) then 595 | anim_instance:SetRootMotionMode(1) -- IgnoreRootMotion 596 | melee_data.root_motion_needs_reset = true 597 | elseif melee_data.root_motion_needs_reset then 598 | -- Enable root motion outside of melee attacks. 599 | anim_instance:SetRootMotionMode(3) -- RootMotionFromMontagesOnly 600 | melee_data.root_motion_needs_reset = false 601 | end 602 | end 603 | 604 | if has_melee_weapon and last_anim_notify_melee_obj then 605 | melee_data.last_weapon = weapon 606 | local combat_anim_subcomp = animation:FindSubcomponentByClass(SHAnimCombatSubcomp_c) 607 | 608 | -- Stops normal melee attacks (aka pressing attack button) 609 | -- We only want attacks to work if we swing the controller 610 | if melee_data.last_time_messed_with_attack_request >= 0.1 and not triggered_melee_recently and combat_anim_subcomp and vr.is_using_controllers() then 611 | local attack = combat_anim_subcomp.Attack 612 | if attack then 613 | local animdata = attack.PlayAnimationData 614 | 615 | if animdata then 616 | local current_montage = attack.CurrentMontage 617 | 618 | if current_montage and attack:IsPlaying() then 619 | local input_data = attack.InputData 620 | 621 | if input_data == nil or input_data:get_full_name():find("BreakingWall") == nil then 622 | animdata.BlendInTime = 0.0 623 | animdata.BlendOutTime = 0.0 624 | anim_instance:Montage_SetPosition(current_montage, current_montage:GetPlayLength()) 625 | 626 | melee_data.last_time_messed_with_attack_request = 0.0 627 | end 628 | end 629 | end 630 | end 631 | end 632 | 633 | if melee_data.accumulated_time > 0.1 then 634 | triggered_melee_recently = false 635 | end 636 | 637 | melee_data.accumulated_time = melee_data.accumulated_time + delta 638 | if melee_data.cooldown_time > 0.0 then 639 | melee_data.cooldown_time = melee_data.cooldown_time - delta 640 | end 641 | local vel_len = velocity:length() 642 | local swinging_fast = vel_len >= 2.5 643 | if melee_data.cooldown_time <= 0.0 and (swinging_fast or math.abs(melee_data.accumulated_time - melee_data.last_tried_melee_time) < 0.1) then 644 | if combat_anim_subcomp then 645 | local attack = combat_anim_subcomp.Attack 646 | if attack and attack.CurrentMontage == nil and attack.InputData == nil then 647 | if last_anim_notify_melee_obj then 648 | if swinging_fast then 649 | melee_data.last_tried_melee_time = melee_data.accumulated_time 650 | end 651 | 652 | melee_data.hit_enemy = false 653 | 654 | -- Setting these allows the AnimNotify to actually hit stuff 655 | attack.CurrentMontage = melee_montage 656 | attack.InputData = melee_montage 657 | 658 | local animdata = attack.PlayAnimationData 659 | 660 | if animdata then 661 | animdata.SlotName = melee_attack_name 662 | --attack:PlayOrOverwriteRequest(0.0, animdata, temp_vec3:set(0, 0, 0)) 663 | end 664 | 665 | --if anim_instance:GetCurrentActiveMontage() ~= melee_montage then 666 | if not anim_instance:Montage_IsPlaying(melee_montage) then 667 | anim_instance:Montage_Play(melee_montage, 1.0, 0, 0.0, false) 668 | end 669 | 670 | -- Triggers Notify function, which does the melee attack traces and damage 671 | reusable_anim_notify_ref:write_qword(0x10, last_anim_notify_melee_obj:get_address()) -- 0x10 is the offset of Notify 672 | reusable_anim_notify_ref.NotifySource = mesh 673 | local last_mesh_context = last_anim_notify_melee_obj:read_qword(0x30) -- 0x30 is the offset of MeshContext 674 | last_anim_notify_melee_obj:write_qword(0x30, mesh:get_address()) -- 0x30 is the offset of MeshContext 675 | 676 | -- We need to directly write the damage type address into the melee weapon 677 | -- This is because the game uses this to determine hit reactions. 678 | -- AFAIK there is no reflected method to do this, so we have to do it manually. 679 | local damage_type_strs = { 680 | "Wdp_Combo_L" .. tostring(melee_data.enemy_combo_index + 1) .. "_DamageType_C", -- This allows us to stumble the enemy first 681 | "IronPipeDamage_C" -- This one allows us to break through breakable walls. I don't know why the previous one doesn't work for this. This one hurts but doesn't stumble 682 | } 683 | local damage_type = nil 684 | 685 | for _, dtype_str in ipairs(damage_type_strs) do 686 | damage_type = lookup_damage_type(dtype_str) 687 | 688 | if damage_type == nil then 689 | print("Failed to find damage type: " .. dtype_str) 690 | else 691 | local defobj = damage_type:get_class_default_object() 692 | if defobj:is_a(SHMeleeBaseDamage_c) then 693 | defobj.bIsGroundHit = true -- Lets attacks hit the ground 694 | end 695 | 696 | weapon:write_qword(MELEE_WEAPON_DAMAGE_TYPE_OFFSET, damage_type:get_address()) 697 | end 698 | 699 | local pcall_res = pcall(function() 700 | last_anim_notify_melee_obj:DANGEROUS_call_member_virtual(ANIMNOTIFY_NOTIFY_VTABLE_INDEX + 1, mesh, melee_montage, reusable_anim_notify_ref) 701 | end) 702 | 703 | if not pcall_res then 704 | print("Failed to call melee notify") 705 | end 706 | 707 | if melee_data.hit_enemy then 708 | break 709 | end 710 | end 711 | 712 | -- Reset damage type 713 | weapon:write_qword(MELEE_WEAPON_DAMAGE_TYPE_OFFSET, 0) 714 | 715 | -- Reset mesh context 716 | last_anim_notify_melee_obj:write_qword(0x30, last_mesh_context) 717 | 718 | -- Reset the attack request back so we don't break something 719 | attack.CurrentMontage = nil 720 | attack.InputData = nil 721 | 722 | if not triggered_melee_recently then 723 | vr.trigger_haptic_vibration(0, 0.1, 0.1, 0.1, vr.get_right_joystick_source()) -- Very light vibration 724 | end 725 | end 726 | end 727 | end 728 | end 729 | end 730 | end 731 | end 732 | end) 733 | 734 | local function spawn_actor(world_context, actor_class, location, collision_method, owner) 735 | temp_transform.Translation = location 736 | temp_transform.Rotation.W = 1.0 737 | temp_transform.Scale3D = temp_vec3:set(1.0, 1.0, 1.0) 738 | 739 | local actor = Statics:BeginDeferredActorSpawnFromClass(world_context, actor_class, temp_transform, collision_method, owner) 740 | 741 | if actor == nil then 742 | print("Failed to spawn actor") 743 | return nil 744 | end 745 | 746 | Statics:FinishSpawningActor(actor, temp_transform) 747 | print("Spawned actor") 748 | 749 | return actor 750 | end 751 | 752 | local function reset_crosshair_actor() 753 | if crosshair_actor ~= nil and UEVR_UObjectHook.exists(crosshair_actor) then 754 | pcall(function() 755 | if crosshair_actor.K2_DestroyActor ~= nil then 756 | crosshair_actor:K2_DestroyActor() 757 | end 758 | end) 759 | end 760 | 761 | crosshair_actor = nil 762 | end 763 | 764 | local function reset_crosshair_actor_if_deleted() 765 | if crosshair_actor ~= nil and not UEVR_UObjectHook.exists(crosshair_actor) then 766 | crosshair_actor = nil 767 | end 768 | end 769 | 770 | local function setup_crosshair_actor(game_engine, pawn, crosshair_widget) 771 | local viewport = game_engine.GameViewport 772 | if viewport == nil then 773 | print("Viewport is nil") 774 | return 775 | end 776 | 777 | local world = viewport.World 778 | if world == nil then 779 | print("World is nil") 780 | return 781 | end 782 | 783 | reset_crosshair_actor() 784 | 785 | local pos = pawn:K2_GetActorLocation() 786 | crosshair_actor = spawn_actor(world, actor_c, pos, 1, nil) 787 | 788 | if crosshair_actor == nil then 789 | print("Failed to spawn actor") 790 | return 791 | end 792 | 793 | print("Spawned actor") 794 | 795 | temp_transform.Translation = pos 796 | temp_transform.Rotation.W = 1.0 797 | temp_transform.Scale3D = temp_vec3:set(1.0, 1.0, 1.0) 798 | local widget_component = crosshair_actor:AddComponentByClass(widget_component_c, false, temp_transform, false) 799 | 800 | if widget_component == nil then 801 | print("Failed to add widget component") 802 | return 803 | end 804 | 805 | print("Added widget component") 806 | 807 | -- Add crosshair widget to the widget component 808 | crosshair_widget:RemoveFromViewport() 809 | 810 | 811 | --local wanted_mat_name = "Material /Engine/EngineMaterials/Widget3DPassThrough.Widget3DPassThrough" 812 | local wanted_mat_name = "Material /Engine/EngineMaterials/GizmoMaterial.GizmoMaterial" 813 | local wanted_mat = api:find_uobject(wanted_mat_name) 814 | 815 | widget_component:SetWidget(crosshair_widget) 816 | widget_component:SetVisibility(true) 817 | widget_component:SetHiddenInGame(false) 818 | widget_component:SetCollisionEnabled(0) 819 | 820 | --crosshair_widget:AddToViewport(0) 821 | 822 | widget_component:SetRenderCustomDepth(true) 823 | widget_component:SetCustomDepthStencilValue(100) 824 | widget_component:SetCustomDepthStencilWriteMask(1) 825 | widget_component:SetRenderInDepthPass(false) 826 | 827 | if wanted_mat then 828 | wanted_mat.bDisableDepthTest = true 829 | wanted_mat.BlendMode = 2 830 | --wanted_mat.MaterialDomain = 0 831 | --wanted_mat.ShadingModel = 0 832 | widget_component:SetMaterial(1, wanted_mat) 833 | end 834 | 835 | widget_component.BlendMode = 2 836 | 837 | crosshair_actor:FinishAddComponent(widget_component, false, temp_transform) 838 | widget_component:SetWidget(crosshair_widget) 839 | 840 | -- Disable depth testing 841 | --[[widget_component:SetRenderCustomDepth(true) 842 | widget_component:SetCustomDepthStencilValue(100) 843 | widget_component:SetCustomDepthStencilWriteMask(1) 844 | widget_component:SetRenderCustomDepth(true) 845 | widget_component:SetRenderInDepthPass(false)]] 846 | --widget_component.BlendMode = 2 847 | widget_component:SetTwoSided(true) 848 | 849 | 850 | print("Widget space: " .. tostring(widget_component.Space)) 851 | print("Widget draw size: X=" .. widget_component.DrawSize.X .. ", Y=" .. widget_component.DrawSize.Y) 852 | print("Widget visibility: " .. tostring(widget_component:IsVisible())) 853 | end 854 | 855 | local function reset_hand_actors() 856 | -- We are using pcall on this because for some reason the actors are not always valid 857 | -- even if exists returns true 858 | if left_hand_actor ~= nil and UEVR_UObjectHook.exists(left_hand_actor) then 859 | pcall(function() 860 | if left_hand_actor.K2_DestroyActor ~= nil then 861 | left_hand_actor:K2_DestroyActor() 862 | end 863 | end) 864 | end 865 | 866 | if right_hand_actor ~= nil and UEVR_UObjectHook.exists(right_hand_actor) then 867 | pcall(function() 868 | if right_hand_actor.K2_DestroyActor ~= nil then 869 | right_hand_actor:K2_DestroyActor() 870 | end 871 | end) 872 | end 873 | 874 | if hmd_actor ~= nil and UEVR_UObjectHook.exists(hmd_actor) then 875 | pcall(function() 876 | if hmd_actor.K2_DestroyActor ~= nil then 877 | hmd_actor:K2_DestroyActor() 878 | end 879 | end) 880 | end 881 | 882 | left_hand_actor = nil 883 | right_hand_actor = nil 884 | hmd_actor = nil 885 | right_hand_component = nil 886 | left_hand_component = nil 887 | end 888 | 889 | local function reset_hand_actors_if_deleted() 890 | if left_hand_actor ~= nil and not UEVR_UObjectHook.exists(left_hand_actor) then 891 | left_hand_actor = nil 892 | left_hand_component = nil 893 | end 894 | 895 | if right_hand_actor ~= nil and not UEVR_UObjectHook.exists(right_hand_actor) then 896 | right_hand_actor = nil 897 | right_hand_component = nil 898 | end 899 | 900 | if hmd_actor ~= nil and not UEVR_UObjectHook.exists(hmd_actor) then 901 | hmd_actor = nil 902 | hmd_component = nil 903 | end 904 | end 905 | 906 | local function spawn_hand_actors() 907 | local game_engine = UEVR_UObjectHook.get_first_object_by_class(game_engine_class) 908 | 909 | local viewport = game_engine.GameViewport 910 | if viewport == nil then 911 | print("Viewport is nil") 912 | return 913 | end 914 | 915 | local world = viewport.World 916 | if world == nil then 917 | print("World is nil") 918 | return 919 | end 920 | 921 | reset_hand_actors() 922 | 923 | local pawn = api:get_local_pawn(0) 924 | 925 | if pawn == nil then 926 | --print("Pawn is nil") 927 | return 928 | end 929 | 930 | local pos = pawn:K2_GetActorLocation() 931 | 932 | left_hand_actor = spawn_actor(world, actor_c, pos, 1, nil) 933 | 934 | if left_hand_actor == nil then 935 | print("Failed to spawn left hand actor") 936 | return 937 | end 938 | 939 | right_hand_actor = spawn_actor(world, actor_c, pos, 1, nil) 940 | 941 | if right_hand_actor == nil then 942 | print("Failed to spawn right hand actor") 943 | return 944 | end 945 | 946 | hmd_actor = spawn_actor(world, actor_c, pos, 1, nil) 947 | 948 | if hmd_actor == nil then 949 | print("Failed to spawn hmd actor") 950 | return 951 | end 952 | 953 | print("Spawned hand actors") 954 | 955 | -- Add scene components to the hand actors 956 | --left_hand_component = left_hand_actor:AddComponentByClass(scene_component_c, false, temp_transform, false) 957 | --right_hand_component = right_hand_actor:AddComponentByClass(scene_component_c, false, temp_transform, false) 958 | left_hand_component = left_hand_actor:AddComponentByClass(motion_controller_component_c, false, temp_transform, false) 959 | right_hand_component = right_hand_actor:AddComponentByClass(motion_controller_component_c, false, temp_transform, false) 960 | hmd_component = hmd_actor:AddComponentByClass(scene_component_c, false, temp_transform, false) 961 | 962 | temp_transform.Translation = temp_vec3:set(0, 0, 0) 963 | temp_transform.Rotation.W = 1.0 964 | temp_transform.Scale3D = temp_vec3:set(0.3, 0.3, 0.3) 965 | right_hand_widget_component = right_hand_actor:AddComponentByClass(widget_component_c, false, temp_transform, false) 966 | 967 | if left_hand_component == nil then 968 | print("Failed to add left hand scene component") 969 | return 970 | end 971 | 972 | if right_hand_component == nil then 973 | print("Failed to add right hand scene component") 974 | return 975 | end 976 | 977 | if hmd_component == nil then 978 | print("Failed to add hmd scene component") 979 | return 980 | end 981 | 982 | if right_hand_widget_component == nil then 983 | print("Failed to add right hand widget component") 984 | return 985 | end 986 | 987 | left_hand_component.MotionSource = kismet_string_library:Conv_StringToName("Left") 988 | right_hand_component.MotionSource = kismet_string_library:Conv_StringToName("Right") 989 | left_hand_component.Hand = 0 990 | right_hand_component.Hand = 1 991 | 992 | print("Added scene components") 993 | 994 | left_hand_actor:FinishAddComponent(left_hand_component, false, temp_transform) 995 | right_hand_actor:FinishAddComponent(right_hand_component, false, temp_transform) 996 | hmd_actor:FinishAddComponent(hmd_component, false, temp_transform) 997 | 998 | right_hand_widget_component:SetVisibility(true) 999 | right_hand_widget_component:SetHiddenInGame(false) 1000 | right_hand_widget_component:SetCollisionEnabled(0) 1001 | right_hand_widget_component:SetRenderInDepthPass(false) 1002 | right_hand_widget_component:SetTwoSided(true) 1003 | 1004 | -- Locate all materials that have an Unlit shading model 1005 | --[[local all_materials = Material_c:get_objects_matching(false) 1006 | local unlit_materials = {} 1007 | 1008 | for i, v in ipairs(all_materials) do 1009 | if v.ShadingModel == 0 then 1010 | print("Found unlit material: " .. v:get_full_name()) 1011 | table.insert(unlit_materials, v) 1012 | end 1013 | end 1014 | 1015 | local wanted_mat = unlit_materials[1] 1016 | 1017 | right_hand_widget_component:SetMaterial(1, wanted_mat)]] 1018 | 1019 | right_hand_widget_component.BlendMode = 1 1020 | right_hand_widget_component.Space = 0 -- World 1021 | right_hand_widget_component.LightingChannels.bChannel0 = false 1022 | --right_hand_widget_component.MaskedMaterial = wanted_mat 1023 | --right_hand_widget_component.OverlayMaterial = wanted_mat 1024 | right_hand_widget_component.bCastContactShadow = false 1025 | right_hand_widget_component.bCastDynamicShadow = false 1026 | right_hand_widget_component.bCastStaticShadow = false 1027 | right_hand_widget_component.bAffectDynamicIndirectLighting = false 1028 | right_hand_widget_component.bAffectDistanceFieldLighting = false 1029 | right_hand_actor:FinishAddComponent(right_hand_widget_component, false, temp_transform) 1030 | 1031 | --right_hand_widget_component:K2_AttachToComponent(right_hand_component, none_fname, 0, 0, 0, false) 1032 | 1033 | right_hand_widget_component:K2_SetRelativeLocation(temp_vec3:set(0, 0, 0), false, reusable_hit_result, false) 1034 | right_hand_widget_component:K2_SetRelativeRotation(temp_vec3:set(45, 180, 0), false, reusable_hit_result, false) 1035 | 1036 | -- We don't need to add these, UObjectHook will handle the placement of motion controller components automatically 1037 | --[[local leftstate = UEVR_UObjectHook.get_or_add_motion_controller_state(left_hand_component) 1038 | 1039 | if leftstate then 1040 | leftstate:set_hand(0) -- Left hand 1041 | leftstate:set_permanent(true) 1042 | end 1043 | 1044 | local rightstate = UEVR_UObjectHook.get_or_add_motion_controller_state(right_hand_component) 1045 | 1046 | if rightstate then 1047 | rightstate:set_hand(1) -- Right hand 1048 | rightstate:set_permanent(true) 1049 | end]] 1050 | 1051 | -- The HMD is the only one we need to add manually as UObjectHook doesn't support motion controller components as the HMD 1052 | local hmdstate = UEVR_UObjectHook.get_or_add_motion_controller_state(hmd_component) 1053 | 1054 | if hmdstate then 1055 | hmdstate:set_hand(2) -- HMD 1056 | hmdstate:set_permanent(true) 1057 | end 1058 | end 1059 | 1060 | local camera_component = nil 1061 | local my_pawn = nil 1062 | local is_paused = false 1063 | local is_in_cutscene = false 1064 | local mesh = nil 1065 | local movement_component = nil 1066 | local head_pos = nil 1067 | local pawn_pos = nil 1068 | local anim_instance = nil 1069 | local is_in_full_body_anim = false 1070 | local last_crosshair_widget = nil 1071 | local investigating_item = nil 1072 | 1073 | vr.set_mod_value("VR_RoomscaleMovement", "false") 1074 | vr.set_mod_value("VR_AimMethod", "0") 1075 | vr.set_mod_value("VR_RenderingMethod", "0") -- Native Stereo. Synced sequential doesn't work atm but people shouldnt be using that anyways 1076 | vr.set_mod_value("VR_CameraForwardOffset", "0.0") 1077 | vr.set_mod_value("VR_CameraUpOffset", "0.0") 1078 | vr.set_mod_value("VR_CameraRightOffset", "0.0") 1079 | vr.set_mod_value("UI_Distance", "1.0") 1080 | vr.set_mod_value("UI_Size", "1.0") 1081 | 1082 | api:execute_command("r.Streamline.DLSSG.Enable 0") -- Disable DLSSG, not compatible with VR 1083 | api:execute_command("r.FidelityFX.FI.Enabled 0") -- Disable FSR3 frame interpolation, not compatible with VR 1084 | 1085 | local function should_vr_mode() 1086 | anim_instance = nil 1087 | movement_component = nil 1088 | my_pawn = api:get_local_pawn(0) 1089 | 1090 | if not my_pawn then 1091 | mesh = nil 1092 | investigating_item = nil 1093 | 1094 | if last_crosshair_widget then 1095 | last_crosshair_widget:AddToViewport(0) 1096 | last_crosshair_widget = nil 1097 | end 1098 | 1099 | return false 1100 | end 1101 | 1102 | mesh = my_pawn.Mesh 1103 | 1104 | if not mesh then 1105 | return false 1106 | end 1107 | 1108 | movement_component = my_pawn.Movement 1109 | investigating_item = get_investigating_item(my_pawn) 1110 | 1111 | if not vr.is_hmd_active() then 1112 | if last_crosshair_widget then 1113 | last_crosshair_widget:AddToViewport(0) 1114 | last_crosshair_widget = nil 1115 | end 1116 | 1117 | return false 1118 | end 1119 | 1120 | -- This means we're pushing something 1121 | -- if roomscale movement is on during this it will softlock the game 1122 | if movement_component.PushableComponent then 1123 | return false 1124 | end 1125 | 1126 | if is_jumping_into_hole(my_pawn) then 1127 | return false 1128 | end 1129 | 1130 | if is_using_ladder(my_pawn) then 1131 | return false 1132 | end 1133 | 1134 | camera_component = find_camera_component() 1135 | if not camera_component then 1136 | return false 1137 | end 1138 | 1139 | is_paused = Statics:IsGamePaused(camera_component) 1140 | 1141 | if is_paused then 1142 | return false 1143 | end 1144 | 1145 | is_in_cutscene = SHCharacterStatics:IsCharacterInCutscene(my_pawn) 1146 | 1147 | if is_in_cutscene then 1148 | return false 1149 | end 1150 | 1151 | local animation = my_pawn.Animation 1152 | 1153 | if animation then 1154 | anim_instance = animation.AnimInstance 1155 | 1156 | --[[if anim_instance and anim_instance:IsAnyMontagePlaying() then 1157 | return false 1158 | end]] 1159 | end 1160 | 1161 | if my_pawn.bHidden == true then return false end 1162 | 1163 | if is_save_menu_open() then 1164 | return false 1165 | end 1166 | 1167 | local traversal = my_pawn.Traversal 1168 | 1169 | -- Like jumping over objects and crawling under stuff 1170 | if traversal and traversal.CurrentlyPlayingTraversal ~= nil then 1171 | return false 1172 | end 1173 | 1174 | local controller = my_pawn:GetController() 1175 | 1176 | if controller ~= nil then 1177 | local camera_manager = controller.PlayerCameraManager 1178 | 1179 | if camera_manager ~= nil then 1180 | local view_target_struct = camera_manager.ViewTarget 1181 | 1182 | if view_target_struct ~= nil then 1183 | local target = view_target_struct.Target 1184 | 1185 | if target ~= nil and target ~= my_pawn then 1186 | local allowed = { 1187 | SHItemExecutiveBase_c -- Looking at map, items, etc 1188 | } 1189 | 1190 | local any = false 1191 | 1192 | for _, klass in ipairs(allowed) do 1193 | if target:is_a(klass) then 1194 | any = true 1195 | break 1196 | end 1197 | end 1198 | 1199 | if not any then 1200 | return false 1201 | end 1202 | end 1203 | end 1204 | end 1205 | end 1206 | 1207 | return true 1208 | end 1209 | 1210 | local function on_level_changed(new_level) 1211 | -- All actors can be assumed to be deleted when the level changes 1212 | print("Level changed") 1213 | if new_level then 1214 | print("New level: " .. new_level:get_full_name()) 1215 | end 1216 | crosshair_actor = nil 1217 | left_hand_actor = nil 1218 | right_hand_actor = nil 1219 | left_hand_component = nil 1220 | right_hand_component = nil 1221 | 1222 | melee_data.root_motion_needs_reset = true 1223 | end 1224 | 1225 | uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) 1226 | local viewport = engine.GameViewport 1227 | 1228 | if viewport then 1229 | local world = viewport.World 1230 | 1231 | if world then 1232 | local level = world.PersistentLevel 1233 | 1234 | if last_level ~= level then 1235 | on_level_changed(level) 1236 | end 1237 | 1238 | last_level = level 1239 | end 1240 | end 1241 | 1242 | last_delta = delta 1243 | reset_crosshair_actor_if_deleted() 1244 | reset_hand_actors_if_deleted() 1245 | 1246 | if left_hand_actor == nil or right_hand_actor == nil then 1247 | spawn_hand_actors() 1248 | end 1249 | 1250 | if not vr.is_hmd_active() then 1251 | reset_crosshair_actor() 1252 | --reset_hand_actors() 1253 | return 1254 | end 1255 | 1256 | vr.set_mod_value("VR_RenderingMethod", "0") -- Native Stereo. Synced sequential doesn't work atm but people shouldnt be using that anyways 1257 | vr.set_mod_value("VR_CameraForwardOffset", "0.0") -- Reset camera offsets so weapons look correct and pivot positions are correct 1258 | vr.set_mod_value("VR_CameraUpOffset", "0.0") 1259 | vr.set_mod_value("VR_CameraRightOffset", "0.0") 1260 | 1261 | local pawn = api:get_local_pawn(0) 1262 | 1263 | if not pawn then 1264 | reset_crosshair_actor() 1265 | return 1266 | end 1267 | 1268 | local combat = pawn.Combat 1269 | 1270 | if combat then 1271 | local defence_settings = combat.DefenceSettings 1272 | 1273 | if defence_settings then 1274 | defence_settings.bDodgeUseViewSnapOnEnemy = false -- Stops dodging from jarringly moving the camera 1275 | end 1276 | end 1277 | 1278 | -- Set displayed item text in world space 1279 | local item_investigation_widget = find_item_investigation_widget() 1280 | 1281 | if item_investigation_widget and right_hand_widget_component then 1282 | local main_container = item_investigation_widget.MainContainer 1283 | if main_container ~= nil and main_container:IsRendered() then 1284 | right_hand_widget_component:SetWidget(item_investigation_widget.ItemNameTextBlock) 1285 | --item_investigation_widget.ItemNameTextBlock:SetFontMaterial(nil) 1286 | else 1287 | right_hand_widget_component:SetWidget(nil) 1288 | end 1289 | end 1290 | 1291 | local crosshair_widgets = SHCrosshairWidget_c:get_objects_matching(false) 1292 | 1293 | -- Find first widget that has a valid OwnerCharacter 1294 | local crosshair_widget = nil 1295 | for _, widget in ipairs(crosshair_widgets) do 1296 | if widget.OwnerCharacter ~= nil then 1297 | crosshair_widget = widget 1298 | break 1299 | end 1300 | end 1301 | 1302 | if crosshair_widget == nil then 1303 | reset_crosshair_actor() 1304 | return 1305 | end 1306 | 1307 | if crosshair_actor == nil or last_crosshair_widget ~= crosshair_widget then 1308 | setup_crosshair_actor(engine, pawn, crosshair_widget) 1309 | end 1310 | 1311 | last_crosshair_widget = crosshair_widget 1312 | end) 1313 | 1314 | -- Might not be needed? 1315 | local function should_display_mesh() 1316 | if not my_pawn or not mesh then 1317 | return false 1318 | end 1319 | 1320 | if investigating_item ~= nil or is_map_open() then 1321 | return false 1322 | end 1323 | 1324 | if SHCharacterStatics:IsCharacterInCutscene(my_pawn) == true then 1325 | return true 1326 | end 1327 | 1328 | if is_save_menu_open() or is_jumping_into_hole(my_pawn) then 1329 | return true 1330 | end 1331 | 1332 | if my_pawn.Traversal then 1333 | return true 1334 | end 1335 | 1336 | if my_pawn.Movement and my_pawn.Movement.PushableComponent then 1337 | return true 1338 | end 1339 | 1340 | if my_pawn.bHidden == true then 1341 | return false 1342 | end 1343 | 1344 | return false 1345 | end 1346 | 1347 | local last_camera_x = 0.0 1348 | local last_camera_y = 0.0 1349 | local last_head_z = nil 1350 | local is_using_two_handed_weapon = false 1351 | local is_using_melee_weapon = false 1352 | local should_attach_flashlight = true 1353 | local last_vr_enable_value = true 1354 | 1355 | local item_flip_map = {} 1356 | 1357 | local MAX_LERP_DIST = 500.0 1358 | 1359 | uevr.sdk.callbacks.on_early_calculate_stereo_view_offset(function(device, view_index, world_to_meters, position, rotation, is_double) 1360 | is_allowing_vr_mode = should_vr_mode() 1361 | 1362 | if not is_allowing_vr_mode then 1363 | UEVR_UObjectHook.set_disabled(true) 1364 | last_camera_x = position.x 1365 | last_camera_y = position.y 1366 | last_head_z = position.z -- So we dont get weird lerping from across the map when we re-enable VR mode 1367 | 1368 | if last_roomscale_value == true or last_vr_enable_value == true then 1369 | if camera_component then 1370 | -- Reset relative location 1371 | camera_component:K2_SetRelativeLocation(temp_vec3:set(0, 0, 0), false, empty_hitresult, false) 1372 | end 1373 | 1374 | if my_pawn ~= nil then 1375 | detach_flashlight(my_pawn) 1376 | end 1377 | 1378 | vr.set_mod_value("VR_RoomscaleMovement", "false") 1379 | vr.set_mod_value("VR_AimMethod", "0") 1380 | 1381 | if is_save_menu_open() then 1382 | vr.set_mod_value("VR_DecoupledPitch", "false") 1383 | end 1384 | 1385 | --if mesh and should_display_mesh() then 1386 | if my_pawn and mesh then 1387 | mesh:SetRenderInMainPass(true) 1388 | mesh:SetRenderInDepthPass(true) 1389 | mesh:SetRenderCustomDepth(true) 1390 | end 1391 | 1392 | local animation = my_pawn and my_pawn.Animation or nil 1393 | 1394 | if animation then 1395 | local instance = animation.AnimInstance 1396 | 1397 | if instance then 1398 | instance:SetRootMotionMode(2) -- Default 1399 | melee_data.root_motion_needs_reset = true 1400 | print("Reset root motion mode") 1401 | end 1402 | end 1403 | end 1404 | 1405 | last_roomscale_value = false 1406 | last_vr_enable_value = false 1407 | 1408 | return 1409 | end 1410 | 1411 | last_vr_enable_value = true 1412 | 1413 | if not mesh then 1414 | return 1415 | end 1416 | 1417 | local back_offset = 0.0 1418 | local using_map = is_map_open() 1419 | 1420 | UEVR_UObjectHook.set_disabled(false) 1421 | 1422 | should_attach_flashlight = true 1423 | 1424 | if investigating_item ~= nil then 1425 | should_attach_flashlight = false 1426 | detach_flashlight(my_pawn) 1427 | 1428 | mesh:SetRenderInMainPass(false) 1429 | mesh:SetRenderInDepthPass(false) 1430 | mesh:SetRenderCustomDepth(false) 1431 | 1432 | if using_map then 1433 | -- Realistic two handed map holding 1434 | local right_hand_pos = right_hand_component:K2_GetComponentLocation() 1435 | local left_hand_pos = left_hand_component:K2_GetComponentLocation() 1436 | local dir_to_right_hand = (right_hand_pos - left_hand_pos):normalized() 1437 | local average_hand_up = ((left_hand_component:GetUpVector() + right_hand_component:GetUpVector()) * 0.5):normalized() 1438 | local rotation = kismet_math_library:MakeRotFromXZ(dir_to_right_hand, average_hand_up * -1.0) 1439 | 1440 | local rotation_q = kismet_math_library:Conv_RotatorToQuaternion(rotation) 1441 | 1442 | local tilt_angle = 45 1443 | local tilt_q = kismet_math_library:Quat_MakeFromEuler(temp_vec3:set(tilt_angle, 0, 0)) 1444 | rotation_q = kismet_math_library:Multiply_QuatQuat(rotation_q, tilt_q) 1445 | rotation = kismet_math_library:Quat_Rotator(rotation_q) 1446 | 1447 | investigating_item:K2_SetActorRotation(rotation, false, empty_hitresult, false) 1448 | 1449 | local average_hand_pos = (right_hand_pos + left_hand_pos) * 0.5 1450 | investigating_item:K2_SetActorLocation(average_hand_pos, false, empty_hitresult, false) 1451 | else 1452 | -- Simplistic right handed item holding 1453 | local right_hand_pos = right_hand_component:K2_GetComponentLocation() 1454 | local right_hand_rot = right_hand_component:K2_GetComponentRotation() 1455 | local rotation_q = kismet_math_library:Conv_RotatorToQuaternion(right_hand_rot) 1456 | local tilt_angle = 90 1457 | local flip_angle = 0 1458 | local item_mesh = investigating_item.Mesh 1459 | 1460 | -- Calculate flip angle 1461 | if item_mesh then 1462 | local anim_script_instance = item_mesh.AnimScriptInstance 1463 | 1464 | if anim_script_instance then 1465 | -- First we need to find the ItemFlipCurrentProgress property 1466 | -- We do this because it has random numbers in the name 1467 | local c = anim_script_instance:get_class() 1468 | local caddr = c:get_address() 1469 | if item_flip_map[caddr] == nil then 1470 | local parent = c 1471 | local found = false 1472 | while parent ~= nil do 1473 | local prop = parent:get_child_properties() 1474 | while prop ~= nil do 1475 | if prop:get_fname():to_string():find("ItemFlipCurrentProgress") then 1476 | item_flip_map[caddr] = { result = prop:get_fname():to_string() } 1477 | found = true 1478 | print("Found flip property: " .. prop:get_fname():to_string()) 1479 | break 1480 | end 1481 | prop = prop:get_next() 1482 | end 1483 | 1484 | if found then break end 1485 | parent = parent:get_super() 1486 | end 1487 | 1488 | if not found then 1489 | print("Could not find flip property in class " .. c:get_full_name()) 1490 | item_flip_map[caddr] = { result = nil } 1491 | end 1492 | end 1493 | 1494 | local flip_prop = item_flip_map[caddr].result 1495 | 1496 | if flip_prop ~= nil then 1497 | local flip_progress = anim_script_instance[flip_prop] 1498 | if flip_progress then 1499 | flip_angle = flip_progress * 180 1500 | end 1501 | end 1502 | end 1503 | end 1504 | 1505 | local tilt_q = kismet_math_library:Quat_MakeFromEuler(temp_vec3:set(0, flip_angle, tilt_angle)) 1506 | rotation_q = kismet_math_library:Multiply_QuatQuat(rotation_q, tilt_q) 1507 | right_hand_rot = kismet_math_library:Quat_Rotator(rotation_q) 1508 | investigating_item:K2_SetActorLocation(right_hand_pos, false, empty_hitresult, false) 1509 | investigating_item:K2_SetActorRotation(right_hand_rot, false, empty_hitresult, false) 1510 | end 1511 | 1512 | --back_offset = -25.0 1513 | vr.set_mod_value("VR_RoomscaleMovement", "false") 1514 | last_roomscale_value = false 1515 | else 1516 | if last_roomscale_value == false then 1517 | vr.set_mod_value("VR_RoomscaleMovement", "true") 1518 | vr.set_mod_value("VR_DecoupledPitch", "true") 1519 | vr.set_mod_value("VR_AimMethod", "0") 1520 | last_roomscale_value = true 1521 | end 1522 | 1523 | -- Hide mesh always 1524 | if my_pawn and mesh then 1525 | mesh:SetRenderInMainPass(false) 1526 | mesh:SetRenderInDepthPass(false) 1527 | mesh:SetRenderCustomDepth(false) 1528 | end 1529 | end 1530 | 1531 | local head_rot = mesh:GetSocketRotation(head_fname) 1532 | forward = kismet_math_library:Conv_RotatorToVector(head_rot) 1533 | forward = kismet_math_library:RotateAngleAxis(forward, 90, temp_vec3:set(0, 0, 1)) 1534 | 1535 | --pawn_pos = my_pawn:K2_GetActorLocation() 1536 | --pawn_pos = mesh:K2_GetComponentLocation() 1537 | --pawn_pos = mesh:GetSocketLocation(root_fname) 1538 | pawn_pos = my_pawn.RootComponent:K2_GetComponentLocation() 1539 | head_pos = mesh:GetSocketLocation(head_fname) 1540 | head_pos.Z = pawn_pos.Z + 65.0 1541 | 1542 | if not last_head_z then 1543 | last_head_z = head_pos.Z 1544 | end 1545 | 1546 | local wanted_x = pawn_pos.X + (back_offset * forward.X) 1547 | local wanted_y = pawn_pos.Y + (back_offset * forward.Y) 1548 | local wanted_z = head_pos.Z 1549 | --local camera_x = last_camera_x + ((wanted_x - last_camera_x) * (last_delta * 4)) 1550 | --local camera_y = last_camera_y + ((wanted_y - last_camera_y) * (last_delta * 4)) 1551 | local camera_x = wanted_x -- This is okay because we've figured out how to disable root motion 1552 | local camera_y = wanted_y 1553 | local head_z = last_head_z + ((wanted_z - last_head_z) * (last_delta * 4)) 1554 | position.x = camera_x 1555 | position.y = camera_y 1556 | position.z = head_z 1557 | 1558 | if view_index == 1 then 1559 | if math.abs(wanted_x - camera_x) > MAX_LERP_DIST or math.abs(wanted_y - camera_y) > MAX_LERP_DIST then 1560 | last_camera_x = wanted_x 1561 | last_camera_y = wanted_y 1562 | else 1563 | last_camera_x = camera_x 1564 | last_camera_y = camera_y 1565 | end 1566 | 1567 | if math.abs(wanted_z - head_z) > MAX_LERP_DIST then 1568 | last_head_z = wanted_z 1569 | else 1570 | last_head_z = head_z 1571 | end 1572 | end 1573 | 1574 | if view_index == 1 then 1575 | local game_engine = UEVR_UObjectHook.get_first_object_by_class(game_engine_class) 1576 | 1577 | local viewport = game_engine.GameViewport 1578 | if viewport == nil then 1579 | print("Viewport is nil") 1580 | return 1581 | end 1582 | 1583 | local world = viewport.World 1584 | if world == nil then 1585 | print("World is nil") 1586 | return 1587 | end 1588 | 1589 | -- This is the real game render rotation before any VR modifications 1590 | last_rot:set(rotation.x, rotation.y, rotation.z) 1591 | last_pos:set(position.x, position.y, position.z) 1592 | 1593 | -- This is where we attach the weapons to the motion controllers 1594 | if anim_instance then 1595 | local equipped_weapon = anim_instance:GetEquippedWeapon() 1596 | 1597 | if not equipped_weapon then 1598 | is_using_melee_weapon = false 1599 | end 1600 | 1601 | if equipped_weapon then 1602 | is_using_melee_weapon = equipped_weapon:is_a(SHItemWeaponMelee_c) 1603 | if is_using_melee_weapon then 1604 | is_using_two_handed_weapon = true 1605 | end 1606 | 1607 | -- Disables auto aim basically 1608 | if equipped_weapon.AutoAimMaxRange ~= nil then 1609 | equipped_weapon.AutoAimMaxRange = 1.0 1610 | end 1611 | 1612 | local weapon_name = equipped_weapon:get_fname():to_string() 1613 | local fire_point = equipped_weapon.FirePoint 1614 | 1615 | -- Crosshair widget 1616 | if crosshair_actor and fire_point ~= nil then 1617 | local ignore_actors = {my_pawn, equipped_weapon, crosshair_actor} 1618 | 1619 | local root = equipped_weapon.RootComponent 1620 | local weapon_pos = fire_point:K2_GetComponentLocation() 1621 | local forward_vector = root:GetForwardVector() 1622 | 1623 | if weapon_name:find("Shotgun") or weapon_name:find("Rifle") then 1624 | -- Rotate forward vector by 90 deg on Z axis 1625 | forward_vector = kismet_math_library:RotateAngleAxis(forward_vector, 90, root:GetUpVector()) 1626 | end 1627 | 1628 | local end_pos = weapon_pos + (forward_vector * 8192.0) 1629 | local hit = kismet_system_library:LineTraceSingle(world, weapon_pos, end_pos, 3, true, ignore_actors, 0, reusable_hit_result, true, zero_color, zero_color, 1.0) 1630 | local hit_location = nil 1631 | 1632 | if hit then 1633 | hit_location = weapon_pos + (forward_vector * (reusable_hit_result.Distance * 0.9)) 1634 | else 1635 | hit_location = weapon_pos + (forward_vector * 8192.0) 1636 | end 1637 | 1638 | local delta = weapon_pos - hit_location 1639 | local len = math.max(0.1, delta:length() * 0.001) 1640 | 1641 | crosshair_actor:SetActorScale3D(temp_vec3:set(len, len, len)) 1642 | 1643 | local rot = kismet_math_library:Conv_VectorToRotator(forward_vector) 1644 | rot.Yaw = rot.Yaw + 180 1645 | rot.Roll = 0 1646 | rot.Pitch = -rot.Pitch 1647 | crosshair_actor:K2_SetActorLocationAndRotation(hit_location, rot, false, empty_hitresult, false) 1648 | end 1649 | 1650 | -- Attach the weapon to the right hand 1651 | local root_component = equipped_weapon.RootComponent 1652 | 1653 | if root_component then 1654 | local state = UEVR_UObjectHook.get_or_add_motion_controller_state(root_component) 1655 | 1656 | if state then 1657 | state:set_hand(1) -- Right hand 1658 | state:set_permanent(true) 1659 | 1660 | --print("Equipped weapon: " .. equipped_weapon:get_fname():to_string()) 1661 | local name = equipped_weapon:get_fname():to_string() 1662 | 1663 | if name:find("Pistol") then 1664 | state:set_rotation_offset(temp_vec3f:set(0.149, 0.084, 0.077)) 1665 | state:set_location_offset(temp_vec3f:set(-2.982, -10.160, 4.038)) 1666 | is_using_two_handed_weapon = false 1667 | elseif name:find("Shotgun") then 1668 | state:set_rotation_offset(temp_vec3f:set(0.037, 1.556, -0.022)) -- Euler (radians) 1669 | state:set_location_offset(temp_vec3f:set(-7.967, -5.752, -0.255)) 1670 | is_using_two_handed_weapon = true 1671 | elseif name:find("Rifle") then 1672 | state:set_rotation_offset(temp_vec3f:set(0.037, 1.556, -0.022)) -- Euler (radians) 1673 | state:set_location_offset(temp_vec3f:set(-30.942, -6.758, 0.017)) 1674 | is_using_two_handed_weapon = true 1675 | --elseif name:find("IronPipe") then 1676 | elseif is_using_melee_weapon then 1677 | if name:find("Chainsaw") then 1678 | state:set_rotation_offset(temp_vec3f:set(-0.737, -0.090, 0.096)) 1679 | state:set_location_offset(temp_vec3f:set(0.257, 2.007, 31.583)) 1680 | else 1681 | state:set_rotation_offset(temp_vec3f:set(0.495, 1.928, 0.004)) -- Euler (radians) 1682 | state:set_location_offset(temp_vec3f:set(-1.444, 30.221, -0.914)) 1683 | end 1684 | is_using_two_handed_weapon = true -- It's two handed but it's a melee weapon 1685 | else 1686 | is_using_two_handed_weapon = not is_using_melee_weapon 1687 | end 1688 | end 1689 | end 1690 | end 1691 | end 1692 | end 1693 | end) 1694 | 1695 | 1696 | uevr.sdk.callbacks.on_post_calculate_stereo_view_offset(function(device, view_index, world_to_meters, position, rotation, is_double) 1697 | if not camera_component then 1698 | return 1699 | end 1700 | 1701 | if not is_allowing_vr_mode then 1702 | return 1703 | end 1704 | 1705 | if view_index ~= 1 then 1706 | return 1707 | end 1708 | 1709 | if hmd_component and last_roomscale_value == true then 1710 | local hmd_pos = hmd_component:K2_GetComponentLocation() 1711 | local hmd_delta = hmd_pos - last_pos 1712 | 1713 | -- Reset the delta if it's too large 1714 | if hmd_delta:length() > MAX_LERP_DIST then 1715 | hmd_delta:set(0, 0, 0) 1716 | end 1717 | 1718 | last_camera_x = last_camera_x + hmd_delta.X 1719 | last_camera_y = last_camera_y + hmd_delta.Y 1720 | end 1721 | 1722 | local camera_overlap = my_pawn.CameraOverlapHandler 1723 | 1724 | if camera_overlap ~= nil then 1725 | if camera_overlap:IsComponentTickEnabled() then 1726 | camera_overlap:SetComponentTickEnabled(false) -- Stops camera from making pawn and other things near it invisible 1727 | print("Disabled camera overlap handler") 1728 | end 1729 | 1730 | --camera_overlap:write_qword(0xD0, 0) -- Nulls out the owner actor, this stops some logic from functioning when we start aiming which hides the pawn 1731 | camera_overlap.OwnerCharacterPlay = nil -- Nulls out the owner actor, this stops some logic from functioning when we start aiming which hides the pawn 1732 | end 1733 | 1734 | -- moves real camera to the VR camera position, this corrects audio and interactions 1735 | camera_component:K2_SetWorldLocation(position, false, empty_hitresult, true) 1736 | 1737 | -- Using the map and some other things 1738 | if anim_instance and anim_instance.bWholeBodyAnimation then 1739 | return 1740 | end 1741 | 1742 | --local camera_delta_to_head = Vector3d.new(position.x - head_pos.X, position.y - head_pos.Y, position.z - head_pos.Z) - (forward * 10) 1743 | --camera_delta_to_head.z = 0 1744 | --local mesh_pos = mesh:K2_GetComponentLocation() 1745 | --mesh:K2_SetWorldLocation(Vector3d.new(mesh_pos.X + camera_delta_to_head.x, mesh_pos.Y + camera_delta_to_head.y, mesh_pos.Z), false, empty_hitresult, false) 1746 | --mesh:K2_AddWorldOffset(camera_delta_to_head, false, empty_hitresult, false) 1747 | 1748 | local hmdrot = hmd_component:K2_GetComponentRotation() 1749 | local rotdelta = hmdrot - last_rot 1750 | --local rotdelta = rotation - last_rot 1751 | 1752 | -- Fix up the rotation delta 1753 | if rotdelta.x > 180 then 1754 | rotdelta.x = rotdelta.x - 360 1755 | elseif rotdelta.x < -180 then 1756 | rotdelta.x = rotdelta.x + 360 1757 | end 1758 | 1759 | if rotdelta.y > 180 then 1760 | rotdelta.y = rotdelta.y - 360 1761 | elseif rotdelta.y < -180 then 1762 | rotdelta.y = rotdelta.y + 360 1763 | end 1764 | 1765 | if rotdelta.z > 180 then 1766 | rotdelta.z = rotdelta.z - 360 1767 | elseif rotdelta.z < -180 then 1768 | rotdelta.z = rotdelta.z + 360 1769 | end 1770 | 1771 | local has_look_operation = false 1772 | 1773 | local attach_parent = camera_component.AttachParent -- Spring arm 1774 | if attach_parent then 1775 | attach_parent.bEnableCameraRotationLag = false 1776 | attach_parent.bEnableCameraLag = false 1777 | 1778 | -- View component 1779 | local attach_parent_parent = attach_parent.AttachParent 1780 | 1781 | if attach_parent_parent then 1782 | attach_parent_parent:SetRotationScale(1.0, nil) 1783 | 1784 | local operation_events = attach_parent_parent.LookOperation.OperationEvents 1785 | if operation_events ~= nil then 1786 | local settings = operation_events.CurrentSettings 1787 | if settings ~= nil then 1788 | settings.bIsSecuredOperation = false -- Dont want this forcing camera control 1789 | end 1790 | attach_parent_parent:ResetLookOperation(nil) 1791 | has_look_operation = true 1792 | else 1793 | -- this is how we make the internal camera aim towards the HMD rotation 1794 | attach_parent_parent:AddToControlRotation(rotdelta, nil) 1795 | vr.recenter_view() 1796 | --attach_parent_parent:OverrideControlRotation(hmdrot, nil) 1797 | end 1798 | end 1799 | end 1800 | 1801 | if anim_instance then 1802 | local equipped_weapon = anim_instance:GetEquippedWeapon() 1803 | 1804 | -- Two handed weapon aiming (like rifles and shotguns) 1805 | if equipped_weapon and is_using_two_handed_weapon and anim_instance:IsAimingWeapon() then 1806 | local left_hand_pos = left_hand_component:K2_GetComponentLocation() 1807 | local right_hand_pos = right_hand_component:K2_GetComponentLocation() 1808 | local dir_to_left_hand = (left_hand_pos - right_hand_pos):normalized() 1809 | local right_hand_rotation = right_hand_component:K2_GetComponentRotation() 1810 | 1811 | local root = equipped_weapon.RootComponent 1812 | local weapon_up_vector = root:GetUpVector() 1813 | local new_direction_rot = kismet_math_library:MakeRotFromXZ(dir_to_left_hand, weapon_up_vector) 1814 | 1815 | local dir_to_left_hand_q = kismet_math_library:Conv_RotatorToQuaternion(new_direction_rot) 1816 | local right_hand_q = kismet_math_library:Conv_RotatorToQuaternion(right_hand_rotation) 1817 | 1818 | local delta_q = kismet_math_library:Quat_Inversed(kismet_math_library:Multiply_QuatQuat(right_hand_q, kismet_math_library:Quat_Inversed(dir_to_left_hand_q))) 1819 | 1820 | local original_grip_position = right_hand_pos 1821 | local delta_to_grip = original_grip_position - root:K2_GetComponentLocation() 1822 | local delta_rotated_q = kismet_math_library:Quat_RotateVector(delta_q, delta_to_grip) 1823 | 1824 | local current_rotation = root:K2_GetComponentRotation() 1825 | local current_rot_q = kismet_math_library:Conv_RotatorToQuaternion(current_rotation) 1826 | local new_rot_q = kismet_math_library:Multiply_QuatQuat(delta_q, current_rot_q) 1827 | 1828 | current_rotation = kismet_math_library:Quat_Rotator(new_rot_q) 1829 | root:K2_SetWorldRotation(current_rotation, false, empty_hitresult, false) 1830 | 1831 | local new_weapon_position = original_grip_position - delta_rotated_q 1832 | root:K2_SetWorldLocation(new_weapon_position, false, empty_hitresult, false) 1833 | 1834 | --detach_flashlight(my_pawn) -- Not gonna detach... for now 1835 | else 1836 | if should_attach_flashlight and not enqueue_detach_flashlight then 1837 | attach_flashlight(my_pawn) 1838 | end 1839 | end 1840 | end 1841 | 1842 | -- TODO: make IK better 1843 | --[[local ik = SHAnimIKHandIKSubcomp_c:get_first_object_matching(false) 1844 | 1845 | if ik then 1846 | if anim_instance then 1847 | ik:SetRightHandLocation(right_hand_component:K2_GetComponentLocation(), 1.0, true) 1848 | ik:SetLeftHandLocation(left_hand_component:K2_GetComponentLocation(), 1.0, true) 1849 | end 1850 | end]] 1851 | end) 1852 | 1853 | uevr.sdk.callbacks.on_post_engine_tick(function() 1854 | -- We do this here so UObjectHook can reset the position back correctly 1855 | if enqueue_detach_flashlight then 1856 | local pawn = api:get_local_pawn(0) 1857 | 1858 | if pawn then 1859 | attach_flashlight(pawn, true) -- the true means detach 1860 | end 1861 | 1862 | enqueue_detach_flashlight = false 1863 | end 1864 | end) 1865 | 1866 | uevr.sdk.callbacks.on_xinput_get_state(function(retval, user_index, state) 1867 | if not is_allowing_vr_mode then return end 1868 | 1869 | -- If right stick Y is down, press the B button 1870 | local max_int16 = 0x7FFF 1871 | if state.Gamepad.sThumbRY <= -max_int16 * 0.75 and investigating_item == nil then 1872 | state.Gamepad.wButtons = state.Gamepad.wButtons | XINPUT_GAMEPAD_B 1873 | end 1874 | 1875 | -- if pressing L3 then press select 1876 | if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ~= 0 and (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) == 0 then 1877 | state.Gamepad.wButtons = state.Gamepad.wButtons | XINPUT_GAMEPAD_BACK 1878 | end 1879 | end) 1880 | 1881 | uevr.sdk.callbacks.on_script_reset(function() 1882 | print("Resetting") 1883 | 1884 | reset_crosshair_actor() 1885 | reset_hand_actors() 1886 | 1887 | if BlueprintUpdateCameraShake ~= nil then 1888 | BlueprintUpdateCameraShake:set_function_flags(BlueprintUpdateCameraShake:get_function_flags() & ~0x400) -- Unmark as native 1889 | end 1890 | 1891 | if ReceivePlayShake ~= nil then 1892 | ReceivePlayShake:set_function_flags(ReceivePlayShake:get_function_flags() & ~0x400) -- Unmark as native 1893 | end 1894 | 1895 | if AnimNotify_MeleeAttackCheck_Notify ~= nil then 1896 | AnimNotify_MeleeAttackCheck_Notify:set_function_flags(AnimNotify_MeleeAttackCheck_Notify:get_function_flags() & ~0x400) -- Unmark as native 1897 | end 1898 | end) -------------------------------------------------------------------------------- /src/Plugin.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Plugin.hpp" 8 | 9 | using namespace uevr; 10 | 11 | std::unique_ptr g_plugin = std::make_unique(); 12 | 13 | SHPlugin::~SHPlugin() { 14 | 15 | } 16 | 17 | void SHPlugin::on_initialize() { 18 | PLUGIN_LOG_ONCE("SHPlugin::on_initialize()"); 19 | 20 | hook_get_spread_shoot_vector(); 21 | hook_melee_trace_check(); 22 | } 23 | 24 | void SHPlugin::on_pre_engine_tick(uevr::API::UGameEngine* engine, float delta) { 25 | 26 | } 27 | 28 | int32_t hook_vtable_fn(std::wstring_view class_name, std::wstring_view fn_name, void* destination, void** original) { 29 | auto obj = (API::UClass*)API::get()->find_uobject(class_name); 30 | 31 | if (obj == nullptr) { 32 | //PLUGIN_LOG_ONCE_ERROR("Failed to find %ls", class_name.data()); 33 | return -1; 34 | } 35 | 36 | auto fn = obj->find_function(fn_name); 37 | 38 | if (fn == nullptr) { 39 | //PLUGIN_LOG_ONCE_ERROR("Failed to find %ls", fn_name.data()); 40 | return -1; 41 | } 42 | 43 | auto native = fn->get_native_function(); 44 | 45 | if (native == nullptr) { 46 | //PLUGIN_LOG_ONCE_ERROR("Failed to get native function"); 47 | return -1; 48 | } 49 | 50 | //PLUGIN_LOG_ONCE("%ls native: 0x%p", fn_name.data(), native); 51 | 52 | auto default_object = obj->get_class_default_object(); 53 | 54 | if (default_object == nullptr) { 55 | //PLUGIN_LOG_ONCE_ERROR("Failed to get default object"); 56 | return -1; 57 | } 58 | 59 | auto insn = utility::scan_disasm((uintptr_t)native, 0x1000, "FF 90 ? ? ? ?"); 60 | 61 | if (!insn) { 62 | //PLUGIN_LOG_ONCE_ERROR("Failed to find the instruction"); 63 | return -1; 64 | } 65 | 66 | auto offset = *(int32_t*)(*insn + 2); 67 | 68 | auto vtable = *(uintptr_t**)default_object; 69 | auto real_fn = vtable[offset / sizeof(void*)]; 70 | 71 | //PLUGIN_LOG_ONCE("Real %ls: 0x%p (index: %d, offset 0x%X)", fn_name.data(), real_fn, offset / sizeof(void*), offset); 72 | 73 | return API::get()->param()->functions->register_inline_hook((void*)real_fn, (void*)destination, original); 74 | } 75 | 76 | // This is one thing we can't do from Lua, 77 | // because well... it's not called from BP. 78 | // It also calls a virtual function internally which we need to find the index of. 79 | void SHPlugin::hook_get_spread_shoot_vector() { 80 | m_on_get_end_trace_loc_hook_id = hook_vtable_fn(L"Class /Script/SHProto.SHItemWeaponRanged", L"GetEndTraceLoc", on_get_end_trace_loc, (void**)&m_on_get_end_trace_loc_hook_fn); 81 | m_trace_start_loc_hook_id = hook_vtable_fn(L"Class /Script/SHProto.SHItemWeaponRanged", L"GetStartTraceLoc", on_get_trace_start_loc, (void**)&m_trace_start_loc_hook_fn); 82 | } 83 | 84 | // Another one that most definitely cannot be done in Lua without FFI. 85 | void SHPlugin::hook_melee_trace_check() { 86 | PLUGIN_LOG_ONCE("SHPlugin::hook_melee_trace_check()"); 87 | 88 | const auto game = utility::get_executable(); 89 | auto fn = utility::find_function_from_string_ref(game, "MeleeWeaponEnvTrace"); 90 | 91 | if (!fn) { 92 | PLUGIN_LOG_ONCE_ERROR("Failed to find MeleeWeaponEnvTrace"); 93 | return; 94 | } 95 | 96 | fn = utility::find_function_start_with_call(*fn); // Looks for E8 call to the function to locate the start 97 | 98 | if (!fn) { 99 | PLUGIN_LOG_ONCE_ERROR("Failed to find MeleeWeaponEnvTrace start"); 100 | return; 101 | } 102 | 103 | PLUGIN_LOG_ONCE("MeleeWeaponEnvTrace: 0x%p", (void*)*fn); 104 | 105 | m_melee_trace_check_hook_id = API::get()->param()->functions->register_inline_hook((void*)*fn, (void*)on_melee_trace_check, (void**)&m_melee_trace_check_hook_fn); 106 | 107 | if (m_melee_trace_check_hook_id == -1) { 108 | PLUGIN_LOG_ONCE_ERROR("Failed to hook MeleeWeaponEnvTrace"); 109 | return; 110 | } 111 | 112 | PLUGIN_LOG_ONCE("Hooked MeleeWeaponEnvTrace"); 113 | } 114 | 115 | bool SHPlugin::on_melee_trace_check_internal( 116 | API::UObject* melee_item, 117 | float a2, float a3, float hit_rotation_ratio, 118 | void* a5, void* a6, 119 | uevr::API::TArray& hit_results 120 | ) 121 | { 122 | PLUGIN_LOG_ONCE("SHPlugin::on_melee_trace_check_internal(0x%p, %f, %f, %f, 0x%p, 0x%p, %d)", melee_item, a2, a3, hit_rotation_ratio, a5, a6, hit_results.count); 123 | 124 | if (melee_item == nullptr) { 125 | return false; 126 | } 127 | 128 | if (!API::get()->param()->vr->is_using_controllers()) { 129 | // Just do what the game originally wanted to do 130 | return m_melee_trace_check_hook_fn(melee_item, a2, a3, hit_rotation_ratio, a5, a6, hit_results); 131 | } 132 | 133 | // Let's try and do it ourselves 134 | static const auto kismet_system_library_c = API::get()->find_uobject(L"Class /Script/Engine.KismetSystemLibrary"); 135 | static const auto kismet_system_library = kismet_system_library_c->get_class_default_object(); 136 | 137 | static const auto kismet_math_library_c = API::get()->find_uobject(L"Class /Script/Engine.KismetMathLibrary"); 138 | static const auto kismet_math_library = kismet_math_library_c->get_class_default_object(); 139 | 140 | static const auto SHMeleeBaseDamage_c = API::get()->find_uobject(L"Class /Script/SHProto.SHMeleeBaseDamage"); 141 | static const auto SHMeleeBaseDamage = SHMeleeBaseDamage_c != nullptr ? SHMeleeBaseDamage_c->get_class_default_object() : nullptr; 142 | 143 | if (SHMeleeBaseDamage != nullptr) { 144 | SHMeleeBaseDamage->set_bool_property(L"bIsGroundHit", true); // Allows us to hit enemies on the ground. I don't even know why this is a thing. 145 | } 146 | 147 | struct { 148 | API::UObject* world_context_object{}; 149 | glm::f64vec3 start{}; 150 | glm::f64vec3 end{}; 151 | float radius{1.0f}; 152 | float half_height{1.0f}; 153 | uint8_t trace_channel{3}; 154 | bool trace_complex{true}; 155 | API::TArray actors_to_ignore{}; 156 | uint8_t draw_debug_type{0}; 157 | //FHitResult hit_result{}; 158 | API::TArray hit_results{}; // For multi variant 159 | bool ignore_self; 160 | float trace_color[4]{1.0f, 0.0f, 0.0f, 1.0f}; 161 | float trace_hit_color[4]{0.0f, 1.0f, 0.0f, 1.0f}; 162 | float draw_time{1.0f}; 163 | bool return_value{false}; 164 | } params; 165 | 166 | auto engine = API::get()->get_engine(); 167 | auto viewport = engine != nullptr ? engine->get_property(L"GameViewport") : nullptr; 168 | auto world = viewport != nullptr ? viewport->get_property(L"World") : nullptr; 169 | 170 | params.world_context_object = world; 171 | params.actors_to_ignore.data = (API::UObject**)API::FMalloc::get()->malloc(2 * sizeof(API::UObject*)); 172 | params.actors_to_ignore.count = 2; 173 | params.actors_to_ignore.capacity = 2; 174 | params.actors_to_ignore.data[0] = melee_item; 175 | params.actors_to_ignore.data[1] = API::get()->get_local_pawn(0); 176 | params.ignore_self = true; 177 | 178 | auto mesh_component = melee_item->get_property(L"Mesh"); 179 | 180 | struct Transform { 181 | char padding[0x60]; // We do not care what's in here 182 | } mesh_transform; 183 | static_assert(sizeof(Transform) == 0x60, "Transform size mismatch"); 184 | 185 | struct BoxSphereBounds { 186 | glm::f64vec3 origin{}; 187 | glm::f64vec3 box_extent{}; 188 | double sphere_radius{}; 189 | } mesh_bounds; 190 | static_assert(sizeof(BoxSphereBounds) == 0x38, "BoxSphereBounds size mismatch"); 191 | 192 | if (mesh_component != nullptr) { 193 | // Enable physics 194 | struct { 195 | bool new_physics{true}; 196 | } simulate_physics_params; 197 | //mesh_component->call_function(L"SetSimulatePhysics", &simulate_physics_params); 198 | //mesh_component->call_function(L"SetAllBodiesSimulatePhysics", &simulate_physics_params); 199 | 200 | /*struct { 201 | uint8_t collision_object_type{5}; // ECC_PhysicsBody 202 | } set_collision_object_type_params; 203 | 204 | mesh_component->call_function(L"SetCollisionObjectType", &set_collision_object_type_params); 205 | 206 | struct { 207 | uint8_t new_collision_enabled{3}; 208 | } new_collision_enabled; 209 | 210 | mesh_component->call_function(L"SetCollisionEnabled", &new_collision_enabled); 211 | 212 | struct { 213 | uint8_t new_response_to_all_channels{0}; // ECR_Ignore 214 | } new_response_to_all_channels; 215 | 216 | mesh_component->call_function(L"SetCollisionResponseToAllChannels", &new_response_to_all_channels); 217 | 218 | struct { 219 | uint8_t channel{5}; // ECC_PhysicsBody 220 | uint8_t new_response{2}; // ECR_Block 221 | } new_response{}; 222 | 223 | mesh_component->call_function(L"SetCollisionResponseToChannel", &new_response); 224 | 225 | struct { 226 | bool generate_overlap_events{true}; 227 | } generate_overlap_events;*/ 228 | 229 | //mesh_component->call_function(L"SetGenerateOverlapEvents", &generate_overlap_events); 230 | 231 | mesh_component->call_function(L"K2_GetComponentToWorld", &mesh_transform); 232 | 233 | auto skeletal_mesh = mesh_component->get_property(L"SkeletalMesh"); 234 | 235 | if (skeletal_mesh != nullptr) { 236 | skeletal_mesh->call_function(L"GetBounds", &mesh_bounds); 237 | 238 | int32_t largest_axis{0}; 239 | double largest_extent{0.0}; 240 | 241 | for (int32_t i = 0; i < 3; i++) { 242 | if (glm::abs(mesh_bounds.box_extent[i]) > largest_extent) { 243 | largest_extent = glm::abs(mesh_bounds.box_extent[i]); 244 | largest_axis = i; 245 | } 246 | } 247 | 248 | struct { 249 | Transform transform; 250 | glm::f64vec3 location; 251 | glm::f64vec3 result; 252 | } transform_location_params; 253 | 254 | transform_location_params.transform = mesh_transform; 255 | transform_location_params.location = mesh_bounds.origin; 256 | transform_location_params.location[largest_axis] -= mesh_bounds.box_extent[largest_axis]; // Lengthwise along the mesh 257 | 258 | kismet_math_library->call_function(L"TransformLocation", &transform_location_params); 259 | 260 | params.start = transform_location_params.result; 261 | 262 | transform_location_params.location = mesh_bounds.origin; 263 | transform_location_params.location[largest_axis] += mesh_bounds.box_extent[largest_axis]; // Lengthwise along the mesh 264 | 265 | kismet_math_library->call_function(L"TransformLocation", &transform_location_params); 266 | 267 | glm::f64vec3 short_axes{mesh_bounds.box_extent}; 268 | short_axes[largest_axis] = 0.0; 269 | 270 | double biggest_short_axis{0.0}; 271 | 272 | for (int32_t i = 0; i < 3; i++) { 273 | if (glm::abs(short_axes[i]) > biggest_short_axis) { 274 | biggest_short_axis = glm::abs(short_axes[i]); 275 | } 276 | } 277 | 278 | params.end = transform_location_params.result; 279 | params.radius = (float)biggest_short_axis; 280 | params.half_height = (float)(mesh_bounds.box_extent[largest_axis] / 2.0); 281 | //params.half_height = 0.001f; 282 | } 283 | } 284 | 285 | //kismet_system_library->call_function(L"LineTraceSingle", ¶ms); 286 | //kismet_system_library->call_function(L"SphereTraceSingle", ¶ms); 287 | 288 | auto add_uobject_to_tarray = [](API::TArray& array, API::UObject* obj) { 289 | if (array.count >= array.capacity) { 290 | size_t new_capacity = std::max(array.capacity * 2, 2); 291 | 292 | if (array.data != nullptr) { 293 | array.data = (API::UObject**)API::FMalloc::get()->realloc(array.data, new_capacity * sizeof(API::UObject*)); 294 | } else { 295 | array.data = (API::UObject**)API::FMalloc::get()->malloc(new_capacity * sizeof(API::UObject*)); 296 | } 297 | 298 | array.capacity = new_capacity; 299 | } 300 | 301 | array.data[array.count++] = obj; 302 | }; 303 | 304 | bool any_succeed = false; 305 | bool any_enemy = false; 306 | bool any_glass = false; 307 | 308 | for (size_t j = 0; j < 2; ++j) { 309 | if (any_succeed) { 310 | //break; 311 | } 312 | 313 | if (j == 1) { 314 | std::swap(params.start, params.end); // For some reason the game might only hit enemies if the impact hits a certain way 315 | } 316 | 317 | // Do a few traces, we should hit multiple actors/components. 318 | kismet_system_library->call_function(L"CapsuleTraceMulti", ¶ms); 319 | 320 | for (size_t i = 0; i < params.hit_results.count; i++) { 321 | auto& hit_result = params.hit_results.data[i]; 322 | const bool result = hit_result.bBlockingHit && params.return_value; 323 | 324 | if (result) { 325 | if (hit_result.HitObject_ActorIndex > 0) { 326 | auto actor_item = API::FUObjectArray::get()->get_item(hit_result.HitObject_ActorIndex); 327 | 328 | if (actor_item != nullptr) { 329 | auto actor = actor_item->object; 330 | 331 | if (actor != nullptr) { 332 | API::get()->log_info("Hit actor: %s", utility::narrow(actor->get_full_name()).c_str()); 333 | std::cout << "Hit actor: " << utility::narrow(actor->get_full_name()) << std::endl; 334 | 335 | //add_uobject_to_tarray(params.actors_to_ignore, actor); 336 | } 337 | } 338 | 339 | auto component_item = API::FUObjectArray::get()->get_item(hit_result.Component_Index); 340 | 341 | if (component_item != nullptr) { 342 | auto component = component_item->object; 343 | 344 | if (component != nullptr) { 345 | API::get()->log_info("Hit component: %s", utility::narrow(component->get_full_name()).c_str()); 346 | std::cout << "Hit component: " << utility::narrow(component->get_full_name()) << std::endl; 347 | 348 | /*static const auto capsule_component_c = API::get()->find_uobject(L"Class /Script/Engine.CapsuleComponent"); 349 | 350 | if (component->is_a(capsule_component_c)) { 351 | continue; // We don't want to hit capsule components 352 | }*/ 353 | 354 | static const auto mesh_component_c = API::get()->find_uobject(L"Class /Script/Engine.MeshComponent"); 355 | 356 | if (!component->is_a(mesh_component_c)) { 357 | continue; // We only want to hit mesh components 358 | } 359 | 360 | static const auto skeletal_mesh_component_c = API::get()->find_uobject(L"Class /Script/Engine.SkeletalMeshComponent"); 361 | static const auto breakable_glass_component_c = API::get()->find_uobject(L"Class /Script/SHProto.SHBreakableGlassComponent"); 362 | 363 | bool is_enemy = false; 364 | 365 | if (component->is_a(skeletal_mesh_component_c)) { 366 | is_enemy = true; 367 | any_enemy = true; 368 | } else if (component->is_a(breakable_glass_component_c)) { 369 | any_glass = true; 370 | } 371 | 372 | auto& bone_name = hit_result.BoneName; 373 | 374 | if (bone_name.number >= 0 || bone_name.comparison_index >= 0) { 375 | //printf("Hit bone: %s\n", utility::narrow(bone_name.to_string()).c_str()); 376 | const auto bone_name_str = bone_name.to_string(); 377 | const auto is_pelvis = bone_name_str.contains(L"pelvis"); 378 | if (is_enemy && !is_pelvis) { 379 | if (bone_name_str.contains(L"thigh") || bone_name_str.contains(L"calf") || bone_name_str.contains(L"foot")) { 380 | API::get()->dispatch_lua_event("OnMeleeHitLeg", ""); 381 | } 382 | 383 | /*struct { 384 | bool new_physics{false}; 385 | } physics_blend_params; 386 | 387 | component->call_function(L"SetSimulatePhysics", &physics_blend_params);*/ 388 | 389 | //physics_blend_params.new_physics = true; 390 | //component->call_function(L"SetEnablePhysicsBlending", &physics_blend_params); 391 | 392 | //physics_blend_params.new_physics = false; 393 | //component->call_function(L"SetAllBodiesSimulatePhysics", &physics_blend_params); 394 | 395 | struct { 396 | API::FName bone_name{}; 397 | bool new_simulation{true}; 398 | bool include_self{true}; 399 | } set_all_bodies_below_simulate; 400 | 401 | set_all_bodies_below_simulate.bone_name = bone_name; 402 | 403 | //component->call_function(L"SetAllBodiesBelowSimulatePhysics", &set_all_bodies_below_simulate); 404 | 405 | struct { 406 | API::FName bone_name{}; 407 | float physics_blend_weight{1.0f}; 408 | bool skip{false}; 409 | bool include_self{true}; 410 | } set_all_bodies_below_physics_blend_weight; 411 | 412 | set_all_bodies_below_physics_blend_weight.bone_name = bone_name; 413 | 414 | //component->call_function(L"SetAllBodiesBelowPhysicsBlendWeight", &set_all_bodies_below_physics_blend_weight); 415 | 416 | /*struct { 417 | float physics_blend_weight{1.0f}; 418 | } set_physics_blend_weight; 419 | mesh_component->call_function(L"SetPhysicsBlendWeight", &set_physics_blend_weight); 420 | 421 | mesh_component->call_function(L"SetEnablePhysicsBlending", &physics_blend_params);*/ 422 | } 423 | 424 | // get owner 425 | struct { 426 | API::UObject* value{nullptr}; 427 | } owner; 428 | component->call_function(L"GetOwner", &owner); 429 | 430 | if (owner.value != nullptr && !is_pelvis) { 431 | // Look for physical animation component 432 | auto physical_animation_component_ptr = (API::UObject**)owner.value->get_property_data(L"PhysicalAnimation"); 433 | auto physical_animation_component = physical_animation_component_ptr != nullptr ? *physical_animation_component_ptr : nullptr; 434 | 435 | if (physical_animation_component != nullptr && is_enemy) { 436 | struct { 437 | bool active{true}; 438 | } active_params; 439 | 440 | physical_animation_component->call_function(L"Activate", &active_params); 441 | 442 | struct PhysicalAnimationData { 443 | API::FName BodyName; // 0x0 444 | uint8_t bIsLocalSimulation : 1; // 0x8 445 | uint8_t pad_bitfield_8_1 : 7; 446 | char pad_9[0x3]; 447 | float OrientationStrength; // 0xc 448 | float AngularVelocityStrength; // 0x10 449 | float PositionStrength; // 0x14 450 | float VelocityStrength; // 0x18 451 | float MaxLinearForce; // 0x1c 452 | float MaxAngularForce; // 0x20 453 | }; 454 | 455 | // Apply settings to bodies below the bone 456 | struct { 457 | API::FName body_name{}; 458 | PhysicalAnimationData data{}; 459 | bool include_self{true}; 460 | } physical_animation_params; 461 | 462 | physical_animation_params.body_name = bone_name; 463 | physical_animation_params.data.BodyName = bone_name; 464 | physical_animation_params.data.bIsLocalSimulation = false; 465 | physical_animation_params.data.OrientationStrength = 100.0f; 466 | physical_animation_params.data.AngularVelocityStrength = 100.0f; 467 | physical_animation_params.data.PositionStrength = 100.0f; 468 | physical_animation_params.data.VelocityStrength = 100.0f; 469 | physical_animation_params.data.MaxLinearForce = 0.0f; 470 | physical_animation_params.data.MaxAngularForce = 0.0f; 471 | 472 | //physical_animation_component->call_function(L"ApplyPhysicalAnimationSettingsBelow", &physical_animation_params); 473 | //printf("Applied physical animation settings to %s\n", utility::narrow(bone_name.to_string()).c_str()); 474 | } 475 | } 476 | 477 | struct { 478 | glm::f64vec3 impulse{}; 479 | glm::f64vec3 location{}; 480 | API::FName bone_name{}; 481 | } impulse_params; 482 | 483 | //impulse_params.impulse = glm::f64vec3{0.0, 0.0, 1000.0}; 484 | impulse_params.impulse = hit_result.ImpactNormal * -10000.0; 485 | impulse_params.location = hit_result.ImpactPoint; 486 | impulse_params.bone_name = bone_name; 487 | 488 | component->call_function(L"AddImpulseAtLocation", &impulse_params); 489 | } 490 | } 491 | } 492 | } 493 | 494 | if (hit_results.count >= hit_results.capacity) { 495 | size_t new_capacity = std::max(hit_results.capacity * 2, 2); 496 | 497 | if (hit_results.data != nullptr) { 498 | hit_results.data = (FHitResult*)API::FMalloc::get()->realloc(hit_results.data, new_capacity * sizeof(FHitResult)); 499 | } else { 500 | hit_results.data = (FHitResult*)API::FMalloc::get()->malloc(new_capacity * sizeof(FHitResult)); 501 | } 502 | 503 | hit_results.capacity = new_capacity; 504 | } 505 | 506 | memcpy(&hit_results.data[hit_results.count++], &hit_result, sizeof(FHitResult)); 507 | 508 | any_succeed = true; 509 | } 510 | } 511 | } 512 | 513 | if (any_succeed) { 514 | std::string_view event_data = any_enemy ? "Enemy" : (any_glass ? "Glass" : "Environment"); 515 | API::get()->dispatch_lua_event("OnMeleeTraceSuccess", event_data.data()); 516 | } 517 | 518 | return any_succeed; 519 | } 520 | 521 | glm::f64vec3* SHPlugin::on_get_trace_start_loc_internal(uevr::API::UObject* weapon, glm::f64vec3* out_vec) { 522 | auto result = m_trace_start_loc_hook_fn(weapon, out_vec); 523 | 524 | if (result != nullptr) { 525 | //API::get()->log_info("Start Trace Current result: %f, %f, %f", result->x, result->y, result->z); 526 | 527 | auto fire_point_component = weapon->get_property(L"FirePoint"); 528 | 529 | if (fire_point_component != nullptr) { 530 | struct { 531 | glm::f64vec3 location; 532 | } location_params; 533 | fire_point_component->call_function(L"K2_GetComponentLocation", &location_params); 534 | 535 | result->x = location_params.location.x; 536 | result->y = location_params.location.y; 537 | result->z = location_params.location.z; 538 | 539 | //API::get()->dispatch_lua_event("GetStartTraceLoc", "Test"); 540 | } 541 | } 542 | 543 | return result; 544 | } 545 | 546 | void* SHPlugin::on_get_end_trace_loc_internal(uevr::API::UObject* weapon, glm::f64vec2* in_angles, float shootangles, glm::f64vec3* out_vec) { 547 | //API::get()->log_info("SHPlugin::on_get_spread_shoot_vector_internal(0x%p, %f, %f, %f, 0x%p)", weapon, in_angles->x, in_angles->y, out_vec); 548 | 549 | // Call the original function 550 | void* result = m_on_get_end_trace_loc_hook_fn(weapon, in_angles, shootangles, out_vec); 551 | 552 | // Modify the output vector 553 | /*if (out_vec != nullptr) { 554 | API::get()->log_info("Current out_vec: %f, %f, %f", (float)out_vec->x, (float)out_vec->y, (float)out_vec->z); 555 | out_vec->x = 0.0; 556 | out_vec->y = 0.0; 557 | out_vec->z = 0.0; 558 | }*/ 559 | 560 | const auto result_f64vec3 = (glm::f64vec3*)result; 561 | 562 | if (result_f64vec3 != nullptr) { 563 | //API::get()->log_info("Current result: %f, %f, %f", result_f64vec3->x, result_f64vec3->y, result_f64vec3->z); 564 | 565 | // Get the direction the muzzle is facing instead 566 | auto fire_point_component = weapon->get_property(L"FirePoint"); 567 | auto root_component = weapon->get_property(L"RootComponent"); 568 | 569 | if (fire_point_component != nullptr && root_component != nullptr) { 570 | struct { 571 | glm::f64vec3 location; 572 | } location_params; 573 | fire_point_component->call_function(L"K2_GetComponentLocation", &location_params); 574 | 575 | struct { 576 | glm::f64vec3 forward; 577 | } forward_params; 578 | //fire_point_component->call_function(L"GetForwardVector", &forward_params); 579 | root_component->call_function(L"GetForwardVector", &forward_params); 580 | 581 | const auto name = weapon->get_fname()->to_string(); 582 | 583 | // Rifles and shotguns have their muzzle facing the wrong way (90 degrees off) 584 | // so we have to correct it 585 | if (name.contains(L"Rifle") || name.contains(L"Shotgun")) { 586 | struct { 587 | glm::f64vec3 location; 588 | float angle; 589 | glm::f64vec3 axis; 590 | glm::f64vec3 result; 591 | } rotate_params; 592 | static auto kismet_math_library_c = API::get()->find_uobject(L"Class /Script/Engine.KismetMathLibrary"); 593 | static auto kismet_math_library = kismet_math_library_c->get_class_default_object(); 594 | 595 | rotate_params.location = forward_params.forward; 596 | rotate_params.angle = 90.0f; 597 | root_component->call_function(L"GetUpVector", &rotate_params.axis); 598 | kismet_math_library->call_function(L"RotateAngleAxis", &rotate_params); 599 | 600 | forward_params.forward = rotate_params.result; 601 | } 602 | //API::get()->log_info("FirePoint location: %f, %f, %f", location_params.location.x, location_params.location.y, location_params.location.z); 603 | //API::get()->log_info("FirePoint forward: %f, %f, %f", forward_params.forward.x, forward_params.forward.y, forward_params.forward.z); 604 | 605 | // Calculate the new vector 606 | auto new_vec = location_params.location + (forward_params.forward * 8192.0); 607 | 608 | result_f64vec3->x = new_vec.x; 609 | result_f64vec3->y = new_vec.y; 610 | result_f64vec3->z = new_vec.z; 611 | 612 | //API::get()->dispatch_lua_event("GetEndTraceLoc", "Test"); 613 | } 614 | } 615 | 616 | return result; 617 | } -------------------------------------------------------------------------------- /src/Plugin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include "uevr/API.hpp" 7 | #include "uevr/Plugin.hpp" 8 | #include 9 | #include 10 | 11 | #define PLUGIN_LOG_ONCE(...) { \ 12 | static bool _logged_ = false; \ 13 | if (!_logged_) { \ 14 | _logged_ = true; \ 15 | API::get()->log_info(__VA_ARGS__); \ 16 | } } 17 | 18 | #define PLUGIN_LOG_ONCE_ERROR(...) { \ 19 | static bool _logged_ = false; \ 20 | if (!_logged_) { \ 21 | _logged_ = true; \ 22 | API::get()->log_error(__VA_ARGS__); \ 23 | } } 24 | 25 | // Global accessor for our plugin. 26 | class SHPlugin; 27 | extern std::unique_ptr g_plugin; 28 | 29 | class SHPlugin : public uevr::Plugin { 30 | public: 31 | SHPlugin() = default; 32 | virtual ~SHPlugin(); 33 | 34 | void on_initialize() override; 35 | void on_pre_engine_tick(uevr::API::UGameEngine* engine, float delta) override; 36 | 37 | private: 38 | void hook_get_spread_shoot_vector(); 39 | void hook_melee_trace_check(); 40 | 41 | void* on_get_end_trace_loc_internal(uevr::API::UObject* weapon, glm::f64vec2* in_angles, float sangles, glm::f64vec3* out_vec); 42 | static void* on_get_end_trace_loc(uevr::API::UObject* weapon, glm::f64vec2* in_angles, float sangles, glm::f64vec3* out_vec) { 43 | return g_plugin->on_get_end_trace_loc_internal(weapon, in_angles, sangles, out_vec); 44 | } 45 | 46 | glm::f64vec3* on_get_trace_start_loc_internal(uevr::API::UObject* weapon, glm::f64vec3* out_vec); 47 | static glm::f64vec3* on_get_trace_start_loc(uevr::API::UObject* weapon, glm::f64vec3* out_vec) { 48 | return g_plugin->on_get_trace_start_loc_internal(weapon, out_vec); 49 | } 50 | 51 | bool m_hooked{false}; 52 | 53 | int32_t m_on_get_end_trace_loc_hook_id{}; 54 | int32_t m_trace_start_loc_hook_id{}; 55 | using GetEndTraceLocFn = void*(*)(uevr::API::UObject*, glm::f64vec2*, float, glm::f64vec3*); 56 | GetEndTraceLocFn m_on_get_end_trace_loc_hook_fn{}; 57 | using GetStartTraceLocFn = glm::f64vec3*(*)(uevr::API::UObject*, glm::f64vec3*); 58 | GetStartTraceLocFn m_trace_start_loc_hook_fn{}; 59 | 60 | struct FHitResult { 61 | int32_t FaceIndex; 62 | float Time; 63 | float Distance; 64 | glm::f64vec3 Location; 65 | glm::f64vec3 ImpactPoint; 66 | glm::f64vec3 Normal; 67 | glm::f64vec3 ImpactNormal; 68 | glm::f64vec3 TraceStart; 69 | glm::f64vec3 TraceEnd; 70 | float PenetrationDepth; 71 | int32_t MyItem; 72 | int32_t Item; 73 | uint8_t ElementIndex; 74 | uint8_t bBlockingHit : 1; 75 | uint8_t bStartPenetrating : 1; 76 | uint32_t PhysMaterialIndex; 77 | uint32_t PhysMaterialSerialNumber; 78 | uint32_t HitObject_ActorIndex; 79 | uint32_t HitObject_ActorSerialNumber; 80 | uint32_t HitObject_ManagerIndex; 81 | uint32_t HitObject_ManagerSerialNumber; 82 | uint32_t HitObject_ActorInstanceIndex; 83 | uint32_t HitObject_ActorInstanceUID; 84 | uint32_t Component_Index; 85 | uint32_t Component_SerialNumber; 86 | uevr::API::FName BoneName; 87 | uevr::API::FName MyBoneName; 88 | }; 89 | static_assert(sizeof(FHitResult) == 0xE8, "FHitResult size mismatch"); 90 | 91 | int32_t m_melee_trace_check_hook_id{}; 92 | using MeleeTraceCheckFn = bool (*)(uevr::API::UObject*, float, float, float, void*, void*, uevr::API::TArray&); 93 | MeleeTraceCheckFn m_melee_trace_check_hook_fn{}; 94 | bool on_melee_trace_check_internal(uevr::API::UObject*, float, float, float, void*, void*, uevr::API::TArray&); 95 | static bool on_melee_trace_check( 96 | uevr::API::UObject* a1, 97 | float a2, float a3, float hit_rotation_ratio, 98 | void* a5, void* a6, 99 | uevr::API::TArray& hit_results 100 | ) 101 | { 102 | return g_plugin->on_melee_trace_check_internal(a1, a2, a3, hit_rotation_ratio, a5, a6, hit_results); 103 | } 104 | }; -------------------------------------------------------------------------------- /src/uevr/API.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file (API.h) is licensed under the MIT license and is separate from the rest of the UEVR codebase. 3 | 4 | Copyright (c) 2023 praydog 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* C API for UEVR. */ 26 | /* Must be ANSI C89 compatible. */ 27 | #ifndef UEVR_API_H 28 | #define UEVR_API_H 29 | 30 | #ifndef __cplusplus 31 | #include 32 | #include 33 | #endif 34 | 35 | #define UEVR_IN 36 | #define UEVR_OUT 37 | 38 | #define UEVR_PLUGIN_VERSION_MAJOR 2 39 | #define UEVR_PLUGIN_VERSION_MINOR 33 40 | #define UEVR_PLUGIN_VERSION_PATCH 0 41 | 42 | #define UEVR_RENDERER_D3D11 0 43 | #define UEVR_RENDERER_D3D12 1 44 | 45 | typedef struct { 46 | int major; 47 | int minor; 48 | int patch; 49 | } UEVR_PluginVersion; 50 | 51 | /* strong typedefs */ 52 | #define DECLARE_UEVR_HANDLE(name) struct name##__ { int unused; }; \ 53 | typedef struct name##__ *name 54 | 55 | DECLARE_UEVR_HANDLE(UEVR_UGameEngineHandle); 56 | DECLARE_UEVR_HANDLE(UEVR_UEngineHandle); 57 | DECLARE_UEVR_HANDLE(UEVR_FSlateRHIRendererHandle); 58 | DECLARE_UEVR_HANDLE(UEVR_FViewportInfoHandle); 59 | DECLARE_UEVR_HANDLE(UEVR_UGameViewportClientHandle); 60 | DECLARE_UEVR_HANDLE(UEVR_FViewportHandle); 61 | DECLARE_UEVR_HANDLE(UEVR_FCanvasHandle); 62 | DECLARE_UEVR_HANDLE(UEVR_UObjectArrayHandle); 63 | DECLARE_UEVR_HANDLE(UEVR_UObjectHandle); 64 | DECLARE_UEVR_HANDLE(UEVR_FFieldHandle); 65 | DECLARE_UEVR_HANDLE(UEVR_UFieldHandle); 66 | DECLARE_UEVR_HANDLE(UEVR_FPropertyHandle); 67 | DECLARE_UEVR_HANDLE(UEVR_UStructHandle); 68 | DECLARE_UEVR_HANDLE(UEVR_UClassHandle); 69 | DECLARE_UEVR_HANDLE(UEVR_UFunctionHandle); 70 | DECLARE_UEVR_HANDLE(UEVR_FNameHandle); 71 | DECLARE_UEVR_HANDLE(UEVR_FFieldClassHandle); 72 | DECLARE_UEVR_HANDLE(UEVR_FConsoleManagerHandle); 73 | DECLARE_UEVR_HANDLE(UEVR_IConsoleObjectHandle); 74 | DECLARE_UEVR_HANDLE(UEVR_IConsoleCommandHandle); 75 | DECLARE_UEVR_HANDLE(UEVR_IConsoleVariableHandle); 76 | DECLARE_UEVR_HANDLE(UEVR_TArrayHandle); 77 | DECLARE_UEVR_HANDLE(UEVR_FMallocHandle); 78 | DECLARE_UEVR_HANDLE(UEVR_FRHITexture2DHandle); 79 | DECLARE_UEVR_HANDLE(UEVR_UScriptStructHandle); 80 | DECLARE_UEVR_HANDLE(UEVR_FArrayPropertyHandle); 81 | DECLARE_UEVR_HANDLE(UEVR_FBoolPropertyHandle); 82 | DECLARE_UEVR_HANDLE(UEVR_FStructPropertyHandle); 83 | DECLARE_UEVR_HANDLE(UEVR_FEnumPropertyHandle); 84 | DECLARE_UEVR_HANDLE(UEVR_UEnumHandle); 85 | DECLARE_UEVR_HANDLE(UEVR_FNumericPropertyHandle); 86 | 87 | /* OpenXR stuff */ 88 | DECLARE_UEVR_HANDLE(UEVR_XrInstance); 89 | DECLARE_UEVR_HANDLE(UEVR_XrSession); 90 | DECLARE_UEVR_HANDLE(UEVR_XrSpace); 91 | 92 | #define UEVR_LEFT_EYE 0 93 | #define UEVR_RIGHT_EYE 1 94 | 95 | #define UEVR_OPENXR_SWAPCHAIN_LEFT_EYE 0 96 | #define UEVR_OPENXR_SWAPCHAIN_RIGHT_EYE 1 97 | #define UEVR_OPENXR_SWAPCHAIN_UI 2 98 | 99 | typedef int UEVR_Eye; 100 | typedef int UEVR_TrackedDeviceIndex; 101 | typedef int UEVR_OpenXRSwapchainIndex; 102 | 103 | typedef struct { 104 | float x; 105 | float y; 106 | } UEVR_Vector2f; 107 | 108 | typedef struct { 109 | float x; 110 | float y; 111 | float z; 112 | } UEVR_Vector3f; 113 | 114 | typedef struct { 115 | double x; 116 | double y; 117 | double z; 118 | } UEVR_Vector3d; 119 | 120 | typedef struct { 121 | float x; 122 | float y; 123 | float z; 124 | float w; 125 | } UEVR_Vector4f; 126 | 127 | typedef struct { 128 | float w; 129 | float x; 130 | float y; 131 | float z; 132 | } UEVR_Quaternionf; 133 | 134 | typedef struct { 135 | float pitch; 136 | float yaw; 137 | float roll; 138 | } UEVR_Rotatorf; 139 | 140 | typedef struct { 141 | double pitch; 142 | double yaw; 143 | double roll; 144 | } UEVR_Rotatord; 145 | 146 | typedef struct { 147 | float m[4][4]; 148 | } UEVR_Matrix4x4f; 149 | 150 | typedef struct { 151 | double m[4][4]; 152 | } UEVR_Matrix4x4d; 153 | 154 | /* Generic DX renderer callbacks */ 155 | typedef void (*UEVR_OnPresentCb)(); 156 | typedef void (*UEVR_OnDeviceResetCb)(); 157 | 158 | /* VR Specific renderer callbacks */ 159 | typedef void (*UEVR_OnPostRenderVRFrameworkDX11Cb)(void*, void*, void*); /* immediate_context, ID3D11Texture2D* resource, ID3D11RenderTargetView* rtv */ 160 | /* On DX12 the resource state is D3D12_RESOURCE_STATE_RENDER_TARGET */ 161 | typedef void (*UEVR_OnPostRenderVRFrameworkDX12Cb)(void*, void*, void*); /* command_list, ID3D12Resource* resource, D3D12_CPU_DESCRIPTOR_HANDLE* rtv */ 162 | 163 | /* Windows callbacks*/ 164 | typedef bool (*UEVR_OnMessageCb)(void*, unsigned int, unsigned long long, long long); 165 | typedef void (*UEVR_OnXInputGetStateCb)(unsigned int*, unsigned int, void*); /* retval, dwUserIndex, pState, read MSDN for details */ 166 | typedef void (*UEVR_OnXInputSetStateCb)(unsigned int*, unsigned int, void*); /* retval, dwUserIndex, pVibration, read MSDN for details */ 167 | 168 | /* UE Callbacks */ 169 | typedef void (*UEVR_Engine_TickCb)(UEVR_UGameEngineHandle engine, float delta_seconds); 170 | typedef void (*UEVR_Slate_DrawWindow_RenderThreadCb)(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info); 171 | typedef void (*UEVR_ViewportClient_DrawCb)(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas); 172 | 173 | DECLARE_UEVR_HANDLE(UEVR_StereoRenderingDeviceHandle); 174 | /* the position and rotation must be converted to double format based on the is_double parameter. */ 175 | typedef void (*UEVR_Stereo_CalculateStereoViewOffsetCb)(UEVR_StereoRenderingDeviceHandle, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double); 176 | 177 | /* Generic DX Renderer */ 178 | typedef bool (*UEVR_OnPresentFn)(UEVR_OnPresentCb); 179 | typedef bool (*UEVR_OnDeviceResetFn)(UEVR_OnDeviceResetCb); 180 | 181 | /* VR Renderer */ 182 | typedef bool (*UEVR_OnPostRenderVRFrameworkDX11Fn)(UEVR_OnPostRenderVRFrameworkDX11Cb); 183 | typedef bool (*UEVR_OnPostRenderVRFrameworkDX12Fn)(UEVR_OnPostRenderVRFrameworkDX12Cb); 184 | 185 | /* Windows */ 186 | typedef bool (*UEVR_OnMessageFn)(UEVR_OnMessageCb); 187 | typedef bool (*UEVR_OnXInputGetStateFn)(UEVR_OnXInputGetStateCb); 188 | typedef bool (*UEVR_OnXInputSetStateFn)(UEVR_OnXInputSetStateCb); 189 | 190 | /* Engine */ 191 | typedef bool (*UEVR_Engine_TickFn)(UEVR_Engine_TickCb); 192 | typedef bool (*UEVR_Slate_DrawWindow_RenderThreadFn)(UEVR_Slate_DrawWindow_RenderThreadCb); 193 | typedef bool (*UEVR_Stereo_CalculateStereoViewOffsetFn)(UEVR_Stereo_CalculateStereoViewOffsetCb); 194 | typedef bool (*UEVR_ViewportClient_DrawFn)(UEVR_ViewportClient_DrawCb); 195 | typedef bool (*UEVR_UFunction_NativeFn)(UEVR_UObjectHandle, void*, void*); /* obj, frame, ret */ 196 | typedef bool (*UEVR_UFunction_NativePreFn)(UEVR_UFunctionHandle, UEVR_UObjectHandle, void*, void*); /* obj, frame, ret */ 197 | typedef bool (*UEVR_UFunction_NativePostFn)(UEVR_UFunctionHandle, UEVR_UObjectHandle, void*, void*); /* obj, frame, ret */ 198 | 199 | typedef void (*UEVR_PluginRequiredVersionFn)(UEVR_PluginVersion*); 200 | 201 | typedef struct { 202 | UEVR_OnPresentFn on_present; 203 | UEVR_OnDeviceResetFn on_device_reset; 204 | UEVR_OnMessageFn on_message; 205 | UEVR_OnXInputGetStateFn on_xinput_get_state; 206 | UEVR_OnXInputSetStateFn on_xinput_set_state; 207 | UEVR_OnPostRenderVRFrameworkDX11Fn on_post_render_vr_framework_dx11; 208 | UEVR_OnPostRenderVRFrameworkDX12Fn on_post_render_vr_framework_dx12; 209 | } UEVR_PluginCallbacks; 210 | 211 | typedef struct { 212 | void (*log_error)(const char* format, ...); 213 | void (*log_warn)(const char* format, ...); 214 | void (*log_info)(const char* format, ...); 215 | bool (*is_drawing_ui)(); 216 | bool (*remove_callback)(void* cb); 217 | unsigned int (*get_persistent_dir)(wchar_t* buffer, unsigned int buffer_size); 218 | int (*register_inline_hook)(void* target, void* dst, void** original); 219 | void (*unregister_inline_hook)(int hook_id); 220 | void (*dispatch_lua_event)(const char* event_name, const char* event_data); 221 | } UEVR_PluginFunctions; 222 | 223 | typedef struct { 224 | UEVR_Engine_TickFn on_pre_engine_tick; 225 | UEVR_Engine_TickFn on_post_engine_tick; 226 | UEVR_Slate_DrawWindow_RenderThreadFn on_pre_slate_draw_window_render_thread; 227 | UEVR_Slate_DrawWindow_RenderThreadFn on_post_slate_draw_window_render_thread; 228 | UEVR_Stereo_CalculateStereoViewOffsetFn on_pre_calculate_stereo_view_offset; 229 | UEVR_Stereo_CalculateStereoViewOffsetFn on_post_calculate_stereo_view_offset; 230 | UEVR_ViewportClient_DrawFn on_pre_viewport_client_draw; 231 | UEVR_ViewportClient_DrawFn on_post_viewport_client_draw; 232 | 233 | UEVR_Stereo_CalculateStereoViewOffsetFn on_early_calculate_stereo_view_offset; 234 | } UEVR_SDKCallbacks; 235 | 236 | typedef struct { 237 | int renderer_type; 238 | void* device; 239 | void* swapchain; 240 | void* command_queue; 241 | } UEVR_RendererData; 242 | 243 | typedef struct { 244 | UEVR_UEngineHandle (*get_uengine)(); 245 | void (*set_cvar_int)(const char* module_name, const char* name, int value); 246 | UEVR_UObjectArrayHandle (*get_uobject_array)(); 247 | 248 | UEVR_UObjectHandle (*get_player_controller)(int index); 249 | UEVR_UObjectHandle (*get_local_pawn)(int index); 250 | UEVR_UObjectHandle (*spawn_object)(UEVR_UClassHandle klass, UEVR_UObjectHandle outer); 251 | 252 | /* Handles exec commands, find_console_command does not */ 253 | void (*execute_command)(const wchar_t* command); 254 | void (*execute_command_ex)(UEVR_UObjectHandle world, const wchar_t* command, void* output_device); 255 | 256 | UEVR_FConsoleManagerHandle (*get_console_manager)(); 257 | } UEVR_SDKFunctions; 258 | 259 | typedef struct { 260 | UEVR_TArrayHandle (*get_console_objects)(UEVR_FConsoleManagerHandle mgr); 261 | UEVR_IConsoleObjectHandle (*find_object)(UEVR_FConsoleManagerHandle mgr, const wchar_t* name); 262 | UEVR_IConsoleVariableHandle (*find_variable)(UEVR_FConsoleManagerHandle mgr, const wchar_t* name); 263 | UEVR_IConsoleCommandHandle (*find_command)(UEVR_FConsoleManagerHandle mgr, const wchar_t* name); 264 | 265 | UEVR_IConsoleCommandHandle (*as_command)(UEVR_IConsoleObjectHandle object); 266 | 267 | void (*variable_set)(UEVR_IConsoleVariableHandle cvar, const wchar_t* value); 268 | void (*variable_set_ex)(UEVR_IConsoleVariableHandle cvar, const wchar_t* value, unsigned int flags); 269 | int (*variable_get_int)(UEVR_IConsoleVariableHandle cvar); 270 | float (*variable_get_float)(UEVR_IConsoleVariableHandle cvar); 271 | 272 | /* better to just use execute_command if possible */ 273 | void (*command_execute)(UEVR_IConsoleCommandHandle cmd, const wchar_t* args); 274 | } UEVR_ConsoleFunctions; 275 | 276 | DECLARE_UEVR_HANDLE(UEVR_FUObjectItemHandle); 277 | 278 | typedef struct { 279 | UEVR_UObjectHandle (*find_uobject)(const wchar_t* name); 280 | 281 | bool (*is_chunked)(); 282 | bool (*is_inlined)(); 283 | unsigned int (*get_objects_offset)(); 284 | unsigned int (*get_item_distance)(); 285 | 286 | int (*get_object_count)(UEVR_UObjectArrayHandle array); 287 | void* (*get_objects_ptr)(UEVR_UObjectArrayHandle array); 288 | UEVR_UObjectHandle (*get_object)(UEVR_UObjectArrayHandle array, int index); 289 | UEVR_FUObjectItemHandle (*get_item)(UEVR_UObjectArrayHandle array, int index); 290 | } UEVR_UObjectArrayFunctions; 291 | 292 | typedef struct { 293 | UEVR_FFieldHandle (*get_next)(UEVR_FFieldHandle field); 294 | UEVR_FFieldClassHandle (*get_class)(UEVR_FFieldHandle field); 295 | UEVR_FNameHandle (*get_fname)(UEVR_FFieldHandle field); 296 | } UEVR_FFieldFunctions; 297 | 298 | typedef struct { 299 | UEVR_UFieldHandle (*get_next)(UEVR_UFieldHandle field); 300 | } UEVR_UFieldFunctions; 301 | 302 | typedef struct { 303 | int (*get_offset)(UEVR_FPropertyHandle prop); 304 | unsigned long long (*get_property_flags)(UEVR_FPropertyHandle prop); 305 | bool (*is_param)(UEVR_FPropertyHandle prop); 306 | bool (*is_out_param)(UEVR_FPropertyHandle prop); 307 | bool (*is_return_param)(UEVR_FPropertyHandle prop); 308 | bool (*is_reference_param)(UEVR_FPropertyHandle prop); 309 | bool (*is_pod)(UEVR_FPropertyHandle prop); 310 | } UEVR_FPropertyFunctions; 311 | 312 | typedef struct { 313 | UEVR_UStructHandle (*get_super_struct)(UEVR_UStructHandle klass); 314 | UEVR_FFieldHandle (*get_child_properties)(UEVR_UStructHandle klass); 315 | UEVR_UFunctionHandle (*find_function)(UEVR_UStructHandle klass, const wchar_t* name); 316 | UEVR_FPropertyHandle (*find_property)(UEVR_UStructHandle klass, const wchar_t* name); 317 | int (*get_properties_size)(UEVR_UStructHandle klass); /* size in bytes */ 318 | int (*get_min_alignment)(UEVR_UStructHandle klass); 319 | UEVR_UFieldHandle (*get_children)(UEVR_UStructHandle klass); 320 | } UEVR_UStructFunctions; 321 | 322 | typedef struct { 323 | UEVR_UObjectHandle (*get_class_default_object)(UEVR_UClassHandle klass); 324 | } UEVR_UClassFunctions; 325 | 326 | typedef struct { 327 | void* (*get_native_function)(UEVR_UFunctionHandle function); 328 | bool (*hook_ptr)(UEVR_UFunctionHandle function, UEVR_UFunction_NativePreFn pre_hook, UEVR_UFunction_NativePostFn post_hook); 329 | unsigned int (*get_function_flags)(UEVR_UFunctionHandle function); 330 | void (*set_function_flags)(UEVR_UFunctionHandle function, unsigned int flags); 331 | } UEVR_UFunctionFunctions; 332 | 333 | typedef struct { 334 | UEVR_UClassHandle (*get_class)(UEVR_UObjectHandle object); 335 | UEVR_UObjectHandle (*get_outer)(UEVR_UObjectHandle object); 336 | 337 | /* pointer to the property data, not the UProperty/FProperty */ 338 | void* (*get_property_data)(UEVR_UObjectHandle object, const wchar_t* name); 339 | bool (*is_a)(UEVR_UObjectHandle object, UEVR_UClassHandle other); 340 | 341 | void (*process_event)(UEVR_UObjectHandle object, UEVR_UFunctionHandle function, void* params); 342 | void (*call_function)(UEVR_UObjectHandle object, const wchar_t* name, void* params); 343 | 344 | UEVR_FNameHandle (*get_fname)(UEVR_UObjectHandle object); 345 | 346 | bool (*get_bool_property)(UEVR_UObjectHandle object, const wchar_t* name); 347 | void (*set_bool_property)(UEVR_UObjectHandle object, const wchar_t* name, bool value); 348 | } UEVR_UObjectFunctions; 349 | 350 | DECLARE_UEVR_HANDLE(UEVR_UObjectHookMotionControllerStateHandle); 351 | 352 | typedef struct { 353 | void (*set_rotation_offset)(UEVR_UObjectHookMotionControllerStateHandle, const UEVR_Quaternionf* rotation); 354 | void (*set_location_offset)(UEVR_UObjectHookMotionControllerStateHandle, const UEVR_Vector3f* location); 355 | void (*set_hand)(UEVR_UObjectHookMotionControllerStateHandle, unsigned int hand); 356 | void (*set_permanent)(UEVR_UObjectHookMotionControllerStateHandle, bool permanent); 357 | } UEVR_UObjectHookMotionControllerStateFunctions; 358 | 359 | typedef struct { 360 | void (*activate)(); 361 | bool (*exists)(UEVR_UObjectHandle object); 362 | 363 | /* if 0 or nullptr is passed, it will return how many objects are in the array */ 364 | /* so you can allocate the right amount of memory */ 365 | int (*get_objects_by_class)(UEVR_UClassHandle klass, UEVR_UObjectHandle* out_objects, unsigned int max_objects, bool allow_default); 366 | int (*get_objects_by_class_name)(const wchar_t* class_name, UEVR_UObjectHandle* out_objects, unsigned int max_objects, bool allow_default); 367 | 368 | UEVR_UObjectHandle (*get_first_object_by_class)(UEVR_UClassHandle klass, bool allow_default); 369 | UEVR_UObjectHandle (*get_first_object_by_class_name)(const wchar_t* class_name, bool allow_default); 370 | 371 | UEVR_UObjectHookMotionControllerStateHandle (*get_or_add_motion_controller_state)(UEVR_UObjectHandle object); 372 | UEVR_UObjectHookMotionControllerStateHandle (*get_motion_controller_state)(UEVR_UObjectHandle object); 373 | 374 | UEVR_UObjectHookMotionControllerStateFunctions* mc_state; 375 | 376 | bool (*is_disabled)(); 377 | void (*set_disabled)(bool disabled); 378 | 379 | void (*remove_motion_controller_state)(UEVR_UObjectHandle object); 380 | } UEVR_UObjectHookFunctions; 381 | 382 | typedef struct { 383 | UEVR_FNameHandle (*get_fname)(UEVR_FFieldClassHandle field_class); 384 | } UEVR_FFieldClassFunctions; 385 | 386 | typedef struct { 387 | unsigned int (*to_string)(UEVR_FNameHandle name, wchar_t* buffer, unsigned int buffer_size); 388 | void (*constructor)(UEVR_FNameHandle name, const wchar_t* data, unsigned int find_type); 389 | } UEVR_FNameFunctions; 390 | 391 | typedef struct { 392 | UEVR_FMallocHandle (*get)(); 393 | 394 | void* (*malloc)(UEVR_FMallocHandle instance, unsigned int size, unsigned int alignment); 395 | void* (*realloc)(UEVR_FMallocHandle instance, void* ptr, unsigned int size, unsigned int alignment); 396 | void (*free)(UEVR_FMallocHandle instance, void* ptr); 397 | } UEVR_FMallocFunctions; 398 | 399 | DECLARE_UEVR_HANDLE(UEVR_IPooledRenderTargetHandle); 400 | 401 | typedef struct { 402 | void (*activate)(); 403 | UEVR_IPooledRenderTargetHandle (*get_render_target)(const wchar_t* name); 404 | } UEVR_FRenderTargetPoolHookFunctions; 405 | 406 | typedef struct { 407 | UEVR_FRHITexture2DHandle (*get_scene_render_target)(); 408 | UEVR_FRHITexture2DHandle (*get_ui_render_target)(); 409 | } UEVR_FFakeStereoRenderingHookFunctions; 410 | 411 | typedef struct { 412 | void* (*get_native_resource)(UEVR_FRHITexture2DHandle texture); 413 | } UEVR_FRHITexture2DFunctions; 414 | 415 | DECLARE_UEVR_HANDLE(UEVR_StructOpsHandle); 416 | 417 | typedef struct { 418 | UEVR_StructOpsHandle (*get_struct_ops)(UEVR_UScriptStructHandle script_struct); 419 | int (*get_struct_size)(UEVR_UScriptStructHandle script_struct); 420 | } UEVR_UScriptStructFunctions; 421 | 422 | typedef struct { 423 | UEVR_FPropertyHandle (*get_inner)(UEVR_FArrayPropertyHandle prop); 424 | } UEVR_FArrayPropertyFunctions; 425 | 426 | typedef struct { 427 | unsigned int (*get_field_size)(UEVR_FBoolPropertyHandle prop); 428 | unsigned int (*get_byte_offset)(UEVR_FBoolPropertyHandle prop); 429 | unsigned int (*get_byte_mask)(UEVR_FBoolPropertyHandle prop); 430 | unsigned int (*get_field_mask)(UEVR_FBoolPropertyHandle prop); 431 | bool (*get_value_from_object)(UEVR_FBoolPropertyHandle prop, void* object); 432 | bool (*get_value_from_propbase)(UEVR_FBoolPropertyHandle prop, void* addr); 433 | void (*set_value_in_object)(UEVR_FBoolPropertyHandle prop, void* object, bool value); 434 | void (*set_value_in_propbase)(UEVR_FBoolPropertyHandle prop, void* addr, bool value); 435 | } UEVR_FBoolPropertyFunctions; 436 | 437 | typedef struct { 438 | UEVR_UScriptStructHandle (*get_struct)(UEVR_FStructPropertyHandle prop); 439 | } UEVR_FStructPropertyFunctions; 440 | 441 | typedef struct { 442 | UEVR_FNumericPropertyHandle (*get_underlying_prop)(UEVR_FEnumPropertyHandle prop); 443 | UEVR_UEnumHandle (*get_enum)(UEVR_FEnumPropertyHandle prop); 444 | } UEVR_FEnumPropertyFunctions; 445 | 446 | typedef struct { 447 | const UEVR_SDKFunctions* functions; 448 | const UEVR_SDKCallbacks* callbacks; 449 | const UEVR_UObjectFunctions* uobject; 450 | const UEVR_UObjectArrayFunctions* uobject_array; 451 | const UEVR_FFieldFunctions* ffield; 452 | const UEVR_FPropertyFunctions* fproperty; 453 | const UEVR_UStructFunctions* ustruct; 454 | const UEVR_UClassFunctions* uclass; 455 | const UEVR_UFunctionFunctions* ufunction; 456 | const UEVR_UObjectHookFunctions* uobject_hook; 457 | const UEVR_FFieldClassFunctions* ffield_class; 458 | const UEVR_FNameFunctions* fname; 459 | const UEVR_ConsoleFunctions* console; 460 | const UEVR_FMallocFunctions* malloc; 461 | const UEVR_FRenderTargetPoolHookFunctions* render_target_pool_hook; 462 | const UEVR_FFakeStereoRenderingHookFunctions* stereo_hook; 463 | const UEVR_FRHITexture2DFunctions* frhitexture2d; 464 | const UEVR_UScriptStructFunctions* uscriptstruct; 465 | const UEVR_FArrayPropertyFunctions* farrayproperty; 466 | const UEVR_FBoolPropertyFunctions* fboolproperty; 467 | const UEVR_FStructPropertyFunctions* fstructproperty; 468 | const UEVR_FEnumPropertyFunctions* fenumproperty; 469 | const UEVR_UFieldFunctions* ufield; 470 | } UEVR_SDKData; 471 | 472 | DECLARE_UEVR_HANDLE(UEVR_IVRSystem); 473 | DECLARE_UEVR_HANDLE(UEVR_IVRChaperone); 474 | DECLARE_UEVR_HANDLE(UEVR_IVRChaperoneSetup); 475 | DECLARE_UEVR_HANDLE(UEVR_IVRCompositor); 476 | DECLARE_UEVR_HANDLE(UEVR_IVROverlay); 477 | DECLARE_UEVR_HANDLE(UEVR_IVROverlayView); 478 | DECLARE_UEVR_HANDLE(UEVR_IVRHeadsetView); 479 | DECLARE_UEVR_HANDLE(UEVR_IVRScreenshots); 480 | DECLARE_UEVR_HANDLE(UEVR_IVRRenderModels); 481 | DECLARE_UEVR_HANDLE(UEVR_IVRApplications); 482 | DECLARE_UEVR_HANDLE(UEVR_IVRSettings); 483 | DECLARE_UEVR_HANDLE(UEVR_IVRResources); 484 | DECLARE_UEVR_HANDLE(UEVR_IVRExtendedDisplay); 485 | DECLARE_UEVR_HANDLE(UEVR_IVRTrackedCamera); 486 | DECLARE_UEVR_HANDLE(UEVR_IVRDriverManager); 487 | DECLARE_UEVR_HANDLE(UEVR_IVRInput); 488 | DECLARE_UEVR_HANDLE(UEVR_IVRIOBuffer); 489 | DECLARE_UEVR_HANDLE(UEVR_IVRSpatialAnchors); 490 | DECLARE_UEVR_HANDLE(UEVR_IVRNotifications); 491 | DECLARE_UEVR_HANDLE(UEVR_IVRDebug); 492 | 493 | typedef struct { 494 | UEVR_IVRSystem (*get_vr_system)(); 495 | UEVR_IVRChaperone (*get_vr_chaperone)(); 496 | UEVR_IVRChaperoneSetup (*get_vr_chaperone_setup)(); 497 | UEVR_IVRCompositor (*get_vr_compositor)(); 498 | UEVR_IVROverlay (*get_vr_overlay)(); 499 | UEVR_IVROverlayView (*get_vr_overlay_view)(); 500 | UEVR_IVRHeadsetView (*get_vr_headset_view)(); 501 | UEVR_IVRScreenshots (*get_vr_screenshots)(); 502 | UEVR_IVRRenderModels (*get_vr_render_models)(); 503 | UEVR_IVRApplications (*get_vr_applications)(); 504 | UEVR_IVRSettings (*get_vr_settings)(); 505 | UEVR_IVRResources (*get_vr_resources)(); 506 | UEVR_IVRExtendedDisplay (*get_vr_extended_display)(); 507 | UEVR_IVRTrackedCamera (*get_vr_tracked_camera)(); 508 | UEVR_IVRDriverManager (*get_vr_driver_manager)(); 509 | UEVR_IVRInput (*get_vr_input)(); 510 | UEVR_IVRIOBuffer (*get_vr_io_buffer)(); 511 | UEVR_IVRSpatialAnchors (*get_vr_spatial_anchors)(); 512 | UEVR_IVRNotifications (*get_vr_notifications)(); 513 | UEVR_IVRDebug (*get_vr_debug)(); 514 | } UEVR_OpenVRData; 515 | 516 | typedef struct { 517 | UEVR_XrInstance (*get_xr_instance)(); 518 | UEVR_XrSession (*get_xr_session)(); 519 | 520 | UEVR_XrSpace (*get_stage_space)(); /* XrSpace */ 521 | UEVR_XrSpace (*get_view_space)(); /* XrSpace */ 522 | } UEVR_OpenXRData; 523 | 524 | DECLARE_UEVR_HANDLE(UEVR_ActionHandle); 525 | DECLARE_UEVR_HANDLE(UEVR_InputSourceHandle); 526 | 527 | typedef struct { 528 | bool (*is_runtime_ready)(); 529 | bool (*is_openvr)(); 530 | bool (*is_openxr)(); 531 | 532 | bool (*is_hmd_active)(); 533 | 534 | void (*get_standing_origin)(UEVR_Vector3f* out_origin); 535 | void (*get_rotation_offset)(UEVR_Quaternionf* out_rotation); 536 | void (*set_standing_origin)(const UEVR_Vector3f* origin); 537 | void (*set_rotation_offset)(const UEVR_Quaternionf* rotation); 538 | 539 | UEVR_TrackedDeviceIndex (*get_hmd_index)(); 540 | UEVR_TrackedDeviceIndex (*get_left_controller_index)(); 541 | UEVR_TrackedDeviceIndex (*get_right_controller_index)(); 542 | 543 | /* OpenVR/OpenXR space. */ 544 | void (*get_pose)(UEVR_TrackedDeviceIndex index, UEVR_Vector3f* out_position, UEVR_Quaternionf* out_rotation); 545 | void (*get_transform)(UEVR_TrackedDeviceIndex index, UEVR_Matrix4x4f* out_transform); 546 | 547 | /* For getting grip or aim poses for the controllers */ 548 | void (*get_grip_pose)(UEVR_TrackedDeviceIndex index, UEVR_Vector3f* out_position, UEVR_Quaternionf* out_rotation); 549 | void (*get_aim_pose)(UEVR_TrackedDeviceIndex index, UEVR_Vector3f* out_position, UEVR_Quaternionf* out_rotation); 550 | void (*get_grip_transform)(UEVR_TrackedDeviceIndex index, UEVR_Matrix4x4f* out_transform); 551 | void (*get_aim_transform)(UEVR_TrackedDeviceIndex index, UEVR_Matrix4x4f* out_transform); 552 | 553 | void (*get_eye_offset)(UEVR_Eye eye, UEVR_Vector3f* out_position); 554 | 555 | /* Converted to UE projection matrix */ 556 | void (*get_ue_projection_matrix)(UEVR_Eye eye, UEVR_Matrix4x4f* out_projection); 557 | 558 | UEVR_InputSourceHandle (*get_left_joystick_source)(); 559 | UEVR_InputSourceHandle (*get_right_joystick_source)(); 560 | 561 | UEVR_ActionHandle (*get_action_handle)(const char* action_path); 562 | 563 | bool (*is_action_active)(UEVR_ActionHandle action, UEVR_InputSourceHandle source); 564 | bool (*is_action_active_any_joystick)(UEVR_ActionHandle action); 565 | void (*get_joystick_axis)(UEVR_InputSourceHandle source, UEVR_Vector2f* out_axis); 566 | void (*trigger_haptic_vibration)(float seconds_from_now, float duration, float frequency, float amplitude, UEVR_InputSourceHandle source); 567 | /* if any controller action is active or has been active within certain previous timeframe */ 568 | bool (*is_using_controllers)(); 569 | bool (*is_decoupled_pitch_enabled)(); 570 | 571 | unsigned int (*get_movement_orientation)(); 572 | unsigned int (*get_lowest_xinput_index)(); 573 | 574 | void (*recenter_view)(); 575 | void (*recenter_horizon)(); 576 | 577 | unsigned int (*get_aim_method)(); 578 | void (*set_aim_method)(unsigned int method); 579 | bool (*is_aim_allowed)(); 580 | void (*set_aim_allowed)(bool allowed); 581 | 582 | unsigned int (*get_hmd_width)(); 583 | unsigned int (*get_hmd_height)(); 584 | unsigned int (*get_ui_width)(); 585 | unsigned int (*get_ui_height)(); 586 | 587 | bool (*is_snap_turn_enabled)(); 588 | void (*set_snap_turn_enabled)(bool enabled); 589 | void (*set_decoupled_pitch_enabled)(bool enabled); 590 | 591 | void (*set_mod_value)(const char* key, const char* value); 592 | void (*get_mod_value)(const char* key, char* value, unsigned int value_size); 593 | void (*save_config)(); 594 | void (*reload_config)(); 595 | } UEVR_VRData; 596 | 597 | typedef struct { 598 | void* uevr_module; 599 | const UEVR_PluginVersion* version; 600 | const UEVR_PluginFunctions* functions; 601 | const UEVR_PluginCallbacks* callbacks; 602 | const UEVR_RendererData* renderer; 603 | 604 | const UEVR_VRData* vr; 605 | const UEVR_OpenVRData* openvr; 606 | const UEVR_OpenXRData* openxr; 607 | 608 | /* Engine/Game specific functions and data */ 609 | const UEVR_SDKData* sdk; 610 | } UEVR_PluginInitializeParam; 611 | 612 | typedef bool (*UEVR_PluginInitializeFn)(const UEVR_PluginInitializeParam*); 613 | 614 | #endif -------------------------------------------------------------------------------- /src/uevr/API.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file (API.hpp) is licensed under the MIT license and is separate from the rest of the UEVR codebase. 3 | 4 | Copyright (c) 2023 praydog 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | extern "C" { 28 | #include "API.h" 29 | } 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | namespace uevr { 44 | class API { 45 | private: 46 | static inline std::unique_ptr s_instance{}; 47 | 48 | public: 49 | // ALWAYS call initialize first in uevr_plugin_initialize 50 | static auto& initialize(const UEVR_PluginInitializeParam* param) { 51 | if (param == nullptr) { 52 | throw std::runtime_error("param is null"); 53 | } 54 | 55 | if (s_instance != nullptr) { 56 | return s_instance; 57 | } 58 | 59 | s_instance = std::make_unique(param); 60 | return s_instance; 61 | } 62 | 63 | // only call this AFTER calling initialize 64 | static auto& get() { 65 | if (s_instance == nullptr) { 66 | throw std::runtime_error("API not initialized"); 67 | } 68 | 69 | return s_instance; 70 | } 71 | 72 | public: 73 | API(const UEVR_PluginInitializeParam* param) 74 | : m_param{param}, 75 | m_sdk{param->sdk} 76 | { 77 | } 78 | 79 | virtual ~API() { 80 | 81 | } 82 | 83 | inline const UEVR_PluginInitializeParam* param() const { 84 | return m_param; 85 | } 86 | 87 | inline const UEVR_SDKData* sdk() const { 88 | return m_sdk; 89 | } 90 | 91 | std::filesystem::path get_persistent_dir(std::optional file = std::nullopt) { 92 | static const auto fn = param()->functions->get_persistent_dir; 93 | const auto size = fn(nullptr, 0); 94 | if (size == 0) { 95 | return std::filesystem::path{}; 96 | } 97 | 98 | std::wstring result(size, L'\0'); 99 | fn(result.data(), size + 1); 100 | 101 | if (file.has_value()) { 102 | return std::filesystem::path{result} / file.value(); 103 | } 104 | 105 | return result; 106 | } 107 | 108 | void dispatch_lua_event(std::string_view event_name, std::string_view event_data) { 109 | static const auto fn = param()->functions->dispatch_lua_event; 110 | fn(event_name.data(), event_data.data()); 111 | } 112 | 113 | template void log_error(const char* format, Args... args) { m_param->functions->log_error(format, args...); } 114 | template void log_warn(const char* format, Args... args) { m_param->functions->log_warn(format, args...); } 115 | template void log_info(const char* format, Args... args) { m_param->functions->log_info(format, args...); } 116 | 117 | public: 118 | // C++ wrapper structs for the C structs 119 | struct UObject; 120 | struct UEngine; 121 | struct UGameEngine; 122 | struct UWorld; 123 | struct UStruct; 124 | struct UClass; 125 | struct UFunction; 126 | struct UScriptStruct; 127 | struct UStructOps; 128 | struct FField; 129 | struct UField; 130 | struct FProperty; 131 | struct FFieldClass; 132 | struct FUObjectArray; 133 | struct FName; 134 | struct FConsoleManager; 135 | struct IConsoleObject; 136 | struct IConsoleVariable; 137 | struct IConsoleCommand; 138 | struct ConsoleObjectElement; 139 | struct UObjectHook; 140 | struct FRHITexture2D; 141 | struct IPooledRenderTarget; 142 | 143 | template 144 | struct TArray; 145 | 146 | // dynamic_cast variant 147 | template 148 | static T* dcast(UObject* obj) { 149 | if (obj == nullptr) { 150 | return nullptr; 151 | } 152 | 153 | if (obj->is_a(T::static_class())) { 154 | return static_cast(obj); 155 | } 156 | 157 | return nullptr; 158 | } 159 | 160 | template 161 | T* find_uobject(std::wstring_view name) { 162 | static const auto fn = sdk()->uobject_array->find_uobject; 163 | return (T*)fn(name.data()); 164 | } 165 | 166 | UEngine* get_engine() { 167 | static const auto fn = sdk()->functions->get_uengine; 168 | return (UEngine*)fn(); 169 | } 170 | 171 | UObject* get_player_controller(int32_t index) { 172 | static const auto fn = sdk()->functions->get_player_controller; 173 | return (UObject*)fn(index); 174 | } 175 | 176 | UObject* get_local_pawn(int32_t index) { 177 | static const auto fn = sdk()->functions->get_local_pawn; 178 | return (UObject*)fn(index); 179 | } 180 | 181 | UObject* spawn_object(UClass* klass, UObject* outer) { 182 | static const auto fn = sdk()->functions->spawn_object; 183 | return (UObject*)fn(klass->to_handle(), outer->to_handle()); 184 | } 185 | 186 | void execute_command(std::wstring_view command) { 187 | static const auto fn = sdk()->functions->execute_command; 188 | fn(command.data()); 189 | } 190 | 191 | void execute_command_ex(UWorld* world, std::wstring_view command, void* output_device) { 192 | static const auto fn = sdk()->functions->execute_command_ex; 193 | fn((UEVR_UObjectHandle)world, command.data(), output_device); 194 | } 195 | 196 | FUObjectArray* get_uobject_array() { 197 | static const auto fn = sdk()->functions->get_uobject_array; 198 | return (FUObjectArray*)fn(); 199 | } 200 | 201 | FConsoleManager* get_console_manager() { 202 | static const auto fn = sdk()->functions->get_console_manager; 203 | return (FConsoleManager*)fn(); 204 | } 205 | 206 | struct FMalloc { 207 | static FMalloc* get() { 208 | static const auto fn = initialize()->get; 209 | return (FMalloc*)fn(); 210 | } 211 | 212 | inline UEVR_FMallocHandle to_handle() { return (UEVR_FMallocHandle)this; } 213 | inline UEVR_FMallocHandle to_handle() const { return (UEVR_FMallocHandle)this; } 214 | 215 | using FMallocSizeT = uint32_t; // because of C89 216 | 217 | void* malloc(FMallocSizeT size, uint32_t alignment = 0) { 218 | static const auto fn = initialize()->malloc; 219 | return fn(to_handle(), size, alignment); 220 | } 221 | 222 | void* realloc(void* original, FMallocSizeT size, uint32_t alignment = 0) { 223 | static const auto fn = initialize()->realloc; 224 | return fn(to_handle(), original, size, alignment); 225 | } 226 | 227 | void free(void* original) { 228 | static const auto fn = initialize()->free; 229 | fn(to_handle(), original); 230 | } 231 | 232 | private: 233 | static inline const UEVR_FMallocFunctions* s_functions{nullptr}; 234 | static inline const UEVR_FMallocFunctions* initialize() { 235 | if (s_functions == nullptr) { 236 | s_functions = API::get()->sdk()->malloc; 237 | } 238 | 239 | return s_functions; 240 | } 241 | }; 242 | 243 | struct FName { 244 | inline UEVR_FNameHandle to_handle() { return (UEVR_FNameHandle)this; } 245 | inline UEVR_FNameHandle to_handle() const { return (UEVR_FNameHandle)this; } 246 | 247 | enum class EFindName : uint32_t { 248 | Find, 249 | Add 250 | }; 251 | 252 | FName() = default; 253 | FName(std::wstring_view name, EFindName find_type = EFindName::Add) { 254 | static const auto fn = initialize()->constructor; 255 | fn(to_handle(), name.data(), (uint32_t)find_type); 256 | } 257 | 258 | std::wstring to_string() const { 259 | static const auto fn = initialize()->to_string; 260 | const auto size = fn(to_handle(), nullptr, 0); 261 | if (size == 0) { 262 | return L""; 263 | } 264 | 265 | std::wstring result(size, L'\0'); 266 | fn(to_handle(), result.data(), size + 1); 267 | return result; 268 | } 269 | 270 | int32_t comparison_index{}; 271 | int32_t number{}; 272 | 273 | private: 274 | static inline const UEVR_FNameFunctions* s_functions{nullptr}; 275 | static inline const UEVR_FNameFunctions* initialize() { 276 | if (s_functions == nullptr) { 277 | s_functions = API::get()->sdk()->fname; 278 | } 279 | 280 | return s_functions; 281 | } 282 | }; 283 | 284 | struct UObject { 285 | inline UEVR_UObjectHandle to_handle() { return (UEVR_UObjectHandle)this; } 286 | inline UEVR_UObjectHandle to_handle() const { return (UEVR_UObjectHandle)this; } 287 | 288 | static UClass* static_class() { 289 | static auto result = API::get()->find_uobject(L"Class /Script/CoreUObject.Object"); 290 | return result; 291 | } 292 | 293 | inline UClass* get_class() const { 294 | static const auto fn = initialize()->get_class; 295 | return (UClass*)fn(to_handle()); 296 | } 297 | 298 | inline UObject* get_outer() const { 299 | static const auto fn = initialize()->get_outer; 300 | return (UObject*)fn(to_handle()); 301 | } 302 | 303 | inline bool is_a(UClass* cmp) const { 304 | static const auto fn = initialize()->is_a; 305 | return fn(to_handle(), cmp->to_handle()); 306 | } 307 | 308 | void process_event(UFunction* function, void* params) { 309 | static const auto fn = initialize()->process_event; 310 | fn(to_handle(), function->to_handle(), params); 311 | } 312 | 313 | void call_function(std::wstring_view name, void* params) { 314 | static const auto fn = initialize()->call_function; 315 | fn(to_handle(), name.data(), params); 316 | } 317 | 318 | // Pointer that points to the address of the data within the object, not the data itself 319 | template 320 | T* get_property_data(std::wstring_view name) const { 321 | static const auto fn = initialize()->get_property_data; 322 | return (T*)fn(to_handle(), name.data()); 323 | } 324 | 325 | // Pointer that points to the address of the data within the object, not the data itself 326 | void* get_property_data(std::wstring_view name) const { 327 | return get_property_data(name); 328 | } 329 | 330 | // use this if you know for sure that the property exists 331 | // or you will cause an access violation 332 | template 333 | T& get_property(std::wstring_view name) const { 334 | return *get_property_data(name); 335 | } 336 | 337 | bool get_bool_property(std::wstring_view name) const { 338 | static const auto fn = initialize()->get_bool_property; 339 | return fn(to_handle(), name.data()); 340 | } 341 | 342 | void set_bool_property(std::wstring_view name, bool value) { 343 | static const auto fn = initialize()->set_bool_property; 344 | fn(to_handle(), name.data(), value); 345 | } 346 | 347 | FName* get_fname() const { 348 | static const auto fn = initialize()->get_fname; 349 | return (FName*)fn(to_handle()); 350 | } 351 | 352 | std::wstring get_full_name() const { 353 | const auto c = get_class(); 354 | 355 | if (c == nullptr) { 356 | return L""; 357 | } 358 | 359 | auto obj_name = get_fname()->to_string(); 360 | 361 | for (auto outer = this->get_outer(); outer != nullptr && outer != this; outer = outer->get_outer()) { 362 | obj_name = outer->get_fname()->to_string() + L'.' + obj_name; 363 | } 364 | 365 | return c->get_fname()->to_string() + L' ' + obj_name; 366 | } 367 | 368 | // dynamic_cast variant 369 | template 370 | T* dcast() { 371 | if (this->is_a(T::static_class())) { 372 | return static_cast(this); 373 | } 374 | 375 | return nullptr; 376 | } 377 | 378 | private: 379 | static inline const UEVR_UObjectFunctions* s_functions{nullptr}; 380 | inline static const UEVR_UObjectFunctions* initialize() { 381 | if (s_functions == nullptr) { 382 | s_functions = API::get()->sdk()->uobject; 383 | } 384 | 385 | return s_functions; 386 | } 387 | }; 388 | 389 | struct UField : public UObject { 390 | inline UEVR_UFieldHandle to_handle() { return (UEVR_UFieldHandle)this; } 391 | inline UEVR_UFieldHandle to_handle() const { return (UEVR_UFieldHandle)this; } 392 | 393 | static UClass* static_class() { 394 | static auto result = API::get()->find_uobject(L"Class /Script/CoreUObject.Field"); 395 | return result; 396 | } 397 | 398 | inline UField* get_next() const { 399 | static const auto fn = initialize()->get_next; 400 | return (UField*)fn(to_handle()); 401 | } 402 | 403 | private: 404 | static inline const UEVR_UFieldFunctions* s_functions{nullptr}; 405 | static inline const UEVR_UFieldFunctions* initialize() { 406 | if (s_functions == nullptr) { 407 | s_functions = API::get()->sdk()->ufield; 408 | } 409 | 410 | return s_functions; 411 | } 412 | }; 413 | 414 | struct UStruct : public UField { 415 | inline UEVR_UStructHandle to_handle() { return (UEVR_UStructHandle)this; } 416 | inline UEVR_UStructHandle to_handle() const { return (UEVR_UStructHandle)this; } 417 | 418 | static UClass* static_class() { 419 | static auto result = API::get()->find_uobject(L"Class /Script/CoreUObject.Struct"); 420 | return result; 421 | } 422 | 423 | UStruct* get_super_struct() const { 424 | static const auto fn = initialize()->get_super_struct; 425 | return (UStruct*)fn(to_handle()); 426 | } 427 | 428 | UStruct* get_super() const { 429 | return get_super_struct(); 430 | } 431 | 432 | UFunction* find_function(std::wstring_view name) const { 433 | static const auto fn = initialize()->find_function; 434 | return (UFunction*)fn(to_handle(), name.data()); 435 | } 436 | 437 | FProperty* find_property(std::wstring_view name) const { 438 | static const auto fn = initialize()->find_property; 439 | return (FProperty*)fn(to_handle(), name.data()); 440 | } 441 | 442 | // Not an array, it's a linked list. Meant to call ->get_next() until nullptr 443 | FField* get_child_properties() const { 444 | static const auto fn = initialize()->get_child_properties; 445 | return (FField*)fn(to_handle()); 446 | } 447 | 448 | UField* get_children() const { 449 | static const auto fn = initialize()->get_children; 450 | return (UField*)fn(to_handle()); 451 | } 452 | 453 | int32_t get_properties_size() const { 454 | static const auto fn = initialize()->get_properties_size; 455 | return fn(to_handle()); 456 | } 457 | 458 | int32_t get_min_alignment() const { 459 | static const auto fn = initialize()->get_min_alignment; 460 | return fn(to_handle()); 461 | } 462 | 463 | private: 464 | static inline const UEVR_UStructFunctions* s_functions{nullptr}; 465 | inline static const UEVR_UStructFunctions* initialize() { 466 | if (s_functions == nullptr) { 467 | s_functions = API::get()->sdk()->ustruct; 468 | } 469 | 470 | return s_functions; 471 | } 472 | }; 473 | 474 | struct UClass : public UStruct { 475 | inline UEVR_UClassHandle to_handle() { return (UEVR_UClassHandle)this; } 476 | inline UEVR_UClassHandle to_handle() const { return (UEVR_UClassHandle)this; } 477 | 478 | static UClass* static_class() { 479 | static auto result = API::get()->find_uobject(L"Class /Script/CoreUObject.Class"); 480 | return result; 481 | } 482 | 483 | UObject* get_class_default_object() const { 484 | static const auto fn = initialize()->get_class_default_object; 485 | return (UObject*)fn(to_handle()); 486 | } 487 | 488 | std::vector get_objects_matching(bool allow_default = false) const { 489 | static auto activate_fn = API::get()->sdk()->uobject_hook->activate; 490 | static auto fn = API::get()->sdk()->uobject_hook->get_objects_by_class; 491 | activate_fn(); 492 | std::vector result{}; 493 | const auto size = fn(to_handle(), nullptr, 0, allow_default); 494 | if (size == 0) { 495 | return result; 496 | } 497 | 498 | result.resize(size); 499 | 500 | fn(to_handle(), (UEVR_UObjectHandle*)result.data(), size, allow_default); 501 | return result; 502 | } 503 | 504 | UObject* get_first_object_matching(bool allow_default = false) const { 505 | static auto activate_fn = API::get()->sdk()->uobject_hook->activate; 506 | static auto fn = API::get()->sdk()->uobject_hook->get_first_object_by_class; 507 | activate_fn(); 508 | return (UObject*)fn(to_handle(), allow_default); 509 | } 510 | 511 | template 512 | std::vector get_objects_matching(bool allow_default = false) const { 513 | std::vector objects = get_objects_matching(allow_default); 514 | 515 | return *reinterpret_cast*>(&objects); 516 | } 517 | 518 | template 519 | T* get_first_object_matching(bool allow_default = false) const { 520 | return (T*)get_first_object_matching(allow_default); 521 | } 522 | 523 | private: 524 | static inline const UEVR_UClassFunctions* s_functions{nullptr}; 525 | inline static const UEVR_UClassFunctions* initialize() { 526 | if (s_functions == nullptr) { 527 | s_functions = API::get()->sdk()->uclass; 528 | } 529 | 530 | return s_functions; 531 | } 532 | }; 533 | 534 | struct UFunction : public UStruct { 535 | inline UEVR_UFunctionHandle to_handle() { return (UEVR_UFunctionHandle)this; } 536 | inline UEVR_UFunctionHandle to_handle() const { return (UEVR_UFunctionHandle)this; } 537 | 538 | static UClass* static_class() { 539 | static auto result = API::get()->find_uobject(L"Class /Script/CoreUObject.Function"); 540 | return result; 541 | } 542 | 543 | void call(UObject* obj, void* params) { 544 | if (obj == nullptr) { 545 | return; 546 | } 547 | 548 | obj->process_event(this, params); 549 | } 550 | 551 | void* get_native_function() const { 552 | static const auto fn = initialize()->get_native_function; 553 | return fn(to_handle()); 554 | } 555 | 556 | uint32_t get_function_flags() const { 557 | static const auto fn = initialize()->get_function_flags; 558 | return fn(to_handle()); 559 | } 560 | 561 | void set_function_flags(uint32_t flags) { 562 | static const auto fn = initialize()->set_function_flags; 563 | fn(to_handle(), flags); 564 | } 565 | 566 | using UEVR_UFunction_CPPPreNative = bool(*)(API::UFunction*, API::UObject*, void*, void*); 567 | using UEVR_UFunction_CPPPostNative = void(*)(API::UFunction*, API::UObject*, void*, void*); 568 | 569 | bool hook_ptr(UEVR_UFunction_CPPPreNative pre, UEVR_UFunction_CPPPostNative post) { 570 | static const auto fn = initialize()->hook_ptr; 571 | return fn(to_handle(), (UEVR_UFunction_NativePreFn)pre, (UEVR_UFunction_NativePostFn)post); 572 | } 573 | 574 | private: 575 | static inline const UEVR_UFunctionFunctions* s_functions{nullptr}; 576 | inline static const UEVR_UFunctionFunctions* initialize() { 577 | if (s_functions == nullptr) { 578 | s_functions = API::get()->sdk()->ufunction; 579 | } 580 | 581 | return s_functions; 582 | } 583 | }; 584 | 585 | struct UScriptStruct : public UStruct { 586 | inline UEVR_UScriptStructHandle to_handle() { return (UEVR_UScriptStructHandle)this; } 587 | inline UEVR_UScriptStructHandle to_handle() const { return (UEVR_UScriptStructHandle)this; } 588 | 589 | static UClass* static_class() { 590 | static auto result = API::get()->find_uobject(L"Class /Script/CoreUObject.ScriptStruct"); 591 | return result; 592 | } 593 | 594 | struct StructOps { 595 | virtual ~StructOps() {}; 596 | 597 | int32_t size; 598 | int32_t alignment; 599 | }; 600 | 601 | StructOps* get_struct_ops() const { 602 | static const auto fn = initialize()->get_struct_ops; 603 | return (StructOps*)fn(to_handle()); 604 | } 605 | 606 | int32_t get_struct_size() const { 607 | static const auto fn = initialize()->get_struct_size; 608 | return fn(to_handle()); 609 | } 610 | 611 | private: 612 | static inline const UEVR_UScriptStructFunctions* s_functions{nullptr}; 613 | inline static const UEVR_UScriptStructFunctions* initialize() { 614 | if (s_functions == nullptr) { 615 | s_functions = API::get()->sdk()->uscriptstruct; 616 | } 617 | 618 | return s_functions; 619 | } 620 | }; 621 | 622 | // Wrapper class for UField AND FField 623 | struct FField { 624 | inline UEVR_FFieldHandle to_handle() { return (UEVR_FFieldHandle)this; } 625 | inline UEVR_FFieldHandle to_handle() const { return (UEVR_FFieldHandle)this; } 626 | 627 | inline FField* get_next() const { 628 | static const auto fn = initialize()->get_next; 629 | return (FField*)fn(to_handle()); 630 | } 631 | 632 | FName* get_fname() const { 633 | static const auto fn = initialize()->get_fname; 634 | return (FName*)fn(to_handle()); 635 | } 636 | 637 | FFieldClass* get_class() const { 638 | static const auto fn = initialize()->get_class; 639 | return (FFieldClass*)fn(to_handle()); 640 | } 641 | 642 | private: 643 | static inline const UEVR_FFieldFunctions* s_functions{nullptr}; 644 | static inline const UEVR_FFieldFunctions* initialize() { 645 | if (s_functions == nullptr) { 646 | s_functions = API::get()->sdk()->ffield; 647 | } 648 | 649 | return s_functions; 650 | } 651 | }; 652 | 653 | // Wrapper class for FProperty AND UProperty 654 | struct FProperty : public FField { 655 | inline UEVR_FPropertyHandle to_handle() { return (UEVR_FPropertyHandle)this; } 656 | inline UEVR_FPropertyHandle to_handle() const { return (UEVR_FPropertyHandle)this; } 657 | 658 | int32_t get_offset() const { 659 | static const auto fn = initialize()->get_offset; 660 | return fn(to_handle()); 661 | } 662 | 663 | uint64_t get_property_flags() const { 664 | static const auto fn = initialize()->get_property_flags; 665 | return fn(to_handle()); 666 | } 667 | 668 | bool is_param() const { 669 | static const auto fn = initialize()->is_param; 670 | return fn(to_handle()); 671 | } 672 | 673 | bool is_out_param() const { 674 | static const auto fn = initialize()->is_out_param; 675 | return fn(to_handle()); 676 | } 677 | 678 | bool is_return_param() const { 679 | static const auto fn = initialize()->is_return_param; 680 | return fn(to_handle()); 681 | } 682 | 683 | bool is_reference_param() const { 684 | static const auto fn = initialize()->is_reference_param; 685 | return fn(to_handle()); 686 | } 687 | 688 | bool is_pod() const { 689 | static const auto fn = initialize()->is_pod; 690 | return fn(to_handle()); 691 | } 692 | 693 | private: 694 | static inline const UEVR_FPropertyFunctions* s_functions{nullptr}; 695 | static inline const UEVR_FPropertyFunctions* initialize() { 696 | if (s_functions == nullptr) { 697 | s_functions = API::get()->sdk()->fproperty; 698 | } 699 | 700 | return s_functions; 701 | } 702 | }; 703 | 704 | struct FArrayProperty : public FProperty { 705 | inline UEVR_FArrayPropertyHandle to_handle() { return (UEVR_FArrayPropertyHandle)this; } 706 | inline UEVR_FArrayPropertyHandle to_handle() const { return (UEVR_FArrayPropertyHandle)this; } 707 | 708 | FProperty* get_inner() const { 709 | static const auto fn = initialize()->get_inner; 710 | return (FProperty*)fn(to_handle()); 711 | } 712 | 713 | private: 714 | static inline const UEVR_FArrayPropertyFunctions* s_functions{nullptr}; 715 | static inline const UEVR_FArrayPropertyFunctions* initialize() { 716 | if (s_functions == nullptr) { 717 | s_functions = API::get()->sdk()->farrayproperty; 718 | } 719 | 720 | return s_functions; 721 | } 722 | }; 723 | 724 | struct FBoolProperty : public FProperty { 725 | inline UEVR_FBoolPropertyHandle to_handle() { return (UEVR_FBoolPropertyHandle)this; } 726 | inline UEVR_FBoolPropertyHandle to_handle() const { return (UEVR_FBoolPropertyHandle)this; } 727 | 728 | uint32_t get_field_size() const { 729 | static const auto fn = initialize()->get_field_size; 730 | return fn(to_handle()); 731 | } 732 | 733 | uint32_t get_byte_offset() const { 734 | static const auto fn = initialize()->get_byte_offset; 735 | return fn(to_handle()); 736 | } 737 | 738 | uint32_t get_byte_mask() const { 739 | static const auto fn = initialize()->get_byte_mask; 740 | return fn(to_handle()); 741 | } 742 | 743 | uint32_t get_field_mask() const { 744 | static const auto fn = initialize()->get_field_mask; 745 | return fn(to_handle()); 746 | } 747 | 748 | bool get_value_from_object(void* object) const { 749 | static const auto fn = initialize()->get_value_from_object; 750 | return fn(to_handle(), object); 751 | } 752 | 753 | bool get_value_from_propbase(void* addr) const { 754 | static const auto fn = initialize()->get_value_from_propbase; 755 | return fn(to_handle(), addr); 756 | } 757 | 758 | void set_value_in_object(void* object, bool value) const { 759 | static const auto fn = initialize()->set_value_in_object; 760 | fn(to_handle(), object, value); 761 | } 762 | 763 | void set_value_in_propbase(void* addr, bool value) const { 764 | static const auto fn = initialize()->set_value_in_propbase; 765 | fn(to_handle(), addr, value); 766 | } 767 | 768 | private: 769 | static inline const UEVR_FBoolPropertyFunctions* s_functions{nullptr}; 770 | static inline const UEVR_FBoolPropertyFunctions* initialize() { 771 | if (s_functions == nullptr) { 772 | s_functions = API::get()->sdk()->fboolproperty; 773 | } 774 | 775 | return s_functions; 776 | } 777 | }; 778 | 779 | struct FStructProperty : public FProperty { 780 | inline UEVR_FStructPropertyHandle to_handle() { return (UEVR_FStructPropertyHandle)this; } 781 | inline UEVR_FStructPropertyHandle to_handle() const { return (UEVR_FStructPropertyHandle)this; } 782 | 783 | UScriptStruct* get_struct() const { 784 | static const auto fn = initialize()->get_struct; 785 | return (UScriptStruct*)fn(to_handle()); 786 | } 787 | 788 | private: 789 | static inline const UEVR_FStructPropertyFunctions* s_functions{nullptr}; 790 | static inline const UEVR_FStructPropertyFunctions* initialize() { 791 | if (s_functions == nullptr) { 792 | s_functions = API::get()->sdk()->fstructproperty; 793 | } 794 | 795 | return s_functions; 796 | } 797 | }; 798 | 799 | struct UEnum : public UObject { 800 | 801 | }; 802 | 803 | struct FNumericProperty : public FProperty { 804 | 805 | }; 806 | 807 | struct FEnumProperty : public FProperty { 808 | inline UEVR_FEnumPropertyHandle to_handle() { return (UEVR_FEnumPropertyHandle)this; } 809 | inline UEVR_FEnumPropertyHandle to_handle() const { return (UEVR_FEnumPropertyHandle)this; } 810 | 811 | FNumericProperty* get_underlying_prop() const { 812 | static const auto fn = initialize()->get_underlying_prop; 813 | return (FNumericProperty*)fn(to_handle()); 814 | } 815 | 816 | UEnum* get_enum() const { 817 | static const auto fn = initialize()->get_enum; 818 | return (UEnum*)fn(to_handle()); 819 | } 820 | 821 | private: 822 | static inline const UEVR_FEnumPropertyFunctions* s_functions{nullptr}; 823 | static inline const UEVR_FEnumPropertyFunctions* initialize() { 824 | if (s_functions == nullptr) { 825 | s_functions = API::get()->sdk()->fenumproperty; 826 | } 827 | 828 | return s_functions; 829 | } 830 | }; 831 | 832 | struct FFieldClass { 833 | inline UEVR_FFieldClassHandle to_handle() { return (UEVR_FFieldClassHandle)this; } 834 | inline UEVR_FFieldClassHandle to_handle() const { return (UEVR_FFieldClassHandle)this; } 835 | 836 | FName* get_fname() const { 837 | static const auto fn = initialize()->get_fname; 838 | return (FName*)fn(to_handle()); 839 | } 840 | 841 | std::wstring get_name() const { 842 | return get_fname()->to_string(); 843 | } 844 | 845 | private: 846 | static inline const UEVR_FFieldClassFunctions* s_functions{nullptr}; 847 | static inline const UEVR_FFieldClassFunctions* initialize() { 848 | if (s_functions == nullptr) { 849 | s_functions = API::get()->sdk()->ffield_class; 850 | } 851 | 852 | return s_functions; 853 | } 854 | }; 855 | 856 | struct ConsoleObjectElement { 857 | wchar_t* key; 858 | int32_t unk[2]; 859 | IConsoleObject* value; 860 | int32_t unk2[2]; 861 | }; 862 | 863 | struct FConsoleManager { 864 | inline UEVR_FConsoleManagerHandle to_handle() { return (UEVR_FConsoleManagerHandle)this; } 865 | inline UEVR_FConsoleManagerHandle to_handle() const { return (UEVR_FConsoleManagerHandle)this; } 866 | 867 | TArray& get_console_objects() { 868 | static const auto fn = initialize()->get_console_objects; 869 | return *(TArray*)fn(to_handle()); 870 | } 871 | 872 | IConsoleObject* find_object(std::wstring_view name) { 873 | static const auto fn = initialize()->find_object; 874 | return (IConsoleObject*)fn(to_handle(), name.data()); 875 | } 876 | 877 | IConsoleVariable* find_variable(std::wstring_view name) { 878 | static const auto fn = initialize()->find_variable; 879 | return (IConsoleVariable*)fn(to_handle(), name.data()); 880 | } 881 | 882 | IConsoleCommand* find_command(std::wstring_view name) { 883 | static const auto fn = initialize()->find_command; 884 | return (IConsoleCommand*)fn(to_handle(), name.data()); 885 | } 886 | 887 | private: 888 | static inline const UEVR_ConsoleFunctions* s_functions{nullptr}; 889 | static inline const UEVR_ConsoleFunctions* initialize() { 890 | if (s_functions == nullptr) { 891 | s_functions = API::get()->sdk()->console; 892 | } 893 | 894 | return s_functions; 895 | } 896 | }; 897 | 898 | struct IConsoleObject { 899 | inline UEVR_IConsoleObjectHandle to_handle() { return (UEVR_IConsoleObjectHandle)this; } 900 | inline UEVR_IConsoleObjectHandle to_handle() const { return (UEVR_IConsoleObjectHandle)this; } 901 | 902 | IConsoleCommand* as_command() { 903 | static const auto fn = initialize()->as_command; 904 | return (IConsoleCommand*)fn(to_handle()); 905 | } 906 | 907 | private: 908 | static inline const UEVR_ConsoleFunctions* s_functions{nullptr}; 909 | static inline const UEVR_ConsoleFunctions* initialize() { 910 | if (s_functions == nullptr) { 911 | s_functions = API::get()->sdk()->console; 912 | } 913 | 914 | return s_functions; 915 | } 916 | }; 917 | 918 | struct IConsoleVariable : public IConsoleObject { 919 | inline UEVR_IConsoleVariableHandle to_handle() { return (UEVR_IConsoleVariableHandle)this; } 920 | inline UEVR_IConsoleVariableHandle to_handle() const { return (UEVR_IConsoleVariableHandle)this; } 921 | 922 | void set(std::wstring_view value) { 923 | static const auto fn = initialize()->variable_set; 924 | fn(to_handle(), value.data()); 925 | } 926 | 927 | void set_ex(std::wstring_view value, uint32_t flags = 0x80000000) { 928 | static const auto fn = initialize()->variable_set_ex; 929 | fn(to_handle(), value.data(), flags); 930 | } 931 | 932 | void set(float value) { 933 | set(std::to_wstring(value)); 934 | } 935 | 936 | void set(int value) { 937 | set(std::to_wstring(value)); 938 | } 939 | 940 | int get_int() const { 941 | static const auto fn = initialize()->variable_get_int; 942 | return fn(to_handle()); 943 | } 944 | 945 | float get_float() const { 946 | static const auto fn = initialize()->variable_get_float; 947 | return fn(to_handle()); 948 | } 949 | 950 | private: 951 | static inline const UEVR_ConsoleFunctions* s_functions{nullptr}; 952 | static inline const UEVR_ConsoleFunctions* initialize() { 953 | if (s_functions == nullptr) { 954 | s_functions = API::get()->sdk()->console; 955 | } 956 | 957 | return s_functions; 958 | } 959 | }; 960 | 961 | struct IConsoleCommand : public IConsoleObject { 962 | inline UEVR_IConsoleCommandHandle to_handle() { return (UEVR_IConsoleCommandHandle)this; } 963 | inline UEVR_IConsoleCommandHandle to_handle() const { return (UEVR_IConsoleCommandHandle)this; } 964 | 965 | void execute(std::wstring_view args) { 966 | static const auto fn = initialize()->command_execute; 967 | fn(to_handle(), args.data()); 968 | } 969 | 970 | private: 971 | static inline const UEVR_ConsoleFunctions* s_functions{nullptr}; 972 | static inline const UEVR_ConsoleFunctions* initialize() { 973 | if (s_functions == nullptr) { 974 | s_functions = API::get()->sdk()->console; 975 | } 976 | 977 | return s_functions; 978 | } 979 | }; 980 | 981 | // TODO 982 | struct UEngine : public UObject { 983 | static UEngine* get() { 984 | return API::get()->get_engine(); 985 | } 986 | }; 987 | 988 | struct UGameEngine : public UEngine { 989 | 990 | }; 991 | 992 | struct UWorld : public UObject { 993 | 994 | }; 995 | 996 | struct FUObjectArray { 997 | inline UEVR_UObjectArrayHandle to_handle() { return (UEVR_UObjectArrayHandle)this; } 998 | inline UEVR_UObjectArrayHandle to_handle() const { return (UEVR_UObjectArrayHandle)this; } 999 | 1000 | static FUObjectArray* get() { 1001 | return API::get()->get_uobject_array(); 1002 | } 1003 | 1004 | static bool is_chunked() { 1005 | static const auto fn = initialize()->is_chunked; 1006 | return fn(); 1007 | } 1008 | 1009 | static bool is_inlined() { 1010 | static const auto fn = initialize()->is_inlined; 1011 | return fn(); 1012 | } 1013 | 1014 | static size_t get_objects_offset() { 1015 | static const auto fn = initialize()->get_objects_offset; 1016 | return (size_t)fn(); 1017 | } 1018 | 1019 | static size_t get_item_distance() { 1020 | static const auto fn = initialize()->get_item_distance; 1021 | return (size_t)fn(); 1022 | } 1023 | 1024 | int32_t get_object_count() const { 1025 | static const auto fn = initialize()->get_object_count; 1026 | return fn(to_handle()); 1027 | } 1028 | 1029 | void* get_objects_ptr() const { 1030 | static const auto fn = initialize()->get_objects_ptr; 1031 | return fn(to_handle()); 1032 | } 1033 | 1034 | UObject* get_object(int32_t index) const { 1035 | static const auto fn = initialize()->get_object; 1036 | return (UObject*)fn(to_handle(), index); 1037 | } 1038 | 1039 | // Generally the same structure most of the time, not too much to worry about 1040 | // Not that this would generally be used raw - instead prefer get_object 1041 | struct FUObjectItem { 1042 | API::UObject* object; 1043 | int32_t flags; 1044 | int32_t cluster_index; 1045 | int32_t serial_number; 1046 | }; 1047 | 1048 | FUObjectItem* get_item(int32_t index) const { 1049 | static const auto fn = initialize()->get_item; 1050 | return (FUObjectItem*)fn(to_handle(), index); 1051 | } 1052 | 1053 | private: 1054 | static inline const UEVR_UObjectArrayFunctions* s_functions{nullptr}; 1055 | static inline const UEVR_UObjectArrayFunctions* initialize() { 1056 | if (s_functions == nullptr) { 1057 | s_functions = API::get()->sdk()->uobject_array; 1058 | } 1059 | 1060 | return s_functions; 1061 | } 1062 | }; 1063 | 1064 | // One of the very few non-opaque structs 1065 | // because these have never changed, if they do its because of bespoke code 1066 | template 1067 | struct TArray { 1068 | T* data; 1069 | int32_t count; 1070 | int32_t capacity; 1071 | 1072 | ~TArray() { 1073 | if (data != nullptr) { 1074 | FMalloc::get()->free(data); 1075 | data = nullptr; 1076 | } 1077 | } 1078 | 1079 | T* begin() { 1080 | return data; 1081 | } 1082 | 1083 | T* end() { 1084 | if (data == nullptr) { 1085 | return nullptr; 1086 | } 1087 | 1088 | return data + count; 1089 | } 1090 | 1091 | T* begin() const { 1092 | return data; 1093 | } 1094 | 1095 | T* end() const { 1096 | if (data == nullptr) { 1097 | return nullptr; 1098 | } 1099 | 1100 | return data + count; 1101 | } 1102 | 1103 | bool empty() const { 1104 | return count == 0 || data == nullptr; 1105 | } 1106 | }; 1107 | 1108 | struct FRHITexture2D { 1109 | inline UEVR_FRHITexture2DHandle to_handle() { return (UEVR_FRHITexture2DHandle)this; } 1110 | inline UEVR_FRHITexture2DHandle to_handle() const { return (UEVR_FRHITexture2DHandle)this; } 1111 | 1112 | void* get_native_resource() const { 1113 | static const auto fn = initialize()->get_native_resource; 1114 | return fn(to_handle()); 1115 | } 1116 | 1117 | private: 1118 | static inline const UEVR_FRHITexture2DFunctions* s_functions{nullptr}; 1119 | static inline const UEVR_FRHITexture2DFunctions* initialize() { 1120 | if (s_functions == nullptr) { 1121 | s_functions = API::get()->sdk()->frhitexture2d; 1122 | } 1123 | 1124 | return s_functions; 1125 | } 1126 | }; 1127 | 1128 | public: 1129 | // UEVR specific stuff 1130 | struct VR { 1131 | static bool is_runtime_ready() { 1132 | static const auto fn = initialize()->is_runtime_ready; 1133 | return fn(); 1134 | } 1135 | 1136 | static bool is_openvr() { 1137 | static const auto fn = initialize()->is_openvr; 1138 | return fn(); 1139 | } 1140 | 1141 | static bool is_openxr() { 1142 | static const auto fn = initialize()->is_openxr; 1143 | return fn(); 1144 | } 1145 | 1146 | static bool is_hmd_active() { 1147 | static const auto fn = initialize()->is_hmd_active; 1148 | return fn(); 1149 | } 1150 | 1151 | static UEVR_Vector3f get_standing_origin() { 1152 | static const auto fn = initialize()->get_standing_origin; 1153 | UEVR_Vector3f result{}; 1154 | 1155 | fn(&result); 1156 | return result; 1157 | } 1158 | 1159 | static UEVR_Quaternionf get_rotation_offset() { 1160 | static const auto fn = initialize()->get_rotation_offset; 1161 | UEVR_Quaternionf result{}; 1162 | 1163 | fn(&result); 1164 | return result; 1165 | } 1166 | 1167 | static void set_standing_origin(const UEVR_Vector3f& origin) { 1168 | static const auto fn = initialize()->set_standing_origin; 1169 | fn(&origin); 1170 | } 1171 | 1172 | static void set_rotation_offset(const UEVR_Quaternionf& offset) { 1173 | static const auto fn = initialize()->set_rotation_offset; 1174 | fn(&offset); 1175 | } 1176 | 1177 | static void set_rotation_offset(const UEVR_Quaternionf* offset) { 1178 | static const auto fn = initialize()->set_rotation_offset; 1179 | fn(offset); 1180 | } 1181 | 1182 | static UEVR_TrackedDeviceIndex get_hmd_index() { 1183 | static const auto fn = initialize()->get_hmd_index; 1184 | return fn(); 1185 | } 1186 | 1187 | static UEVR_TrackedDeviceIndex get_left_controller_index() { 1188 | static const auto fn = initialize()->get_left_controller_index; 1189 | return fn(); 1190 | } 1191 | 1192 | static UEVR_TrackedDeviceIndex get_right_controller_index() { 1193 | static const auto fn = initialize()->get_right_controller_index; 1194 | return fn(); 1195 | } 1196 | 1197 | struct Pose { 1198 | UEVR_Vector3f position; 1199 | UEVR_Quaternionf rotation; 1200 | }; 1201 | 1202 | static Pose get_pose(UEVR_TrackedDeviceIndex index) { 1203 | static const auto fn = initialize()->get_pose; 1204 | Pose result{}; 1205 | 1206 | fn(index, &result.position, &result.rotation); 1207 | return result; 1208 | } 1209 | 1210 | UEVR_Matrix4x4f get_transform(UEVR_TrackedDeviceIndex index) { 1211 | static const auto fn = initialize()->get_transform; 1212 | UEVR_Matrix4x4f result{}; 1213 | 1214 | fn(index, &result); 1215 | return result; 1216 | } 1217 | 1218 | static Pose get_grip_pose(UEVR_TrackedDeviceIndex index) { 1219 | static const auto fn = initialize()->get_grip_pose; 1220 | Pose result{}; 1221 | 1222 | fn(index, &result.position, &result.rotation); 1223 | return result; 1224 | } 1225 | 1226 | static Pose get_aim_pose(UEVR_TrackedDeviceIndex index) { 1227 | static const auto fn = initialize()->get_aim_pose; 1228 | Pose result{}; 1229 | 1230 | fn(index, &result.position, &result.rotation); 1231 | return result; 1232 | } 1233 | 1234 | static UEVR_Matrix4x4f get_grip_transform(UEVR_TrackedDeviceIndex index) { 1235 | static const auto fn = initialize()->get_grip_transform; 1236 | UEVR_Matrix4x4f result{}; 1237 | 1238 | fn(index, &result); 1239 | return result; 1240 | } 1241 | 1242 | static UEVR_Matrix4x4f get_aim_transform(UEVR_TrackedDeviceIndex index) { 1243 | static const auto fn = initialize()->get_aim_transform; 1244 | UEVR_Matrix4x4f result{}; 1245 | 1246 | fn(index, &result); 1247 | return result; 1248 | } 1249 | 1250 | enum class Eye : int32_t { 1251 | LEFT, 1252 | RIGHT 1253 | }; 1254 | 1255 | static UEVR_Vector3f get_eye_offset(Eye eye) { 1256 | static const auto fn = initialize()->get_eye_offset; 1257 | UEVR_Vector3f result{}; 1258 | 1259 | fn((int32_t)eye, &result); 1260 | return result; 1261 | } 1262 | 1263 | static UEVR_Matrix4x4f get_ue_projection_matrix(Eye eye) { 1264 | static const auto fn = initialize()->get_ue_projection_matrix; 1265 | UEVR_Matrix4x4f result{}; 1266 | 1267 | fn((int32_t)eye, &result); 1268 | return result; 1269 | } 1270 | 1271 | static UEVR_InputSourceHandle get_left_joystick_source() { 1272 | static const auto fn = initialize()->get_left_joystick_source; 1273 | return fn(); 1274 | } 1275 | 1276 | static UEVR_InputSourceHandle get_right_joystick_source() { 1277 | static const auto fn = initialize()->get_right_joystick_source; 1278 | return fn(); 1279 | } 1280 | 1281 | static UEVR_ActionHandle get_action_handle(std::string_view name) { 1282 | static const auto fn = initialize()->get_action_handle; 1283 | return fn(name.data()); 1284 | } 1285 | 1286 | static bool is_action_active(UEVR_ActionHandle handle, UEVR_InputSourceHandle source) { 1287 | static const auto fn = initialize()->is_action_active; 1288 | return fn(handle, source); 1289 | } 1290 | 1291 | static bool is_action_active_any_joystick(UEVR_ActionHandle handle) { 1292 | static const auto fn = initialize()->is_action_active_any_joystick; 1293 | return fn(handle); 1294 | } 1295 | 1296 | static UEVR_Vector2f get_joystick_axis(UEVR_InputSourceHandle source) { 1297 | static const auto fn = initialize()->get_joystick_axis; 1298 | UEVR_Vector2f result{}; 1299 | 1300 | fn(source, &result); 1301 | return result; 1302 | } 1303 | 1304 | static void trigger_haptic_vibration(UEVR_TrackedDeviceIndex index, float amplitude, float frequency, float duration, UEVR_InputSourceHandle source) { 1305 | static const auto fn = initialize()->trigger_haptic_vibration; 1306 | fn(index, amplitude, frequency, duration, source); 1307 | } 1308 | 1309 | static bool is_using_contriollers() { 1310 | static const auto fn = initialize()->is_using_controllers; 1311 | return fn(); 1312 | } 1313 | 1314 | static bool is_decoupled_pitch_enabled() { 1315 | static const auto fn = initialize()->is_decoupled_pitch_enabled; 1316 | return fn(); 1317 | } 1318 | 1319 | enum class AimMethod : int32_t { 1320 | GAME, 1321 | HEAD, 1322 | RIGHT_CONTROLLER, 1323 | LEFT_CONTROLLER, 1324 | TWO_HANDED_RIGHT, 1325 | TWO_HANDED_LEFT, 1326 | }; 1327 | 1328 | static AimMethod get_movement_orientation() { 1329 | static const auto fn = initialize()->get_movement_orientation; 1330 | return (AimMethod)fn(); 1331 | } 1332 | 1333 | static uint32_t get_lowest_xinput_index() { 1334 | static const auto fn = initialize()->get_lowest_xinput_index; 1335 | return fn(); 1336 | } 1337 | 1338 | static void recenter_view() { 1339 | static const auto fn = initialize()->recenter_view; 1340 | fn(); 1341 | } 1342 | 1343 | static void recenter_horizon() { 1344 | static const auto fn = initialize()->recenter_horizon; 1345 | fn(); 1346 | } 1347 | 1348 | static AimMethod get_aim_method() { 1349 | static const auto fn = initialize()->get_aim_method; 1350 | return (AimMethod)fn(); 1351 | } 1352 | 1353 | static void set_aim_method(AimMethod method) { 1354 | static const auto fn = initialize()->set_aim_method; 1355 | fn((uint32_t)method); 1356 | } 1357 | 1358 | static bool is_aim_allowed() { 1359 | static const auto fn = initialize()->is_aim_allowed; 1360 | return fn(); 1361 | } 1362 | 1363 | static void set_aim_allowed(bool allowed) { 1364 | static const auto fn = initialize()->set_aim_allowed; 1365 | fn(allowed); 1366 | } 1367 | 1368 | static uint32_t get_hmd_width() { 1369 | static const auto fn = initialize()->get_hmd_width; 1370 | return fn(); 1371 | } 1372 | 1373 | static uint32_t get_hmd_height() { 1374 | static const auto fn = initialize()->get_hmd_height; 1375 | return fn(); 1376 | } 1377 | 1378 | static uint32_t get_ui_width() { 1379 | static const auto fn = initialize()->get_ui_width; 1380 | return fn(); 1381 | } 1382 | 1383 | static uint32_t get_ui_height() { 1384 | static const auto fn = initialize()->get_ui_height; 1385 | return fn(); 1386 | } 1387 | 1388 | static bool is_snap_turn_enabled() { 1389 | static const auto fn = initialize()->is_snap_turn_enabled; 1390 | return fn(); 1391 | } 1392 | 1393 | static void set_snap_turn_enabled(bool enabled) { 1394 | static const auto fn = initialize()->set_snap_turn_enabled; 1395 | fn(enabled); 1396 | } 1397 | 1398 | static void set_decoupled_pitch_enabled(bool enabled) { 1399 | static const auto fn = initialize()->set_decoupled_pitch_enabled; 1400 | fn(enabled); 1401 | } 1402 | 1403 | template 1404 | static void set_mod_value(std::string_view key, const T& value) { 1405 | static const auto fn = initialize()->set_mod_value; 1406 | 1407 | if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { 1408 | fn(key.data(), value); 1409 | return; 1410 | } else if constexpr (std::is_same_v || std::is_same_v) { 1411 | fn(key.data(), value.data()); 1412 | return; 1413 | } else if constexpr (std::is_same_v) { 1414 | if (value) { 1415 | fn(key.data(), "true"); 1416 | } else { 1417 | fn(key.data(), "false"); 1418 | } 1419 | 1420 | return; 1421 | } 1422 | 1423 | 1424 | fn(key.data(), std::to_string(value).c_str()); 1425 | } 1426 | 1427 | template 1428 | static T get_mod_value(std::string_view key) { 1429 | static const auto fn = initialize()->get_mod_value; 1430 | char result[256]{}; 1431 | fn(key.data(), result, sizeof(result)); 1432 | 1433 | static_assert(!std::is_same_v, "Cannot get a void value"); 1434 | static_assert(!std::is_same_v, "Cannot get a const char* value"); 1435 | static_assert(!std::is_same_v, "Cannot get a const char* value"); 1436 | static_assert(!std::is_same_v, "Cannot get a char* value"); 1437 | 1438 | if constexpr (std::is_same_v) { 1439 | return std::string{result}; 1440 | } else if constexpr (std::is_same_v) { 1441 | return std::string_view{result} == "true"; 1442 | } else if constexpr (std::is_integral_v) { 1443 | if constexpr (std::is_unsigned_v) { 1444 | return (T)std::stoul(result); 1445 | } 1446 | 1447 | return (T)std::stoi(result); 1448 | } else if constexpr (std::is_floating_point_v) { 1449 | return (T)std::stod(result); 1450 | } 1451 | 1452 | return T{}; 1453 | } 1454 | 1455 | static void save_config() { 1456 | static const auto fn = initialize()->save_config; 1457 | fn(); 1458 | } 1459 | 1460 | static void reload_config() { 1461 | static const auto fn = initialize()->reload_config; 1462 | fn(); 1463 | } 1464 | 1465 | private: 1466 | static inline const UEVR_VRData* s_functions{nullptr}; 1467 | static inline const UEVR_VRData* initialize() { 1468 | if (s_functions == nullptr) { 1469 | s_functions = API::get()->param()->vr; 1470 | } 1471 | 1472 | return s_functions; 1473 | } 1474 | }; 1475 | 1476 | struct UObjectHook { 1477 | struct MotionControllerState; 1478 | 1479 | static void activate() { 1480 | static const auto fn = initialize()->activate; 1481 | fn(); 1482 | } 1483 | 1484 | static bool exists(UObject* obj) { 1485 | static const auto fn = initialize()->exists; 1486 | return fn(obj->to_handle()); 1487 | } 1488 | 1489 | static bool is_disabled() { 1490 | static const auto fn = initialize()->is_disabled; 1491 | return fn(); 1492 | } 1493 | 1494 | static void set_disabled(bool disabled) { 1495 | static const auto fn = initialize()->set_disabled; 1496 | fn(disabled); 1497 | } 1498 | 1499 | static std::vector get_objects_by_class(UClass* c, bool allow_default = false) { 1500 | if (c == nullptr) { 1501 | return {}; 1502 | } 1503 | 1504 | return c->get_objects_matching(allow_default); 1505 | } 1506 | 1507 | static UObject* get_first_object_by_class(UClass* c, bool allow_default = false) { 1508 | if (c == nullptr) { 1509 | return nullptr; 1510 | } 1511 | 1512 | return c->get_first_object_matching(allow_default); 1513 | } 1514 | 1515 | // Must be a USceneComponent 1516 | // Also, do NOT keep the pointer around, it will be invalidated at any time 1517 | // Call it every time you need it 1518 | static MotionControllerState* get_or_add_motion_controller_state(UObject* obj) { 1519 | static const auto fn = initialize()->get_or_add_motion_controller_state; 1520 | return (MotionControllerState*)fn(obj->to_handle()); 1521 | } 1522 | 1523 | static MotionControllerState* get_motion_controller_state(UObject* obj) { 1524 | static const auto fn = initialize()->get_motion_controller_state; 1525 | return (MotionControllerState*)fn(obj->to_handle()); 1526 | } 1527 | 1528 | static void remove_motion_controller_state(UObject* obj) { 1529 | static const auto fn = initialize()->remove_motion_controller_state; 1530 | fn(obj->to_handle()); 1531 | } 1532 | 1533 | struct MotionControllerState { 1534 | inline UEVR_UObjectHookMotionControllerStateHandle to_handle() { return (UEVR_UObjectHookMotionControllerStateHandle)this; } 1535 | inline UEVR_UObjectHookMotionControllerStateHandle to_handle() const { return (UEVR_UObjectHookMotionControllerStateHandle)this; } 1536 | 1537 | void set_rotation_offset(const UEVR_Quaternionf* offset) { 1538 | static const auto fn = initialize()->set_rotation_offset; 1539 | fn(to_handle(), offset); 1540 | } 1541 | 1542 | void set_location_offset(const UEVR_Vector3f* offset) { 1543 | static const auto fn = initialize()->set_location_offset; 1544 | fn(to_handle(), offset); 1545 | } 1546 | 1547 | void set_hand(uint32_t hand) { 1548 | static const auto fn = initialize()->set_hand; 1549 | fn(to_handle(), hand); 1550 | } 1551 | 1552 | void set_permanent(bool permanent) { 1553 | static const auto fn = initialize()->set_permanent; 1554 | fn(to_handle(), permanent); 1555 | } 1556 | 1557 | private: 1558 | static inline const UEVR_UObjectHookMotionControllerStateFunctions* s_functions{nullptr}; 1559 | static inline const UEVR_UObjectHookMotionControllerStateFunctions* initialize() { 1560 | if (s_functions == nullptr) { 1561 | s_functions = API::get()->sdk()->uobject_hook->mc_state; 1562 | } 1563 | 1564 | return s_functions; 1565 | } 1566 | }; 1567 | 1568 | private: 1569 | static inline const UEVR_UObjectHookFunctions* s_functions{nullptr}; 1570 | static inline const UEVR_UObjectHookFunctions* initialize() { 1571 | if (s_functions == nullptr) { 1572 | s_functions = API::get()->sdk()->uobject_hook; 1573 | } 1574 | 1575 | return s_functions; 1576 | } 1577 | }; 1578 | 1579 | struct RenderTargetPoolHook { 1580 | static void activate() { 1581 | static const auto fn = initialize()->activate; 1582 | fn(); 1583 | } 1584 | 1585 | static IPooledRenderTarget* get_render_target(const wchar_t* name) { 1586 | static const auto fn = initialize()->get_render_target; 1587 | return (IPooledRenderTarget*)fn(name); 1588 | } 1589 | 1590 | private: 1591 | static inline const UEVR_FRenderTargetPoolHookFunctions* s_functions{nullptr}; 1592 | static inline const UEVR_FRenderTargetPoolHookFunctions* initialize() { 1593 | if (s_functions == nullptr) { 1594 | s_functions = API::get()->sdk()->render_target_pool_hook; 1595 | } 1596 | 1597 | return s_functions; 1598 | } 1599 | }; 1600 | 1601 | struct StereoHook { 1602 | static FRHITexture2D* get_scene_render_target() { 1603 | static const auto fn = initialize()->get_scene_render_target; 1604 | return (FRHITexture2D*)fn(); 1605 | } 1606 | 1607 | static FRHITexture2D* get_ui_render_target() { 1608 | static const auto fn = initialize()->get_ui_render_target; 1609 | return (FRHITexture2D*)fn(); 1610 | } 1611 | 1612 | private: 1613 | static inline const UEVR_FFakeStereoRenderingHookFunctions* s_functions{nullptr}; 1614 | static inline const UEVR_FFakeStereoRenderingHookFunctions* initialize() { 1615 | if (s_functions == nullptr) { 1616 | s_functions = API::get()->sdk()->stereo_hook; 1617 | } 1618 | 1619 | return s_functions; 1620 | } 1621 | }; 1622 | 1623 | private: 1624 | const UEVR_PluginInitializeParam* m_param; 1625 | const UEVR_SDKData* m_sdk; 1626 | }; 1627 | } -------------------------------------------------------------------------------- /src/uevr/Plugin.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file (Plugin.hpp) is licensed under the MIT license and is separate from the rest of the UEVR codebase. 3 | 4 | Copyright (c) 2023 praydog 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | // Helper header to easily instantiate a plugin 26 | // and get some initial callbacks setup 27 | // the user can inherit from the Plugin class 28 | // and set uevr::g_plugin to their plugin instance 29 | #pragma once 30 | 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | 37 | #include "API.hpp" 38 | 39 | namespace uevr { 40 | class Plugin; 41 | 42 | namespace detail { 43 | static inline ::uevr::Plugin* g_plugin{nullptr}; 44 | } 45 | 46 | class Plugin { 47 | public: 48 | Plugin() { detail::g_plugin = this; } 49 | 50 | virtual ~Plugin() = default; 51 | 52 | // Main plugin callbacks 53 | virtual void on_dllmain() {} 54 | virtual void on_initialize() {} 55 | virtual void on_present() {} 56 | virtual void on_post_render_vr_framework_dx11(ID3D11DeviceContext* context, ID3D11Texture2D* texture, ID3D11RenderTargetView* rtv) {} 57 | virtual void on_post_render_vr_framework_dx12(ID3D12GraphicsCommandList* command_list, ID3D12Resource* rt, D3D12_CPU_DESCRIPTOR_HANDLE* rtv) {} 58 | virtual void on_device_reset() {} 59 | virtual bool on_message(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { return true; } 60 | virtual void on_xinput_get_state(uint32_t* retval, uint32_t user_index, XINPUT_STATE* state) {} 61 | virtual void on_xinput_set_state(uint32_t* retval, uint32_t user_index, XINPUT_VIBRATION* vibration) {} 62 | 63 | // Game/Engine callbacks 64 | virtual void on_pre_engine_tick(API::UGameEngine* engine, float delta) {} 65 | virtual void on_post_engine_tick(API::UGameEngine* engine, float delta) {} 66 | virtual void on_pre_slate_draw_window(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info) {} 67 | virtual void on_post_slate_draw_window(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info) {} 68 | virtual void on_pre_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle, int view_index, float world_to_meters, 69 | UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) {}; 70 | virtual void on_post_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle, int view_index, float world_to_meters, 71 | UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) {}; 72 | 73 | virtual void on_pre_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle) {} 74 | virtual void on_post_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle) {} 75 | 76 | protected: 77 | }; 78 | } 79 | 80 | extern "C" __declspec(dllexport) void uevr_plugin_required_version(UEVR_PluginVersion* version) { 81 | version->major = UEVR_PLUGIN_VERSION_MAJOR; 82 | version->minor = UEVR_PLUGIN_VERSION_MINOR; 83 | version->patch = UEVR_PLUGIN_VERSION_PATCH; 84 | } 85 | 86 | extern "C" __declspec(dllexport) bool uevr_plugin_initialize(const UEVR_PluginInitializeParam* param) { 87 | auto& api = uevr::API::initialize(param); 88 | uevr::detail::g_plugin->on_initialize(); 89 | 90 | auto callbacks = param->callbacks; 91 | auto sdk_callbacks = param->sdk->callbacks; 92 | 93 | callbacks->on_device_reset([]() { 94 | uevr::detail::g_plugin->on_device_reset(); 95 | }); 96 | 97 | callbacks->on_present([]() { 98 | uevr::detail::g_plugin->on_present(); 99 | }); 100 | 101 | callbacks->on_post_render_vr_framework_dx11([](void* context, void* texture, void* rtv) { 102 | uevr::detail::g_plugin->on_post_render_vr_framework_dx11((ID3D11DeviceContext*)context, (ID3D11Texture2D*)texture, (ID3D11RenderTargetView*)rtv); 103 | }); 104 | 105 | callbacks->on_post_render_vr_framework_dx12([](void* command_list, void* rt, void* rtv) { 106 | uevr::detail::g_plugin->on_post_render_vr_framework_dx12((ID3D12GraphicsCommandList*)command_list, (ID3D12Resource*)rt, (D3D12_CPU_DESCRIPTOR_HANDLE*)rtv); 107 | }); 108 | 109 | callbacks->on_message([](void* hwnd, unsigned int msg, unsigned long long wparam, long long lparam) { 110 | return uevr::detail::g_plugin->on_message((HWND)hwnd, msg, wparam, lparam); 111 | }); 112 | 113 | callbacks->on_xinput_get_state([](unsigned int* retval, unsigned int user_index, void* state) { 114 | uevr::detail::g_plugin->on_xinput_get_state(retval, user_index, (XINPUT_STATE*)state); 115 | }); 116 | 117 | callbacks->on_xinput_set_state([](unsigned int* retval, unsigned int user_index, void* vibration) { 118 | uevr::detail::g_plugin->on_xinput_set_state(retval, user_index, (XINPUT_VIBRATION*)vibration); 119 | }); 120 | 121 | sdk_callbacks->on_pre_engine_tick([](UEVR_UGameEngineHandle engine, float delta) { 122 | uevr::detail::g_plugin->on_pre_engine_tick((uevr::API::UGameEngine*)engine, delta); 123 | }); 124 | 125 | sdk_callbacks->on_post_engine_tick([](UEVR_UGameEngineHandle engine, float delta) { 126 | uevr::detail::g_plugin->on_post_engine_tick((uevr::API::UGameEngine*)engine, delta); 127 | }); 128 | 129 | sdk_callbacks->on_pre_slate_draw_window_render_thread([](UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info) { 130 | uevr::detail::g_plugin->on_pre_slate_draw_window(renderer, viewport_info); 131 | }); 132 | 133 | sdk_callbacks->on_post_slate_draw_window_render_thread([](UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info) { 134 | uevr::detail::g_plugin->on_post_slate_draw_window(renderer, viewport_info); 135 | }); 136 | 137 | sdk_callbacks->on_pre_calculate_stereo_view_offset([](UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, 138 | UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) { 139 | uevr::detail::g_plugin->on_pre_calculate_stereo_view_offset(device, view_index, world_to_meters, position, rotation, is_double); 140 | }); 141 | 142 | sdk_callbacks->on_post_calculate_stereo_view_offset([](UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, 143 | UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) { 144 | uevr::detail::g_plugin->on_post_calculate_stereo_view_offset(device, view_index, world_to_meters, position, rotation, is_double); 145 | }); 146 | 147 | sdk_callbacks->on_pre_viewport_client_draw([](UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas) { 148 | uevr::detail::g_plugin->on_pre_viewport_client_draw(viewport_client, viewport, canvas); 149 | }); 150 | 151 | sdk_callbacks->on_post_viewport_client_draw([](UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas) { 152 | uevr::detail::g_plugin->on_post_viewport_client_draw(viewport_client, viewport, canvas); 153 | }); 154 | 155 | return true; 156 | } 157 | 158 | BOOL APIENTRY DllMain(HANDLE handle, DWORD reason, LPVOID reserved) { 159 | if (reason == DLL_PROCESS_ATTACH) { 160 | uevr::detail::g_plugin->on_dllmain(); 161 | } 162 | 163 | return TRUE; 164 | } 165 | --------------------------------------------------------------------------------