├── !Update.ps1 ├── .clang-format ├── .clang-format-ignore ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── deploy.yml │ └── maintenance.yml ├── .gitignore ├── .vscode └── settings.json ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── README.md ├── build-release.ps1 ├── cmake ├── Plugin.h.in ├── config.cmake.in └── version.rc.in ├── docs ├── .vitepress │ └── config.ts ├── config │ ├── config-workflow.md │ ├── data-types.md │ ├── examples.md │ ├── file-helpers.md │ ├── references.md │ ├── schema-whatnot.md │ ├── setup-proxy.md │ └── snippets │ │ ├── NoEnchantmentRestrictionRemake.md │ │ ├── SF_NativeAutoHDR.md │ │ └── YouCanSleepRemake.md ├── dkutil │ ├── about.md │ ├── compilation.md │ └── references.md ├── extra │ └── skse.md ├── hooks │ ├── address-fetching.md │ ├── asm-patch.md │ ├── callsite-logging.md │ ├── cave-hook.md │ ├── hook-handles.md │ ├── hooking-with-dkutil.md │ ├── iat-swap.md │ ├── lto-enabled-hook.md │ ├── memory-edit.md │ ├── references.md │ ├── relocation.md │ ├── trampoline.md │ ├── useful-helpers.md │ └── vtable-swap.md ├── index.md ├── logger │ ├── macros.md │ └── setup.md └── utils │ ├── enumeration.md │ ├── numbers.md │ ├── string.md │ └── templates.md ├── format-all.ps1 ├── generate-sln.ps1 ├── include ├── DKUtil │ ├── Config.hpp │ ├── Extra.hpp │ ├── Hook.hpp │ ├── Impl │ │ ├── Config │ │ │ ├── Data.hpp │ │ │ ├── Ini.hpp │ │ │ ├── Json.hpp │ │ │ ├── Proxy.hpp │ │ │ ├── Schema.hpp │ │ │ ├── Shared.hpp │ │ │ └── Toml.hpp │ │ ├── Extra │ │ │ ├── Serialization │ │ │ │ ├── exception.hpp │ │ │ │ ├── interface.hpp │ │ │ │ ├── mock.hpp │ │ │ │ ├── resolver.hpp │ │ │ │ ├── shared.hpp │ │ │ │ └── variant.hpp │ │ │ ├── xconsole.hpp │ │ │ └── xserialize.hpp │ │ ├── Hook │ │ │ ├── API.hpp │ │ │ ├── Assembly.hpp │ │ │ ├── Internal.hpp │ │ │ ├── Internal │ │ │ │ ├── ASMPatch.hpp │ │ │ │ ├── CaveHook.hpp │ │ │ │ ├── IATHook.hpp │ │ │ │ ├── RelHook.hpp │ │ │ │ └── VMTHook.hpp │ │ │ ├── JIT.hpp │ │ │ ├── Shared.hpp │ │ │ ├── Shared_Compat.hpp │ │ │ └── Trampoline.hpp │ │ ├── PCH.hpp │ │ └── Utility │ │ │ ├── enumeration.hpp │ │ │ ├── numbers.hpp │ │ │ ├── shared.hpp │ │ │ ├── string.hpp │ │ │ └── templates.hpp │ ├── Logger.hpp │ └── Utility.hpp └── external │ └── toml.hpp ├── package.json ├── pnpm-lock.yaml ├── test ├── ConfigTest.h ├── HookTest.h ├── LoggerTest.h ├── PCH.h ├── PluginTest.cpp ├── SSEExtraTest.h ├── UtilityTest.h └── configs │ ├── AnotherBiteTheConfig.ini │ ├── DKUtilDebugger.ini │ ├── DKUtilDebugger.json │ ├── DKUtilDebugger.toml │ └── Schema_SC.txt └── vcpkg.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: DontAlign 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveBitFields: false 6 | AlignConsecutiveDeclarations: true 7 | AlignConsecutiveMacros: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: Align 10 | AlignTrailingComments: true 11 | AllowAllArgumentsOnNextLine: false 12 | AllowAllParametersOfDeclarationOnNextLine: false 13 | AllowShortBlocksOnASingleLine: Empty 14 | AllowShortCaseLabelsOnASingleLine: false 15 | AllowShortEnumsOnASingleLine: true 16 | AllowShortFunctionsOnASingleLine: All 17 | AllowShortIfStatementsOnASingleLine: Never 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortLoopsOnASingleLine: true 20 | AlwaysBreakAfterReturnType: None 21 | AlwaysBreakBeforeMultilineStrings: true 22 | AlwaysBreakTemplateDeclarations: Yes 23 | BinPackArguments: true 24 | BinPackParameters: true 25 | BitFieldColonSpacing: After 26 | BraceWrapping: 27 | AfterCaseLabel: true 28 | AfterClass: true 29 | AfterControlStatement: false 30 | AfterEnum: true 31 | AfterFunction: true 32 | AfterNamespace: true 33 | AfterStruct: true 34 | AfterUnion: true 35 | AfterExternBlock: true 36 | BeforeCatch: false 37 | BeforeElse: false 38 | BeforeLambdaBody: false 39 | BeforeWhile: false 40 | IndentBraces: false 41 | SplitEmptyFunction: false 42 | SplitEmptyRecord: false 43 | SplitEmptyNamespace: false 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Custom 46 | BreakBeforeTernaryOperators: false 47 | BreakConstructorInitializers: AfterColon 48 | BreakInheritanceList: AfterColon 49 | BreakStringLiterals: true 50 | ColumnLimit: 0 51 | CompactNamespaces: false 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | Cpp11BracedListStyle: false 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | FixNamespaceComments: false 58 | IncludeBlocks: Preserve 59 | IndentCaseBlocks: true 60 | IndentCaseLabels: false 61 | IndentExternBlock: Indent 62 | IndentGotoLabels: false 63 | IndentPPDirectives: AfterHash 64 | IndentWidth: 4 65 | IndentWrappedFunctionNames: true 66 | KeepEmptyLinesAtTheStartOfBlocks: false 67 | Language: Cpp 68 | MaxEmptyLinesToKeep: 1 69 | NamespaceIndentation: All 70 | PointerAlignment: Left 71 | ReflowComments: false 72 | SortIncludes: true 73 | SortUsingDeclarations: true 74 | SpaceAfterCStyleCast: false 75 | SpaceAfterLogicalNot: false 76 | SpaceAfterTemplateKeyword: true 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeCpp11BracedList: false 79 | SpaceBeforeCtorInitializerColon: true 80 | SpaceBeforeInheritanceColon: true 81 | SpaceBeforeParens: ControlStatements 82 | SpaceBeforeRangeBasedForLoopColon: true 83 | SpaceBeforeSquareBrackets: false 84 | SpaceInEmptyBlock: false 85 | SpaceInEmptyParentheses: false 86 | SpacesBeforeTrailingComments: 2 87 | SpacesInAngles: false 88 | SpacesInCStyleCastParentheses: false 89 | SpacesInConditionalStatement: false 90 | SpacesInContainerLiterals: true 91 | SpacesInParentheses: false 92 | SpacesInSquareBrackets: false 93 | Standard: Latest 94 | TabWidth: 4 95 | UseTab: AlignWithSpaces 96 | LineEnding: CRLF 97 | PackConstructorInitializers: BinPack 98 | -------------------------------------------------------------------------------- /.clang-format-ignore: -------------------------------------------------------------------------------- 1 | test/* 2 | include/external/* -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | insert_final_newline = true 4 | 5 | [*.{asm,c,cc,cpp,cxx,h,hpp,hxx,inc,inl,ixx}] 6 | indent_style = tab 7 | indent_size = 4 8 | 9 | [*.{ini,json,toml,xml}] 10 | indent_style = space 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | \!Update.ps1 linguist-vendored -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy GH Pages 2 | 3 | on: 4 | push: 5 | branches: master 6 | paths: 7 | - "docs/**" 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | concurrency: 16 | group: pages 17 | cancel-in-progress: false 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - uses: pnpm/action-setup@v3 28 | with: 29 | version: 8 30 | - name: Setup Node 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: 20 34 | cache: pnpm 35 | - name: Setup Pages 36 | uses: actions/configure-pages@v4 37 | - name: Install dependencies 38 | run: pnpm i 39 | - name: Build VitePress 40 | run: pnpm docs:build 41 | - name: Upload artifact 42 | uses: actions/upload-pages-artifact@v3 43 | with: 44 | path: docs/.vitepress/dist 45 | 46 | deploy: 47 | environment: 48 | name: github-pages 49 | url: ${{ steps.deployment.outputs.page_url }} 50 | needs: build 51 | runs-on: ubuntu-latest 52 | name: Deploy 53 | steps: 54 | - name: Deploy Pages 55 | id: deployment 56 | uses: actions/deploy-pages@v4 57 | -------------------------------------------------------------------------------- /.github/workflows/maintenance.yml: -------------------------------------------------------------------------------- 1 | name: Maintenance 2 | 3 | on: 4 | push: 5 | branches: master 6 | paths: 7 | - "include/**" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | maintenance: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - uses: DoozyX/clang-format-lint-action@v0.16.2 18 | with: 19 | source: '.' 20 | exclude: './docs' 21 | extensions: 'c,cc,cpp,cppm,cxx,h,hpp,hxx,inl,inc,ixx,mxx' 22 | clangFormatVersion: 16 23 | inplace: True 24 | 25 | - uses: EndBug/add-and-commit@v9 26 | with: 27 | committer_name: clang-format 28 | committer_email: 41898282+github-actions[bot]@users.noreply.github.com 29 | message: 'chore: style formatting' 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | docs/.vitepress/dist -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": false 3 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | 3 | # singleton target across multiple projects 4 | if(TARGET DKUtil) 5 | return() 6 | endif() 7 | 8 | # standards & flags 9 | set(CMAKE_CXX_STANDARD 23) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 12 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUG OFF) 13 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 14 | set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE FILEPATH "") 15 | 16 | include(GNUInstallDirs) 17 | 18 | if(NOT DKUTIL_DEBUG_BUILD) 19 | 20 | # info 21 | project( 22 | DKUtil 23 | VERSION 2.0.0 24 | LANGUAGES CXX 25 | ) 26 | 27 | # out-of-source builds only 28 | if(${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR}) 29 | message(FATAL_ERROR "In-source builds are not allowed.") 30 | endif() 31 | 32 | # dependencies 33 | find_package(nlohmann_json CONFIG REQUIRED) 34 | find_package(spdlog CONFIG REQUIRED) 35 | find_package(xbyak CONFIG REQUIRED) 36 | 37 | # header only library 38 | add_library( 39 | ${PROJECT_NAME} 40 | INTERFACE 41 | ) 42 | 43 | add_library( 44 | ${PROJECT_NAME}::${PROJECT_NAME} 45 | ALIAS 46 | ${PROJECT_NAME} 47 | ) 48 | 49 | target_include_directories( 50 | ${PROJECT_NAME} 51 | INTERFACE 52 | $ 53 | ) 54 | 55 | # install 56 | install( 57 | TARGETS ${PROJECT_NAME} 58 | EXPORT ${PROJECT_NAME}-targets 59 | ) 60 | 61 | install( 62 | EXPORT ${PROJECT_NAME}-targets 63 | NAMESPACE ${PROJECT_NAME}:: 64 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 65 | ) 66 | 67 | configure_file( 68 | cmake/config.cmake.in 69 | ${PROJECT_NAME}Config.cmake 70 | @ONLY 71 | ) 72 | 73 | install( 74 | FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 75 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 76 | ) 77 | 78 | install( 79 | DIRECTORY include 80 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/include 81 | ) 82 | 83 | else() 84 | 85 | # info 86 | project( 87 | DKUtilDebugger 88 | VERSION 2.0.0 89 | LANGUAGES CXX 90 | ) 91 | 92 | # out-of-source builds only 93 | if(${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR}) 94 | message(FATAL_ERROR "In-source builds are not allowed.") 95 | endif() 96 | 97 | # in-place configuration 98 | configure_file( 99 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Plugin.h.in 100 | ${CMAKE_CURRENT_BINARY_DIR}/include/Plugin.h 101 | @ONLY 102 | ) 103 | 104 | configure_file( 105 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in 106 | ${CMAKE_CURRENT_BINARY_DIR}/version.rc 107 | @ONLY 108 | ) 109 | 110 | # source files 111 | execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_SOURCE_DIR}/!Update.ps1" "SOURCEGEN" "${PROJECT_VERSION}" "${CMAKE_CURRENT_BINARY_DIR}") 112 | include(${CMAKE_CURRENT_BINARY_DIR}/sourcelist.cmake) 113 | source_group( 114 | TREE ${CMAKE_CURRENT_SOURCE_DIR} 115 | FILES ${SOURCES} 116 | ) 117 | 118 | source_group( 119 | TREE ${CMAKE_CURRENT_BINARY_DIR} 120 | FILES ${CMAKE_CURRENT_BINARY_DIR}/include/Plugin.h 121 | ) 122 | 123 | # dependencies 124 | add_subdirectory($ENV{CommonLibSSEPath} CommonLibSSE) 125 | find_package(nlohmann_json CONFIG REQUIRED) 126 | find_package(spdlog CONFIG REQUIRED) 127 | find_package(xbyak CONFIG REQUIRED) 128 | 129 | # runtime 130 | if(DKU_CONSOLE) 131 | add_executable( 132 | ${PROJECT_NAME} 133 | ${SOURCES} 134 | ${CMAKE_CURRENT_BINARY_DIR}/include/Plugin.h 135 | ${CMAKE_CURRENT_BINARY_DIR}/version.rc 136 | .clang-format 137 | vcpkg.json 138 | ) 139 | target_compile_definitions( 140 | ${PROJECT_NAME} 141 | PRIVATE 142 | DKU_CONSOLE=1 143 | ) 144 | else() 145 | add_library( 146 | ${PROJECT_NAME} 147 | SHARED 148 | ${SOURCES} 149 | ${CMAKE_CURRENT_BINARY_DIR}/include/Plugin.h 150 | ${CMAKE_CURRENT_BINARY_DIR}/version.rc 151 | .clang-format 152 | vcpkg.json 153 | ) 154 | endif() 155 | 156 | # include dir 157 | target_include_directories( 158 | ${PROJECT_NAME} 159 | PRIVATE 160 | ${CMAKE_CURRENT_BINARY_DIR}/include 161 | ${CMAKE_CURRENT_SOURCE_DIR}/include 162 | ${CMAKE_CURRENT_SOURCE_DIR}/src 163 | ) 164 | 165 | # linkage 166 | target_link_libraries( 167 | ${PROJECT_NAME} 168 | PRIVATE 169 | CommonLibSSE::CommonLibSSE 170 | nlohmann_json::nlohmann_json 171 | spdlog::spdlog 172 | xbyak::xbyak 173 | ) 174 | 175 | # compiler def 176 | if (MSVC) 177 | add_compile_definitions(_UNICODE) 178 | 179 | target_compile_options( 180 | ${PROJECT_NAME} 181 | PRIVATE 182 | /MP 183 | /await 184 | /W0 185 | /WX 186 | /permissive- 187 | /Zc:alignedNew 188 | /Zc:auto 189 | /Zc:__cplusplus 190 | /Zc:externC 191 | /Zc:externConstexpr 192 | /Zc:forScope 193 | /Zc:hiddenFriend 194 | /Zc:implicitNoexcept 195 | /Zc:lambda 196 | /Zc:noexceptTypes 197 | /Zc:preprocessor 198 | /Zc:referenceBinding 199 | /Zc:rvalueCast 200 | /Zc:sizedDealloc 201 | /Zc:strictStrings 202 | /Zc:ternary 203 | /Zc:threadSafeInit 204 | /Zc:trigraphs 205 | /Zc:wchar_t 206 | /wd4200 # nonstandard extension used : zero-sized array in struct/union 207 | ) 208 | endif() 209 | 210 | # PCH 211 | target_precompile_headers( 212 | ${PROJECT_NAME} 213 | PRIVATE 214 | test/PCH.h 215 | ) 216 | 217 | if(NOT DKU_CONSOLE) 218 | # post build event 219 | add_custom_command( 220 | TARGET 221 | ${PROJECT_NAME} 222 | POST_BUILD 223 | COMMAND 224 | powershell -NoProfile -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_SOURCE_DIR}/!Update.ps1" "COPY" "${PROJECT_VERSION}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$(ConfigurationName)" "${PROJECT_NAME}" "${ANNIVERSARY_EDITION}" 225 | COMMENT 226 | "Updating project :))" 227 | ) 228 | else() 229 | add_custom_command( 230 | TARGET 231 | ${PROJECT_NAME} 232 | POST_BUILD 233 | COMMAND 234 | ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/test/configs" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$(ConfigurationName)" 235 | ) 236 | endif() 237 | 238 | # called via !Rebuild.ps1 239 | if(DEFINED GROUP) 240 | set_target_properties( 241 | ${PROJECT_NAME} 242 | PROPERTIES 243 | FOLDER 244 | ${GROUP} 245 | ) 246 | endif() 247 | 248 | set(DKUTIL_DEBUG_BUILD OFF) 249 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR} DKUtil) 250 | 251 | endif() 252 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 21, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "common", 11 | "hidden": true, 12 | "cacheVariables": { 13 | "F4SE_SUPPORT_XBYAK": "ON", 14 | "SKSE_SUPPORT_XBYAK": "ON", 15 | "SFSE_SUPPORT_XBYAK": "ON", 16 | "SKSEPluginAuthor": "$env{SKSEPluginAuthor}" 17 | } 18 | }, 19 | { 20 | "name": "vcpkg", 21 | "hidden": true, 22 | "cacheVariables": { 23 | "CMAKE_TOOLCHAIN_FILE": { 24 | "type": "STRING", 25 | "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" 26 | }, 27 | "VCPKG_TARGET_TRIPLET": "x64-windows-static-md" 28 | } 29 | }, 30 | { 31 | "name": "win64", 32 | "hidden": true, 33 | "architecture": "x64", 34 | "cacheVariables": { 35 | "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL" 36 | } 37 | }, 38 | { 39 | "name": "msvc", 40 | "hidden": true, 41 | "cacheVariables": { 42 | "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX $penv{CXXFLAGS}" 43 | }, 44 | "generator": "Visual Studio 17 2022", 45 | "vendor": { 46 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 47 | "intelliSenseMode": "windows-msvc-x64", 48 | "enableMicrosoftCodeAnalysis": true, 49 | "enableClangTidyCodeAnalysis": true 50 | } 51 | } 52 | }, 53 | { 54 | "name": "AE", 55 | "cacheVariables": { 56 | "ENABLE_SKYRIM_AE": "ON", 57 | "ENABLE_SKYRIM_SE": "OFF", 58 | "ENABLE_SKYRIM_VR": "OFF" 59 | }, 60 | "inherits": [ 61 | "common", 62 | "vcpkg", 63 | "win64", 64 | "msvc" 65 | ] 66 | }, 67 | { 68 | "name": "SE", 69 | "cacheVariables": { 70 | "ENABLE_SKYRIM_AE": "OFF", 71 | "ENABLE_SKYRIM_SE": "ON", 72 | "ENABLE_SKYRIM_VR": "OFF" 73 | }, 74 | "inherits": [ 75 | "common", 76 | "vcpkg", 77 | "win64", 78 | "msvc" 79 | ] 80 | }, 81 | { 82 | "name": "VR", 83 | "cacheVariables": { 84 | "ENABLE_SKYRIM_AE": "OFF", 85 | "ENABLE_SKYRIM_SE": "OFF", 86 | "ENABLE_SKYRIM_VR": "ON" 87 | }, 88 | "inherits": [ 89 | "common", 90 | "vcpkg", 91 | "win64", 92 | "msvc" 93 | ] 94 | }, 95 | { 96 | "name": "ALL", 97 | "cacheVariables": { 98 | "ENABLE_SKYRIM_AE": "ON", 99 | "ENABLE_SKYRIM_SE": "ON", 100 | "ENABLE_SKYRIM_VR": "ON" 101 | }, 102 | "inherits": [ 103 | "common", 104 | "vcpkg", 105 | "win64", 106 | "msvc" 107 | ] 108 | }, 109 | { 110 | "name": "PRE-AE", 111 | "cacheVariables": { 112 | "ENABLE_SKYRIM_AE": "OFF", 113 | "ENABLE_SKYRIM_SE": "ON", 114 | "ENABLE_SKYRIM_VR": "ON" 115 | }, 116 | "inherits": [ 117 | "common", 118 | "vcpkg", 119 | "win64", 120 | "msvc" 121 | ] 122 | }, 123 | { 124 | "name": "FLATRIM", 125 | "cacheVariables": { 126 | "ENABLE_SKYRIM_AE": "ON", 127 | "ENABLE_SKYRIM_SE": "ON", 128 | "ENABLE_SKYRIM_VR": "OFF" 129 | }, 130 | "inherits": [ 131 | "common", 132 | "vcpkg", 133 | "win64", 134 | "msvc" 135 | ] 136 | } 137 | ] 138 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

DKUtil

2 | 3 | Some utility headers to help with x64 native plugin development 4 | 5 | # Documentation 6 | See [wiki here!](https://gottyduke.github.io/DKUtil/) 7 | 8 | # Implementations 9 | ![Config](https://img.shields.io/badge/Config-1.2.0-R.svg) 10 | ![Hook](https://img.shields.io/badge/Hook-2.6.6-R.svg) 11 | ![Logger](https://img.shields.io/badge/Logger-1.2.5-R.svg) 12 | ![Utility](https://img.shields.io/badge/Utility-1.0.1-R.svg) 13 | ![Extra(For SKSE)](https://img.shields.io/badge/Extra-1.0.0-R.svg) 14 | 15 | + Config 16 | - abstracted and contained config layer 17 | - `ini`, `toml`, `json` file support 18 | - `bool`, `int64_t`, `double`, `string` type support 19 | - built in array support 20 | - multiple file loads & generate default file 21 | - custom formatted string parser to c++ structure 22 | + Hook 23 | - pattern scanner 24 | - asm patch 25 | - cave hook 26 | - virtual method table swap 27 | - import address table swap 28 | - simple function hook (write_call/write_branch) 29 | - non-volatile call (LTO enabled hooks) 30 | - various usefully gathered utils 31 | + Logge 32 | - logging macros 33 | + Utility 34 | + function 35 | + `consteval` helper functions retrieving the argument count of a function. 36 | + model 37 | + `Singleton` data model abstract class to save boilerplate code. 38 | + `enumeration` addition to the original `RE::stl::enumeration`. 39 | + static reflection for enum name, type name and value name, support value_type(`n`) and flag_type(`1<MIT License, 2020-present DK

56 | -------------------------------------------------------------------------------- /build-release.ps1: -------------------------------------------------------------------------------- 1 | Remove-Item $PSScriptRoot/build -Recurse -Force -ErrorAction:SilentlyContinue -Confirm:$False | Out-Null 2 | & cmake -B $PSScriptRoot/build -S $PSScriptRoot/BG3WASD --preset=REL -DPLUGIN_MODE:BOOL=TRUE 3 | & cmake --build $PSScriptRoot/build --config Release -------------------------------------------------------------------------------- /cmake/Plugin.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace Plugin 5 | { 6 | inline constexpr auto NAME = "@PROJECT_NAME@"sv; 7 | inline constexpr auto AUTHOR = "@SKSEPluginAuthor@"sv; 8 | inline constexpr REL::Version Version 9 | { 10 | @PROJECT_VERSION_MAJOR@u, 11 | @PROJECT_VERSION_MINOR@u, 12 | @PROJECT_VERSION_PATCH@u 13 | }; 14 | } 15 | 16 | 17 | DLLEXPORT constinit auto SKSEPlugin_Version = []() noexcept 18 | { 19 | SKSE::PluginVersionData data{}; 20 | 21 | data.PluginVersion(Plugin::Version); 22 | data.PluginName(Plugin::NAME); 23 | data.AuthorName(Plugin::AUTHOR); 24 | data.UsesAddressLibrary(true); 25 | data.HasNoStructUse(true); 26 | 27 | return data; 28 | }(); 29 | 30 | 31 | DLLEXPORT bool SKSEAPI SKSEPlugin_Query(const SKSE::QueryInterface*, SKSE::PluginInfo* pluginInfo) 32 | { 33 | pluginInfo->name = SKSEPlugin_Version.pluginName; 34 | pluginInfo->infoVersion = SKSE::PluginInfo::kVersion; 35 | pluginInfo->version = SKSEPlugin_Version.pluginVersion; 36 | 37 | return true; 38 | } 39 | -------------------------------------------------------------------------------- /cmake/config.cmake.in: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") 2 | include(CMakeFindDependencyMacro) 3 | 4 | find_path(SIMPLEINI_INCLUDE_DIRS "SimpleIni.h") 5 | find_dependency(imgui CONFIG) 6 | find_dependency(nlohmann_json CONFIG) 7 | find_dependency(spdlog CONFIG) 8 | find_dependency(tomlplusplus CONFIG) 9 | find_dependency(xbyak CONFIG) -------------------------------------------------------------------------------- /cmake/version.rc.in: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 1 VERSIONINFO 4 | FILEVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0 5 | PRODUCTVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0 6 | FILEFLAGSMASK 0x17L 7 | #ifdef _DEBUG 8 | FILEFLAGS 0x1L 9 | #else 10 | FILEFLAGS 0x0L 11 | #endif 12 | FILEOS 0x4L 13 | FILETYPE 0x1L 14 | FILESUBTYPE 0x0L 15 | BEGIN 16 | BLOCK "StringFileInfo" 17 | BEGIN 18 | BLOCK "040904b0" 19 | BEGIN 20 | VALUE "FileDescription", "@PROJECT_NAME@" 21 | VALUE "FileVersion", "@PROJECT_VERSION@.0" 22 | VALUE "InternalName", "@PROJECT_NAME@" 23 | VALUE "LegalCopyright", "MIT License" 24 | VALUE "ProductName", "@PROJECT_NAME@" 25 | VALUE "ProductVersion", "@PROJECT_VERSION@.0" 26 | END 27 | END 28 | BLOCK "VarFileInfo" 29 | BEGIN 30 | VALUE "Translation", 0x409, 1200 31 | END 32 | END 33 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, type DefaultTheme } from 'vitepress' 2 | 3 | const commit = await (await fetch("https://api.github.com/repos/gottyduke/dkutil/commits/master")).json() 4 | const lastUpdate = new Date(commit.commit.author.date).toISOString().slice(0, 10) 5 | 6 | export default defineConfig({ 7 | base: '/DKUtil/', 8 | 9 | title: 'DKUtil', 10 | description: 'Some utility headers to help with windows x64 native plugin development', 11 | lang: 'en-US', 12 | 13 | lastUpdated: true, 14 | cleanUrls: true, 15 | metaChunk: true, 16 | 17 | themeConfig: { 18 | socialLinks: [ 19 | { icon: 'github', link: 'https://github.com/gottyduke' } 20 | ], 21 | 22 | search: { 23 | provider: 'local', 24 | }, 25 | 26 | nav: makeNavBar(), 27 | 28 | sidebar: { 29 | '/dkutil/': { base: '/dkutil/', items: makeSidebarDKUtil() }, 30 | '/logger/': { base: '/logger/', items: makeSidebarLogger() }, 31 | '/config/': { base: '/config/', items: makeSidebarConfig() }, 32 | '/hooks/': { base: '/hooks/', items: makeSidebarHooks() }, 33 | '/utils/': { base: '/utils/', items: makeSidebarUtils() }, 34 | '/extra/': { base: '/extra/', items: makeSidebarExtra() }, 35 | }, 36 | 37 | editLink: { 38 | pattern: 'https://github.com/gottyduke/dkutil/edit/master/docs/:path', 39 | text: 'Edit current page on GitHub' 40 | }, 41 | 42 | footer: { 43 | message: 'Released under the MIT License', 44 | copyright: 'Copyright © 2020-present DK' 45 | }, 46 | } 47 | }) 48 | 49 | function makeNavBar(): DefaultTheme.NavItem[] { 50 | return [ 51 | { 52 | text: `DKUtil(${lastUpdate})`, 53 | items: [ 54 | { 55 | text: 'About this Project', 56 | link: '/dkutil/about', 57 | activeMatch: '/dkutil/' 58 | }, 59 | { 60 | text: 'Projects with DKUtil', 61 | link: '/dkutil/references', 62 | activeMatch: '/dkutil/' 63 | }, 64 | ] 65 | }, 66 | { 67 | text: 'Logger', 68 | link: '/logger/setup', 69 | activeMatch: '/logger/' 70 | }, 71 | { 72 | text: 'Config', 73 | link: '/config/config-workflow', 74 | activeMatch: '/config/' 75 | }, 76 | { 77 | text: 'Hook', 78 | link: '/hooks/hooking-with-dkutil', 79 | activeMatch: '/hooks/' 80 | }, 81 | { 82 | text: 'Utils', 83 | link: '/utils/templates', 84 | activeMatch: '/utils/' 85 | }, 86 | { 87 | text: 'Extra', 88 | link: '/extra/skse', 89 | activeMatch: '/extra/' 90 | }, 91 | ] 92 | } 93 | 94 | function makeSidebarDKUtil(): DefaultTheme.SidebarItem[] { 95 | return [ 96 | { 97 | text: 'DKUtil', 98 | collapsed: false, 99 | items: [ 100 | { text: 'About this Project', link: 'about' }, 101 | { text: 'Compilation Flags', link: 'compilation' }, 102 | { text: 'Projects with DKUtil', link: 'references' }, 103 | ] 104 | }, 105 | ] 106 | } 107 | 108 | function makeSidebarLogger(): DefaultTheme.SidebarItem[] { 109 | return [ 110 | { 111 | text: 'Logger', 112 | collapsed: false, 113 | items: [ 114 | { text: 'Setup', link: 'setup' }, 115 | { text: 'Macros', link: 'macros' }, 116 | ] 117 | }, 118 | ] 119 | } 120 | 121 | function makeSidebarConfig(): DefaultTheme.SidebarItem[] { 122 | return [ 123 | { 124 | text: 'Config', 125 | collapsed: false, 126 | items: [ 127 | { text: 'Configure How-to', link: 'config-workflow' }, 128 | { text: 'Data Types', link: 'data-types' }, 129 | { text: 'Setup Proxy', link: 'setup-proxy' }, 130 | { text: 'Schema Parser', link: 'schema-whatnot' }, 131 | { text: 'File Helper', link: 'file-helpers' }, 132 | ] 133 | }, 134 | { 135 | text: 'Examples', 136 | collapsed: false, 137 | items: [ 138 | { text: 'Singleton Settings', link: 'examples' }, 139 | { text: 'External References', link: 'references' }, 140 | ] 141 | }, 142 | ] 143 | } 144 | 145 | function makeSidebarHooks(): DefaultTheme.SidebarItem[] { 146 | return [ 147 | { 148 | text: 'Hooks & Memory', 149 | collapsed: false, 150 | items: [ 151 | { text: 'Hooking with DKUtil', link: 'hooking-with-dkutil' }, 152 | { text: 'Memory Edit', link: 'memory-edit' }, 153 | { text: 'Address Fetching', link: 'address-fetching' }, 154 | { text: 'Hook Handles', link: 'hook-handles' }, 155 | { text: 'Trampoline', link: 'trampoline' }, 156 | ] 157 | }, 158 | { 159 | text: 'API', 160 | collapsed: false, 161 | items: [ 162 | { text: 'Relocation', link: 'relocation' }, 163 | { text: 'ASM Patch', link: 'asm-patch' }, 164 | { text: 'Cave Hook', link: 'cave-hook' }, 165 | { text: 'VTable Swap', link: 'vtable-swap' }, 166 | { text: 'IAT Swap', link: 'iat-swap' }, 167 | { text: 'LTO-Enabled Hook', link: 'lto-enabled-hook' }, 168 | ] 169 | }, 170 | { 171 | text: 'Examples', 172 | collapsed: false, 173 | items: [ 174 | { text: 'Useful Helpers', link: 'useful-helpers' }, 175 | { text: 'Callsite Logging', link: 'callsite-logging' }, 176 | { text: 'External References', link: 'references' }, 177 | ] 178 | }, 179 | ] 180 | } 181 | 182 | function makeSidebarUtils(): DefaultTheme.SidebarItem[] { 183 | return [ 184 | { 185 | text: 'Utils', 186 | collapsed: false, 187 | items: [ 188 | { text: 'Templates', link: 'templates' }, 189 | { text: 'Enumeration', link: 'enumeration' }, 190 | { text: 'String', link: 'string' }, 191 | { text: 'Numbers', link: 'numbers' }, 192 | ] 193 | }, 194 | ] 195 | } 196 | 197 | function makeSidebarExtra(): DefaultTheme.SidebarItem[] { 198 | return [ 199 | { 200 | text: 'Extra', 201 | collapsed: false, 202 | items: [ 203 | { text: 'Serializable', link: 'skse' }, 204 | ] 205 | }, 206 | ] 207 | } -------------------------------------------------------------------------------- /docs/config/config-workflow.md: -------------------------------------------------------------------------------- 1 | # DKUtil::Config 2 | 3 | An all purpose configuration module for x64 native plugin projects, supports `ini`, `json` and `toml` file types out of box. No more handling these settings on our own, use simplied API from this library! 4 | 5 | ## Workflow 6 | 7 | The configuration module in DKUtil works as "proxy per file", each `Proxy` represents one specific configuration file and manages the subsequent `Bind`, `Load`, `Write`, and `Generate` calls to this file and its bound `Data`. 8 | 9 | 1. Declare `Data` with key names. 10 | 2. Initialize a `Proxy` for a config file on system. 11 | 3. Binds the `Data` to `Proxy` with default values. 12 | 4. Use `Proxy` to load/regenerate/output the bound `Data`. 13 | 5. Use updated `Data` in code. 14 | 15 | ## Usage 16 | 17 | The entire module is behind the namespace `DKUtil::Config` or `dku::Config`. To use, include the header: 18 | 19 | ```cpp 20 | #include "DKUtil/Config.hpp" 21 | ``` 22 | 23 | ## Config Lookup Path 24 | 25 | By default the config file lookup directory is the current working path. 26 | The relative path `CONFIG_ENTRY` can be defined before including. 27 | 28 | ```cpp 29 | #define CONFIG_ENTRY "configs\\" 30 | // this translates to "process_cwd/configs/" 31 | #include "DKUtil/Config.hpp" 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/config/data-types.md: -------------------------------------------------------------------------------- 1 | # Data Types 2 | 3 | `DKUtil::Config` supports 3 primitive data types, string variants and their array type(collection). 4 | 5 | + Integer 6 | + Double 7 | + Boolean 8 | + String 9 | 10 | ## Declaring 11 | 12 | To declare configuration data, initialize them with key field and optional section field. 13 | 14 | ```cpp 15 | using namespace DKUtil::Alias; // type alias 16 | 17 | Integer myInt64Data{ "MyIntDataKey", "General" }; // std::int64_t 18 | Double myDoubleData{ "MyDoubleKey", "General" }; // double 19 | Boolean myBoolData{ "MyBoolKey" }; // bool 20 | String myStringData{ "MyStringKey" }; // std::basic_string 21 | ``` 22 | 23 | ::: tip Section 24 | Section field is optional and unnamed sections will be put under `Global`. Section is also ignored for JSON. 25 | ::: 26 | 27 | ## Accessing 28 | 29 | The data can be accessed by operator `*` as if it were a pointer, or the const getter method `get_data()`/`get_collection()`. 30 | 31 | ::: warning Collection on Singular Data 32 | When the data is singular but called with `get_collection()`, a size-1 collection will be returned with the singular data only. 33 | ::: 34 | 35 | If the data is a collection, the reference of its members can be accessed by operator `[]` with index. There are two exceptions to this: 36 | 37 | + The **first** element will be returned if the data is not a collection. 38 | + The **last** element will be returned if the index is out of bound. 39 | 40 | `is_collection()` can be used, or `get_size()` which will return `0` if it's not a collection. 41 | 42 | ::: warning 43 | This behavior is planned to change with internal exception handling 44 | ::: 45 | -------------------------------------------------------------------------------- /docs/config/examples.md: -------------------------------------------------------------------------------- 1 | # Config Examples 2 | 3 | Given configuration file `MyConfigFile.toml`: 4 | 5 | ```toml 6 | [SectionA] 7 | MyIntData = 10086 8 | MyIntArray = [99, 96, 92, 87, 71] 9 | MyDouble = 114.514 10 | MyBool = true 11 | MyString = "Hello toml" 12 | MyStringArray = [ 13 | "First", 14 | "Second", 15 | "LastButNotLeast" 16 | ] 17 | ``` 18 | 19 | ::: code-group 20 | 21 | ```cpp [Load Data] 22 | #include "DKUtil/Config.hpp" 23 | 24 | using namespace DKUtil::Alias; // For type alias 25 | 26 | class Settings : dku::model::Singleton 27 | { 28 | public: 29 | Integer MyInt64Data{ "MyIntData" }; 30 | Integer MyInt64ArrayData{ "MyIntArray" }; 31 | Double MyDoubleData{ "MyDouble" }; 32 | Boolean MyBoolData{ "MyBool" }; 33 | String MyStringData{ "MyString" }; 34 | String MyStringArrayData{ "MyStringArray" }; 35 | 36 | void Load() noexcept 37 | { 38 | _config.Bind<0, 100>(MyInt64Data, 10); 39 | _config.Bind<-360, 360>(MyInt64ArrayData, 10, 78, 314, 996); 40 | _config.Bind(MyStringData, "Hello DKUtil"); 41 | _config.Bind(MyStringArrayData, "First", "Second", "Third"); 42 | _config.Bind(MyBoolData, true); 43 | _config.Bind<0.0, 10.0>(MyDoubleData, 3.14154); 44 | 45 | _config.Load(); 46 | } 47 | 48 | private: 49 | // cannot use auto for class member declaration 50 | TomlConfig _config = COMPILE_PROXY("MyConfigFile.toml"sv); 51 | } 52 | ``` 53 | 54 | ```cpp [Use Data] 55 | auto* setting = Settings::GetSingleton(); 56 | 57 | // access 58 | if (setting->MyBoolData.get_data()) { 59 | // true 60 | } 61 | 62 | // access data 63 | INFO("{} {} {}", *setting->MyInt64Data, *setting->MyStringData, *setting->MyDoubleData); 64 | 65 | // traverse the collection 66 | for (auto& str : setting->MyStringArrayData.get_collection()) { 67 | INFO(str); 68 | } 69 | 70 | // modify 71 | auto& data = *setting->MyInt64Data; 72 | int result = SomeCalc(data); 73 | data *= result; 74 | ``` 75 | 76 | ::: 77 | 78 | ::: tip 79 | Wrapping in a static Settings class is recommended but not required. 80 | ::: 81 | 82 | Also see [external references](references) -------------------------------------------------------------------------------- /docs/config/file-helpers.md: -------------------------------------------------------------------------------- 1 | # File Helpers 2 | 3 | `DKUtil::Config` provides some helper functions for files: 4 | 5 | + To get full path of config file, relative or not: 6 | ```cpp 7 | std::string GetPath(const std::string_view a_file); 8 | ``` 9 | 10 | + To list all files fulfilling the criterion: 11 | ```cpp 12 | template 13 | std::vector GetAllFiles( 14 | std::string_view a_dir = {}, 15 | std::string_view a_ext = {}, 16 | std::string_view a_prefix = {}, 17 | std::string_view a_suffix = {}); 18 | ``` -------------------------------------------------------------------------------- /docs/config/references.md: -------------------------------------------------------------------------------- 1 | # Snippets 2 | 3 | Some code snippets that utilizes DKUtil::Config. 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/config/schema-whatnot.md: -------------------------------------------------------------------------------- 1 | # Schema Parser 2 | 3 | `Config::Schema` is a special type of `Proxy` that allows you to parse strings into custom c++ data structure. 4 | 5 | ## Example 6 | 7 | Given a struct `CustomData`: 8 | 9 | ```cpp 10 | struct CustomData 11 | { 12 | std::uint64_t form; 13 | std::string name; 14 | std::string payload; 15 | bool excluded; 16 | }; 17 | ``` 18 | 19 | And expect schema string with delimiter `|`: 20 | 21 | ``` 22 | "0x12345|Random|None|true" 23 | ``` 24 | 25 | ## One Liner 26 | 27 | For the sake of ease of use, `DKUtil::Config` provides static function to parse strings as you go: 28 | 29 | ```cpp 30 | std::string s{ "0x12345|Random|None|true" }; 31 | 32 | CustomData data = dku::Config::ParseSchemaString(s, "|"); 33 | INFO("{} {} {} {}", data.form, data.name, data.payload, data.excluded); 34 | ``` 35 | 36 | ## Use Proxy 37 | 38 | To parse the entire schema file that consists lines of schema strings: 39 | 40 | ```cpp 41 | auto schema = SCHEMA_PROXY("Schema_SC.txt"); 42 | schema.Load(); 43 | auto& parser = schema.get_parser(); 44 | 45 | std::vector data; 46 | while (true) { 47 | auto entry = parser.ParseNextLine("|"); 48 | if (!entry.has_value()) { 49 | break; 50 | } 51 | 52 | data.push_back(entry.value()); 53 | } 54 | ``` 55 | 56 | ::: tip 57 | You can also utilize [`dku::Config::GetAllFiles(...)`](file-helpers) to collect files at runtime. 58 | ::: 59 | 60 | ## Aggregate Conversion 61 | 62 | All schema functions support user defined type and varadic template arguments: 63 | 64 | ```cpp 65 | std::string s{ "0x12345|1.5|true" }; 66 | 67 | auto [a,b,c] = dku::Config::ParseSchemaString(s, "|"); 68 | ``` 69 | 70 | ## Special Values 71 | 72 | **spaces**: all whitespaces for data type except `std::string` will be trimmed before parsing. 73 | 74 | ```cpp 75 | " 1000 " == "1000 " == "1000" 76 | ``` 77 | 78 | **bool**: `true`/`false` is case insensitive, `1`/`0` is also accepted. 79 | 80 | ```cpp 81 | " True " == "True" == "true" == "1" 82 | ``` 83 | 84 | **hex**: to guarantee a hex number conversion, regardless of prefix `0x` in string segment, use `dku::numbers::hex` in place of `std::uint64_t`. 85 | 86 | ```cpp 87 | dku::numbers::hex formID; // special hex number holder 88 | formID = "0x14"; // 0x14 89 | formID = "100"; // 0x100 90 | formID = 100; // 0x64 91 | std::uint64_t num = formID; // implicit conversion to numbers 92 | fmt::format("{} {}", formID, num); // formats to "0x64 100" 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/config/setup-proxy.md: -------------------------------------------------------------------------------- 1 | # Proxy 2 | 3 | `Proxy` supports `json`, `ini`, and `toml` files. Its type can be known at compile time and/or runtime determined for the given file. 4 | 5 | If the file name and type is already known at the compile time, use `COMPILE_PROXY` macro to get the corresponding parser for such file type. 6 | 7 | If the file name is runtime generated, such as file iterator from a directory, use `RUNTIME_PROXY` macro for file type/name that is unknown at the compile time. 8 | 9 | ```cpp 10 | #include "DKUtil/Config.hpp" 11 | 12 | JsonConfig MainConfig = COMPILE_PROXY("MyConfigFile.json"sv); 13 | // or to omit the type 14 | auto MainConfig = COMPILE_PROXY("MyConfigFile.json"sv); 15 | 16 | std::string runtimeFileName = SomeLoadFileCall(); 17 | auto RuntimeConfig = RUNTIME_PROXY(runtimeFile); 18 | ``` 19 | 20 | ## Bind 21 | 22 | `Proxy::Bind` is used for binding data to proxy **with** default value. 23 | 24 | ```cpp 25 | Integer myInt64Data{ "MyIntData" }; 26 | 27 | auto Proxy = COMPILE_PROXY("MyConfigFile.toml"sv); 28 | 29 | Proxy.Bind(myInt64Data, 100); 30 | ``` 31 | 32 | To bind data with default collection value, pass in multiple values: 33 | 34 | ```cpp 35 | Proxy.Bind(myInt64Data, 100, 200, 300); 36 | ``` 37 | 38 | To bind data with a min/max numeric range, pass in template parameters. Range limits collection values as well. 39 | 40 | ```cpp 41 | Proxy.Bind<-100, 100>(myInt64Data, -20, 50); 42 | ``` 43 | 44 | ::: tip Mismatch 45 | The actual data value/collection will be updated after `Proxy::Load`, a default singular value will become a collection of values if the actual config is array. 46 | ::: 47 | 48 | ## Load 49 | 50 | After binding data to proxy, `Proxy::Load` can be used to load, parse, and update bound data values. 51 | 52 | ::: code-group 53 | 54 | ```cpp [File] 55 | Proxy.Load(); 56 | ``` 57 | 58 | ```cpp [String] 59 | std::ifstream reader{ filename }; 60 | std::string file{}; 61 | reader >> file; 62 | 63 | Proxy.Load(file.data()); 64 | ``` 65 | 66 | ::: 67 | 68 | ::: warning Missing File 69 | If the file cannot be found, a default configuration file with default values will be written in place. 70 | ::: 71 | 72 | ## Write 73 | 74 | `Proxy::Write` can be used to output to the file with current bound data values. 75 | 76 | ```cpp 77 | Proxy.Write() 78 | ``` 79 | 80 | `Proxy::Write` also accepts an optional output filename parameter instead. 81 | 82 | ## Append 83 | 84 | To append new data entries to an existing config file(retroactive update): 85 | 86 | ```cpp 87 | Proxy.Load(); 88 | // generate new entries with default value 89 | Proxy.Generate(); 90 | // write new config file 91 | Proxy.Write(); 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/config/snippets/NoEnchantmentRestrictionRemake.md: -------------------------------------------------------------------------------- 1 | [NoEnchantmentRestrictionRemake](https://github.com/gottyduke/NoEnchantmentRestrictionRemake) 2 | 3 | ::: code-group 4 | 5 | ```cpp [Config.h] 6 | #pragma once 7 | #include "DKUtil/Config.hpp" 8 | 9 | namespace Config 10 | { 11 | using namespace DKUtil::Alias; 12 | 13 | extern Boolean EnableDE; 14 | extern Boolean EnableUE; 15 | extern Integer UEMax; 16 | extern String Exclusion; 17 | 18 | void Load(); 19 | } 20 | ``` 21 | 22 | ```cpp [Config.cpp] 23 | #include "Config.h" 24 | 25 | namespace Config 26 | { 27 | Boolean EnableDE{ "EnableDisenchantEverything" }; 28 | Boolean EnableUE{ "EnableUnlimitedEnchantment" }; 29 | Integer UEMax{ "EnchantmentMaxSlots" }; 30 | String Exclusion{ "ExcludedEnchantments" }; 31 | 32 | void Load() 33 | { 34 | auto mainConfig = COMPILE_PROXY("NoEnchantmentRestrictionRemake.toml"sv); 35 | 36 | mainConfig.Bind(EnableDE, true); 37 | mainConfig.Bind(EnableUE, true); 38 | mainConfig.Bind(UEMax, 3); 39 | mainConfig.Bind(Exclusion, "0|Example.esp"); 40 | 41 | mainConfig.Load(); 42 | 43 | INFO("Config Loaded"sv); 44 | } 45 | } 46 | ``` 47 | 48 | ```cpp [Usage] 49 | DKUtil::Hook::WriteImm(_Hook_UES->TramPtr, static_cast(*Config::UEMax)); // [!code focus] 50 | 51 | void Validator::ResolveExclusion() 52 | { 53 | auto* dataHandler = RE::TESDataHandler::GetSingleton(); 54 | 55 | for (auto& ex : Config::Exclusion.get_collection()) { // [!code focus] 56 | auto list = dku::string::split(ex, "|"sv); // [!code focus] 57 | if (list.size() < 2) { 58 | continue; 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | ::: 65 | -------------------------------------------------------------------------------- /docs/config/snippets/SF_NativeAutoHDR.md: -------------------------------------------------------------------------------- 1 | [SF_NativeAutoHDR](https://github.com/ersh1/SF_NativeAutoHDR) 2 | 3 | ::: code-group 4 | 5 | ```cpp [Settings.h] 6 | #pragma once 7 | #include "DKUtil/Config.hpp" 8 | 9 | namespace Settings 10 | { 11 | using namespace DKUtil::Alias; 12 | 13 | class Main : public DKUtil::model::Singleton
14 | { 15 | public: 16 | Integer ImageSpaceBufferFormat{ "ImageSpaceBufferFormat", "Main" }; 17 | Boolean UpgradeUIRenderTarget{ "UpgradeUIRenderTarget", "Main" }; 18 | Integer UpgradeRenderTargets{ "UpgradeRenderTargets", "Main" }; 19 | 20 | Integer FrameBufferFormat{ "FrameBufferFormat", "HDR" }; 21 | 22 | String RenderTargetsToUpgrade{ "RenderTargetsToUpgrade", "RenderTargets" }; 23 | 24 | void Load() noexcept; 25 | 26 | private: 27 | TomlConfig config = COMPILE_PROXY("NativeAutoHDR.toml"sv); 28 | }; 29 | } 30 | ``` 31 | 32 | ```cpp [Settings.cpp] 33 | #include "Settings.h" 34 | 35 | namespace Settings 36 | { 37 | void Main::Load() noexcept 38 | { 39 | static std::once_flag ConfigInit; 40 | std::call_once(ConfigInit, [&]() { 41 | config.Bind(ImageSpaceBufferFormat, 0); 42 | config.Bind(UpgradeUIRenderTarget, true); 43 | config.Bind(UpgradeRenderTargets, 2); 44 | config.Bind(FrameBufferFormat, 0); 45 | config.Bind(RenderTargetsToUpgrade, "SF_ColorBuffer", "HDRImagespaceBuffer", "ImageSpaceHalfResBuffer", "ImageProcessColorTarget", "ImageSpaceBufferB10G11R11", "ImageSpaceBufferE5B9G9R9", "TAA_idTech7HistoryColorTarget", "EnvBRDF", "ImageSpaceBufferR10G10B10A2"); 46 | }); 47 | 48 | config.Load(); 49 | 50 | INFO("Config loaded"sv) 51 | } 52 | } 53 | ``` 54 | 55 | ```cpp [Usage] 56 | const auto settings = Settings::Main::GetSingleton(); 57 | 58 | if (*settings->FrameBufferFormat == 2) { // [!code focus] 59 | SetBufferFormat(RE::Buffers::FrameBuffer, RE::BS_DXGI_FORMAT::BS_DXGI_FORMAT_R16G16B16A16_FLOAT); 60 | } else { 61 | SetBufferFormat(RE::Buffers::FrameBuffer, RE::BS_DXGI_FORMAT::BS_DXGI_FORMAT_R10G10B10A2_UNORM); 62 | } 63 | 64 | switch (*settings->ImageSpaceBufferFormat) { // [!code focus] 65 | case 1: 66 | SetBufferFormat(RE::Buffers::ImageSpaceBuffer, RE::BS_DXGI_FORMAT::BS_DXGI_FORMAT_R10G10B10A2_UNORM); 67 | break; 68 | case 2: 69 | SetBufferFormat(RE::Buffers::ImageSpaceBuffer, RE::BS_DXGI_FORMAT::BS_DXGI_FORMAT_R16G16B16A16_FLOAT); 70 | break; 71 | } 72 | 73 | if (*settings->UpgradeUIRenderTarget) { // [!code focus] 74 | SetBufferFormat(RE::Buffers::ScaleformCompositeBuffer, RE::BS_DXGI_FORMAT::BS_DXGI_FORMAT_R16G16B16A16_FLOAT); 75 | } 76 | ``` 77 | 78 | ::: 79 | -------------------------------------------------------------------------------- /docs/config/snippets/YouCanSleepRemake.md: -------------------------------------------------------------------------------- 1 | [YouCanSleepRemake](https://github.com/gottyduke/YouCanSleepRemake) 2 | 3 | ::: code-group 4 | 5 | ```cpp [Config.h] 6 | #pragma once 7 | #include "DKUtil/Config.hpp" 8 | 9 | namespace Config 10 | { 11 | using namespace DKUtil::Alias; 12 | 13 | extern Boolean EnableSleepWait[8]; 14 | 15 | void Load(); 16 | } 17 | ``` 18 | 19 | ```cpp [Config.cpp] 20 | #include "Config.h" 21 | 22 | namespace Config 23 | { 24 | Boolean EnableSleepWait[8] = { 25 | { "InAir" }, 26 | { "Trespassing" }, 27 | { "AskedToLeave" }, 28 | { "GuardsPursuing" }, 29 | { "EnemiesNearby" }, 30 | { "TakingHealthDamage" }, 31 | { "Owned" }, 32 | { "InUse" }, 33 | }; 34 | 35 | void Load() 36 | { 37 | auto main = COMPILE_PROXY("YouCanSleepRemake.toml"sv); 38 | 39 | for (auto index = 0; index < std::extent_v; ++index) { 40 | main.Bind(EnableSleepWait[index], true); 41 | } 42 | 43 | main.Load(); 44 | 45 | INFO("Config Loaded"sv); 46 | } 47 | } 48 | ``` 49 | 50 | ```cpp [Usage] 51 | std::ptrdiff_t OffsetTbl[8] = { 52 | 0x2E, // You cannot sleep in the air. 53 | 0x89, // You cannot sleep while trespassing. 54 | 0xB1, // You cannot sleep while being asked to leave. 55 | 0xF6, // You cannot sleep while guards are pursuing you. 56 | 0x11F, // You cannot sleep when enemies are nearby. 57 | 0x146, // You cannot sleep while taking health damage. 58 | 0x1BB, // This object is already in use by someone else. 59 | REL::Relocate(0x3BC, 0x3C0) // You cannot sleep in an owned bed. 60 | }; 61 | 62 | const auto funcAddr = DKUtil::Hook::IDToAbs(AE_FuncID, SE_FuncID); 63 | 64 | for (auto index = 0; index < std::extent_v; ++index) { 65 | if (*Config::EnableSleepWait[index]) { // [!code focus] 66 | DKUtil::Hook::WriteImm(funcAddr + OffsetTbl[index], JmpShort); // [!code focus] 67 | INFO("Enabled SleepWait {} at 0x{:X}", Config::EnableSleepWait[index].get_key(), OffsetTbl[index]); // [!code focus] 68 | } 69 | } 70 | 71 | // loop check for in air position 72 | if (*Config::EnableSleepWait[0]) { // [!code focus] 73 | DKUtil::Hook::WriteData(funcAddr + InAirLoopOffset, &InAirHookNop, sizeof(InAirHookNop)); 74 | } 75 | 76 | INFO("Hooks installed"sv); 77 | ``` 78 | 79 | ::: 80 | -------------------------------------------------------------------------------- /docs/dkutil/about.md: -------------------------------------------------------------------------------- 1 | # DKUtil 2 | 3 | Started as a helper header for personal projects, improved on demand with more and more features. 4 | 5 | **platform**: Windows 6 | **standard**: c++20 7 | 8 | ## Usage Requirement 9 | 10 | + [CMake](https://cmake.org) 11 | + [vcpkg](https://github.com/microsoft/vcpkg/releases) 12 | + `/std:c++20` or `/std:latest` 13 | + Config 14 | + [SimpleIni](https://github.com/brofield/simpleini) 15 | + [nlohmann-json](https://github.com/nlohmann/json) 16 | + Hook 17 | + [xbyak](https://github.com/herumi/xbyak) 18 | + Logger 19 | + [spdlog](https://github.com/gabime/spdlog) 20 | + Extra(For SKSE) 21 | + [CommonLibSSE](https://github.com/Ryan-rsm-McKenzie/CommonLibSSE) 22 | 23 | ::: warning Dependencies 24 | All dependencies will be handled by vcpkg & CMake. 25 | ::: 26 | 27 | ## Installation 28 | 29 | ::: code-group 30 | ```ps [local copy] 31 | git clone https://github.com/gottyduke/DKUtil.git 32 | ``` 33 | 34 | ```ps [git submodule] 35 | git submodule add https://github.com/gottyduke/DKUtil.git extern/DKUtil 36 | git submodule update --init --recursive -f 37 | git submodule update --remote -f 38 | ``` 39 | ::: 40 | 41 | ::: tip Environment 42 | Adding path to DKUtil to environment variable `DKUtilPath` can help with consuming the library. 43 | ::: 44 | 45 | ::: info PluginTemplate 46 | If this is your first time using DKUtil and starting a fresh plugin project, consider using [PluginTemplate](https://github.com/gottyduke/plugintemplate) and skip DKUtil setup process. 47 | ::: 48 | 49 | ## Consume in Projects 50 | 51 | ### CMake 52 | 53 | Add to your target project's `CMakeLists.txt`: 54 | 55 | ::: code-group 56 | ```CMake [local tree] 57 | add_subdirectory("Path/To/DKUtil" DKUtil) 58 | ``` 59 | 60 | ```CMake{33} [global dependency] 61 | # dependency macros 62 | macro(find_dependency_path DEPENDENCY FILE) 63 | # searches extern for dependencies and if not checks the environment variable 64 | if(NOT ${DEPENDENCY} STREQUAL "") 65 | # Check extern 66 | message( 67 | STATUS 68 | "Searching for ${DEPENDENCY} using file ${FILE}" 69 | ) 70 | find_path("${DEPENDENCY}Path" 71 | ${FILE} 72 | PATHS "extern/${DEPENDENCY}" 73 | ) 74 | 75 | if("${${DEPENDENCY}Path}" STREQUAL "${DEPENDENCY}Path-NOTFOUND") 76 | # Check path 77 | message( 78 | STATUS 79 | "Getting environment for ${DEPENDENCY}Path: $ENV{${DEPENDENCY}Path}" 80 | ) 81 | set("${DEPENDENCY}Path" "$ENV{${DEPENDENCY}Path}") 82 | endif() 83 | 84 | message( 85 | STATUS 86 | "Found ${DEPENDENCY} in ${${DEPENDENCY}Path}; adding" 87 | ) 88 | add_subdirectory("${${DEPENDENCY}Path}" ${DEPENDENCY}) 89 | endif() 90 | endmacro() 91 | 92 | # find DKUtil with environment DKUtilPath or "extern/DKUtil" 93 | find_dependency_path(DKUtil include/DKUtil/Logger.hpp) 94 | ``` 95 | ::: 96 | 97 | ### VCPKG Port 98 | 99 | Planned, low priority due to no demand 100 | -------------------------------------------------------------------------------- /docs/dkutil/compilation.md: -------------------------------------------------------------------------------- 1 | # Compilation 2 | 3 | DKUtil is a header only library (so far), when incorporated into CMake projects, header only libraries (a.k.a INTERFACE target) won't be visible in generated msvc solution. 4 | 5 | There are sneaky build config to enable DKUtilDebugger target in CMake, however, currently it requires SKSE workspace set up. If you are not a user of [SKSEPlugins](https://github.com/gottyduke/SKSEPlugins), it will be difficult to build this way. 6 | 7 | ```ps 8 | cmake -B build -S . --preset=REL -DPLUGIN_MODE:BOOL=TRUE -DDKUTIL_DEBUG_BUILD:BOOL=TRUE 9 | ``` -------------------------------------------------------------------------------- /docs/dkutil/references.md: -------------------------------------------------------------------------------- 1 | # External References 2 | 3 | Open source game plugin projects that utilize DKUtil functions! DKUtil improves with these amazing developers' feedback and contributions. 4 | 5 | ## Baldur's Gate 3: 6 | 7 | + [BG3WASD](https://github.com/Ch4nKyy/BG3WASD) 8 | + [BG3_AchievementEnabler](https://github.com/gottyduke/BG3_AchievementEnabler) 9 | + [BG3_NativeCameraTweaks](https://github.com/ersh1/BG3_NativeCameraTweaks) 10 | 11 | ## Skyrim Special Edition: 12 | 13 | + [Armor Rating Rescaled SKSE](https://github.com/gottyduke/ArmorRatingRescaledRemake) 14 | + [BehaviorDataInjector](https://github.com/max-su-2019/BehaviorDataInjector) 15 | + [CombatPathingRevolution](https://github.com/max-su-2019/CombatPathingRevolution) 16 | + [Individual Shout Cooldown](https://github.com/gottyduke/IndividualShoutCooldownRemake) 17 | + [MaxsuDetectionMeter](https://github.com/max-su-2019/MaxsuDetectionMeter) 18 | + [No Enchantment Restriction SKSE](https://github.com/gottyduke/NoEnchantmentRestrictionRemake) 19 | + [Plugin Tutorial CN](https://github.com/gottyduke/PluginTutorialCN) 20 | + [SCAR](https://github.com/max-su-2019/SCAR) 21 | + [SKSEPlugins](https://github.com/gottyduke/SKSEPlugins) 22 | + [Speed Casting SKSE](https://github.com/gottyduke/SpeedCastingRemake) 23 | + [StaggerEffectFix](https://github.com/max-su-2019/StaggerEffectFix) 24 | + [You Can Sleep SKSE](https://github.com/gottyduke/YouCanSleepRemake) 25 | + ... 26 | 27 | ## Starfield: 28 | 29 | + [SF_NativeAutoHDR](https://github.com/ersh1/SF_NativeAutoHDR) 30 | + [Starfield-Luma](https://github.com/EndlesslyFlowering/Starfield-Luma) 31 | + [StrippingArmor](https://github.com/cotyounoyume/StrippingArmor) 32 | + [ClassicSprintingStarfieldv2](https://github.com/MCPC10/ClassicSprintingStarfieldv2) 33 | + [BakaAchievementEnabler](https://github.com/shad0wshayd3/BakaAchievementEnabler) 34 | + [SF_PluginTemplate](https://github.com/gottyduke/SF_PluginTemplate) 35 | + [BakaKillMyGames](https://github.com/shad0wshayd3/BakaKillMyGames) 36 | + [BakaQuickFullSaves](https://github.com/shad0wshayd3/BakaQuickFullSaves) 37 | + [BakaQuitGameFix](https://github.com/shad0wshayd3/BakaQuitGameFix) 38 | + [Disable-Dialogue-Camera](https://github.com/AntoniX35/Disable-Dialogue-Camera) 39 | + [UltimateBooster](https://github.com/cotyounoyume/UltimateBooster) 40 | + [SFSE-Save-Unfinished-Ships](https://github.com/kvnmtz/SFSE-Save-Unfinished-Ships) 41 | + [Sprint-Stuttering-Fix](https://github.com/AntoniX35/Sprint-Stuttering-Fix) 42 | + [Weapon-Swap-Stuttering-Fix](https://github.com/AntoniX35/Weapon-Swap-Stuttering-Fix) 43 | + [NonPlayableUnlockerSFSE](https://github.com/cotyounoyume/NonPlayableUnlockerSFSE) 44 | + ... 45 | 46 | ## Others: 47 | 48 | + [MHWI_CNTextColor](https://github.com/gottyduke/MHW-I_CNTextColor) 49 | 50 | ... and many more from the thriving community. -------------------------------------------------------------------------------- /docs/extra/skse.md: -------------------------------------------------------------------------------- 1 | # DKUtil::Extra 2 | 3 | On expanding small additions that requires `CommonLibSSE` to function. 4 | + `CONSOLE` logging macro but for in-game console. 5 | + `serializable` painless, all-in-one serialization solution for SKSE plugins. 6 | 7 | ## CONSOLE 8 | logging macro for in game console 9 | ```C++ 10 | CONSOLE("{} {} {}", a, b, c); 11 | CONSOLE("This will be logged to in game console", a, b, c); 12 | ``` 13 | 14 | ## serializable 15 | All in one, painless serialization solution. All serializables are automatically registered to internal handler, and unregistered upon destruction(although probably not needed). All reasonable data types are supported, they are flattened first with internal resolvers, then serialized with SKSE. 16 | Common types: 17 | + STL containers 18 | + user defined aggregate types 19 | + all trivial types 20 | + string 21 | 22 | ### declaration 23 | Wrap the data type in `dku::serializable<>`, with the second template parameter giving it a unique hash identifier. This identifier is used in the future for variant updating/backwards compatibility feature. 24 | ```C++ 25 | struct MyDataType 26 | { 27 | int a; 28 | bool b; 29 | char c; 30 | std::string s; 31 | std::vector arr; 32 | }; 33 | using MyComplexDataType = std::map; 34 | 35 | // empty 36 | dku::serializable mySimpleData; 37 | // or initializer list 38 | dku::serializable mySimpleData( 39 | {1, true, 'a', "Elo", {1,2,3,4,5}}); 40 | // or assignment 41 | dku::serializable mySimpleData = MyDataType{ 42 | {1, true, 'a', "Elo", {1,2,3,4,5}}}; 43 | 44 | dku::serializable myDataMap; 45 | // ... 46 | ``` 47 | 48 | ### using, modifying 49 | The data can be accessed by prepending the asterisk symbol `*` or using arrow operator `->` as if it were a pointer type, or the getter method `get()`. 50 | ```C++ 51 | for (auto& [id, data] : *myDataMap) { 52 | // ... 53 | } 54 | 55 | myDataMap->emplace(...); 56 | ``` 57 | 58 | ### registering 59 | During plugin load, simply call it once and you are done: 60 | ```C++ 61 | DLLEXPORT bool SKSEAPI SKSEPlugin_Load(const SKSE::LoadInterface* a_skse) 62 | { 63 | DKUtil::Logger::Init(Plugin::NAME, REL::Module::get().version().string()); 64 | 65 | INFO("{} v{} loaded", Plugin::NAME, Plugin::Version); 66 | 67 | // this 68 | dku::serialization::api::RegisterSerializable(); 69 | 70 | return true; 71 | } 72 | ``` 73 | You can also pass in optional plugin identifier, although the internal hash function already makes a unique id. 74 | 75 | ### custom resolver 76 | You can attach custom resolver callbacks to the `dku::serializable<>` types and they'll be called during `Save`, `Load`, `Revert` state, **after** they are resolved. 77 | ```C++ 78 | dku::serializable myDataMap; 79 | using type = decltype(myDataMap)::type; 80 | 81 | // resolver function takes its underlying data 82 | void CustomCallback(type& a_data, dku::serialization::ResolveOrder a_order) 83 | { 84 | if (a_order == dku::serialization::ResolveOrder::kSave) { 85 | INFO("Saved"); 86 | } else if (a_order == dku::serialization::ResolveOrder::kLoad) { 87 | INFO("Loaded"); 88 | for (auto& [f, d] : a_data) { 89 | INFO("{} {}", f, d.s); 90 | } 91 | } else { 92 | INFO("Reverted"); 93 | } 94 | } 95 | 96 | myDataMap.add_resolver(CustomCallback); 97 | ``` 98 | 99 | ### flattened layout map 100 | Tweak the preprocessor definitions a bit, define `DKU_X_MOCK` will redirect all read/write calls to its internal buffer for testing, and generate a flattened layout map of the data type it's operating on. 101 | 102 | --- 103 | -------------------------------------------------------------------------------- /docs/hooks/address-fetching.md: -------------------------------------------------------------------------------- 1 | # Address Fetching 2 | 3 | `DKUtil::Hook` provides some address fetching helpers at runtime. 4 | 5 | ## Pattern Scan 6 | 7 | To do a pattern scan using KMP, given string pattern: 8 | 9 | ```cpp 10 | std::string pattern{ "40 57 48 83 EC 30 48 8B 0D ?? ?? ?? ??" }; 11 | ``` 12 | 13 | ### Syntax 14 | 15 | ```cpp 16 | void* search_pattern( 17 | std::string_view pattern, 18 | std::uintptr_t base = 0, 19 | std::size_t size = 0 20 | ); 21 | ``` 22 | 23 | ### Parameter 24 | 25 | + `pattern` : string pattern to search, spacing is optional, each byte must be two characters. 26 | + `base` : address of memory block to search, defaults to module.textx. 27 | + `size` : size of memory block to search, defaults to module.textx.size. 28 | 29 | Returns `nullptr` if not found. Otherwise return the first match. 30 | 31 | ### Linear Search 32 | 33 | To use pattern at compile time, use specialized template version: 34 | 35 | ```cpp 36 | void* search_pattern<"40 57 48 83 EC 30 48 8B 0D ?? ?? ?? ??">(base = 0, size = 0); 37 | ``` 38 | 39 | This template version performs a linear search instead of default KMP. 40 | 41 | ## Rip Addressing 42 | 43 | To get the actual address of a rip-relative displacement used in an instruction. 44 | 45 | Given target assembly: 46 | 47 | ```asm 48 | 0x141234567: call [rip + 0x30] 49 | 0x14123456D: lea rax, ds: [rip + 0x1110] 50 | 0x141234574: mov rax, ds: [rip + 0x114514] 51 | ``` 52 | 53 | We want the final address of these rip displacements: 54 | 55 | ```cpp 56 | std::uintptr_t funcAddr = dku::Hook::GetDisp(0x141234567); 57 | auto actorSingleton = dku::Hook::GetDisp(0x14123456D); 58 | bool significance = *dku::Hook::GetDisp(0x141234574); 59 | ``` 60 | 61 | ## Adjust Pointer 62 | 63 | Offset a pointer with type cast. 64 | 65 | ```cpp 66 | // read bool member value at 0x220 from a class pointer 67 | auto& member = *dku::Hook::adjust_pointer(actor, 0x220); 68 | ``` 69 | 70 | ## Module IAT 71 | 72 | Get import address of method in a library loaded by module. 73 | 74 | ```cpp 75 | void* GetImportAddress( 76 | std::string_view moduleName, 77 | std::string_view libraryName, 78 | std::string_view importName) 79 | ``` 80 | 81 | ## Class VTable 82 | 83 | Get the address of n-th function in class virtual function table. 84 | 85 | ```cpp 86 | size_t n = 0x8; // get 8th function 87 | Actor* actor = new Actor(); // class pointer, also vptr 88 | auto func = dku::Hook::TblToAbs(actor, n); 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/hooks/asm-patch.md: -------------------------------------------------------------------------------- 1 | # Assembly Patch 2 | 3 | Replace a block of target memory with patch. 4 | 5 | ## Syntax 6 | 7 | ```cpp 8 | ASMPatchHandle AddASMPatch( 9 | std::uintptr_t address, 10 | offset_pair offsets, 11 | Patch* patch, 12 | bool forward = true 13 | ); 14 | ``` 15 | 16 | ## Paramter 17 | 18 | + `address` : target instruction address. 19 | + `offsets` : pair containing the {begin, end} offsets from target instruction to patch. 20 | + `patch` : pointer to the memory patch data structure(see also: [patch structure](memory-edit#patch-structure)). 21 | + `forward` : **optional**, whether to skip the rest of `NOP` fillers. 22 | 23 | ## HookHandle 24 | 25 | A `ASMPatchHandle` object will be returned: 26 | 27 | ```cpp 28 | class ASMPatchHandle 29 | { 30 | const offset_pair Offset; 31 | const std::size_t PatchSize; 32 | std::vector OldBytes{}; 33 | std::vector PatchBuf{}; 34 | }; 35 | ``` 36 | 37 | ## Example 38 | 39 | Prepare the hook: 40 | 41 | ```cpp 42 | using namespace DKUtil::Alias; 43 | 44 | std::uintptr_t mem_addr = 0x7FF712345678; 45 | // or offset from module base 46 | std::uintptr_t mem_addr = dku::Hook::Module::get().base() + 0x345678; 47 | ``` 48 | 49 | Mark the begin and the end of target code region to patch: 50 | 51 | ```cpp 52 | // starts at mem_addr + 0x120 53 | // ends at mem_addr + 0x130 54 | // entire memory region size to patch is 0x10 55 | auto offset = std::make_pair(0x120, 0x130); 56 | ``` 57 | 58 | ::: tip Offset Pair 59 | Sometimes your patch begins at your memory address, which is a pair of `{0x0, size}`. 60 | ::: 61 | 62 | Commit the hook: 63 | 64 | ::: code-group 65 | 66 | ```cpp [Raw Patch] 67 | OpCode AsmSrc[]{ 68 | 0xB8, // mov eax, 69 | 0x00, 0x00, 0x00, 0x00, // Imm32 70 | 0x89, 0XC1, // mov ecx, eax 71 | }; 72 | 73 | auto Hook = DKUtil::Hook::AddASMPatch(funcAddr, offset, { &AsmPatch, sizeof(AsmSrc) }); 74 | Hook->Enable(); 75 | ``` 76 | 77 | ```cpp [DKUtil Patch] 78 | Patch AsmSrc{ 79 | "\xB8" // mov eax 80 | "\x00\x00\x00\x00" // Imm32 81 | "\x89\xC1", // mov ecx eax 82 | 7 83 | }; 84 | 85 | auto Hook = DKUtil::Hook::AddASMPatch(funcAddr, offset, &AsmSrc); 86 | Hook->Enable(); 87 | ``` 88 | 89 | ```cpp [Xbyak] 90 | struct ChangeEcxPatch : 91 | public Xbyak::CodeGenerator 92 | { 93 | ChangeEcxPatch() 94 | { 95 | mov(eax, static_cast(0x0)); 96 | mov(ecx, eax); 97 | } 98 | }; 99 | 100 | ChangeEcxPatch patch{}; 101 | patch.ready(); 102 | 103 | auto Hook = DKUtil::Hook::AddASMPatch(funcAddr, offset, &patch); 104 | Hook->Enable(); 105 | ``` 106 | 107 | ::: 108 | 109 | ## Auto Trampoline 110 | 111 | If the given target memory region size defined by `offsets` is less than the size of assembly patch, a trampoline will be utilized to fulfill the patch and setup the auto detour/return. This action requires a minimal target memory space of `0x5`. 112 | -------------------------------------------------------------------------------- /docs/hooks/callsite-logging.md: -------------------------------------------------------------------------------- 1 | # Callsite Logging 2 | 3 | Sometimes during plugin development, you may want to track down callers to specific function during runtime, and especially when it's a virtual function/member function, that makes it even harder to find. 4 | 5 | Here's a common example of intercepting a virtual function at runtime and log its callers(more specifically, its return address): 6 | 7 | ## Target Function 8 | 9 | ```cpp 10 | void TESObjectREFR::SetStartingPosition(const NiPoint3& a_pos); // 0x54 in vtbl 11 | // this function is a virtual member function of class TESObjectREFR, 12 | // first we need to translate it to __cdecl function: 13 | void SetStartingPosition(TESObjectREFR* a_this, const NiPoint3& a_pos); 14 | // per x64 calling convention, *this pointer is implicitly passed as first argument 15 | ``` 16 | 17 | ## Target Assembly 18 | 19 | ```asm 20 | 40:56 | push rsi 21 | 57 | push rdi 22 | 41:56 | push r14 23 | ``` 24 | 25 | We only need 5 bytes minimum to setup a cave hook, so these three lines are sufficient. 26 | 27 | ## Example 28 | 29 | ```cpp 30 | using namespace DKUtil::Alias; 31 | 32 | // assume we acquired an instance of the class TESObjectREFR 33 | TESObjectREFR* refr_instance = 0x123456; 34 | 35 | class SetStartingPositionEx : 36 | public Xbyak::CodeGenerator 37 | { 38 | // because vptr is at 0x0 of class instance, so a pointer to class is a pointer to vptr 39 | inline static std::uintptr_t* vtbl{ *std::bit_cast(refr_instance) }; 40 | // the index of the target virtual function we want to log in vtbl 41 | inline static std::size_t index{ 0x54 }; 42 | 43 | // actual logging function, we have added third argument, it's the return address/callsite 44 | static void Intercept_SSP( 45 | TESObjectREFR* a_this, 46 | const RE::NiPoint3& a_pos, 47 | std::uintptr_t a_caller = 0) // [!code ++] 48 | { 49 | INFO("ret 0x{:X}", dku::Hook::GetRawAddress(a_caller)); 50 | } 51 | 52 | public: 53 | SetStartingPositionEx() 54 | { 55 | // move the return address on stack[rsp] to third argument, as per x64 calling convention 56 | mov(r8, qword[rsp]); 57 | } 58 | 59 | static void Install() 60 | { 61 | SetStartingPositionEx sse{}; 62 | sse.ready(); 63 | 64 | // generates prolog & epilog patches that preserve register values, 65 | // so our logging function won't break anything 66 | auto [ppatch, epatch] = dku::Hook::JIT::MakeNonVolatilePatch( 67 | { 68 | dku::Hook::Register::ALL 69 | }); 70 | 71 | // actual prolog patch 72 | Patch prolog; 73 | // first we move the third argument 74 | prolog.Append(sse); 75 | // then apply the preserve patch 76 | prolog.Append(ppatch); 77 | // nothing extra to add for epilog patch, because we didn't break stack balance 78 | 79 | // target function is at index 0x54 in vtbl 80 | auto addr = vtbl[index]; 81 | auto hook = dku::Hook::AddCaveHook( 82 | addr, 83 | { 0, 5 }, 84 | FUNC_INFO(Intercept_SSP), 85 | &prolog, 86 | &epatch, 87 | // important that we restore original function prolog(the 5 bytes we took) after returning from logger 88 | HookFlag::kRestoreAfterEpilog); 89 | hook->Enable(); 90 | } 91 | }; 92 | ``` 93 | 94 | ## Custom Prolog/Epilog 95 | 96 | When composing arguments for custom cave functions, do follow [x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170). 97 | -------------------------------------------------------------------------------- /docs/hooks/cave-hook.md: -------------------------------------------------------------------------------- 1 | # CaveHook 2 | 3 | Detour to hook function from target memory. 4 | 5 | ## Syntax 6 | 7 | ```cpp 8 | CaveHookHandle AddCaveHook( 9 | std::uintptr_t address, 10 | offset_pair offset, 11 | FuncInfo funcInfo, 12 | Patch* prolog = nullptr, 13 | Patch* epilog = nullptr, 14 | HookFlag flag = HookFlag::kSkipNOP 15 | ); 16 | ``` 17 | 18 | ## Parameter 19 | 20 | + `address` : target instruction address. 21 | + `offsets` : pair containing the {begin, end} offsets from target instruction to patch. 22 | + `funcInfo` : FUNC_INFO macro wrapper of hook function. 23 | + `prolog` : **optional**, memory patch **before** detouring to hook function, `nullptr` if none. 24 | + `epilog` : **optional**, memory patch **after** returning from hook function, `nullptr` if none. 25 | + `flag` : **optional**, special flag for cave hook operation. 26 | 27 | ## HookFlag 28 | 29 | ```cpp 30 | enum class HookFlag 31 | { 32 | kNoFlag = 0, // default 33 | 34 | kSkipNOP = 1u << 0, // skip NOPs 35 | kRestoreBeforeProlog = 1u << 1, // apply stolens before prolog 36 | kRestoreAfterProlog = 1u << 2, // apply stolens after prolog 37 | kRestoreBeforeEpilog = 1u << 3, // apply stolens before epilog 38 | kRestoreAfterEpilog = 1u << 4, // apply stolens after epilog 39 | }; 40 | ``` 41 | 42 | You can set multiple flags by passing `{HookFlag, HookFlag, ...}`. 43 | 44 | ## HookHandle 45 | 46 | A `CaveHookHandle` object will be returned: 47 | 48 | ```cpp 49 | class CaveHookHandle 50 | { 51 | const offset_pair Offset; 52 | const std::size_t CaveSize; 53 | const std::uintptr_t CaveEntry; 54 | std::uintptr_t CavePtr{ 0x0 }; 55 | std::vector OldBytes{}; 56 | std::vector CaveBuf{}; 57 | }; 58 | ``` 59 | 60 | ## Workflow 61 | 62 | CaveHook will first fill the target block of memory defined by `address` + `{offset_pair}` with `NOP`, then write its detour into trampoline, which then applies user prolog patch(if any), detour to user function, applies user epilog patch(if any), then return to original block. Original bytes of target block can will be applied depending on the `HookFlag` parameter(if set). 63 | 64 | ## Example 65 | 66 | ```cpp 67 | using namespace DKUtil::Alias; 68 | 69 | // hook function 70 | float Hook_MyAwesomeFunc(int a_awesomeInt) { 71 | // do awesome stuff 72 | return static_cast(a_awesomeInt); 73 | } 74 | 75 | std::uintptr_t funcAddr = 0x7FF712345678; 76 | // or offset from module base 77 | std::uintptr_t funcAddr = dku::Hook::Module::get().base() + 0x345678; 78 | 79 | // mark the begin and the end of target code to patch 80 | // starts at funcAddr + 0x120 81 | // ends at funcAddr + 0x130 82 | auto offset = std::make_pair(0x120, 0x130); 83 | 84 | // this is DKUtil::Hook::Patch, you can also use xbyak or raw patch 85 | // move return value to xmm3 86 | DKUtil::Hook::Patch Epilog = { 87 | "\x0F\x10\xD8", // movups xmm3, xmm0 88 | 0x3 // size of patch 89 | }; 90 | 91 | auto Hook_MAF = dku::Hook::AddCaveHook( 92 | funcAddr, 93 | offset, 94 | FUNC_INFO(Hook_MyAwesomeFunc), 95 | nullptr, 96 | &Epilog, 97 | dku::HookFlag::kRestoreAfterEpilog); 98 | 99 | Hook_MAF->Enable(); 100 | ``` 101 | 102 | ::: details Workflow for This Example 103 | Under the hood it'll `NOP` all bytes from `funcAddr + 0x120` to `funcAddr + 0x130`, set a branch call to `Hook_MyAwesomeFunc`, apply custom epilog patch, apply original bytes taken from 0x120 to 0x130(`kRestoreAfterEpilog`), then finally return to `funcAddr + 0x130`. 104 | ::: 105 | 106 | ## Custom Prolog/Epilog 107 | 108 | When composing arguments for custom cave functions, do follow [x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170). 109 | -------------------------------------------------------------------------------- /docs/hooks/hook-handles.md: -------------------------------------------------------------------------------- 1 | # Hook Handles 2 | 3 | `HookHandle` class is the base object of a successful `DKUtil::Hook` operation, and it's used to access the hook specific data and enable/disable the hook. 4 | 5 | ```cpp 6 | class HookHandle 7 | { 8 | const std::uintptr_t Address; 9 | const std::uintptr_t TramEntry; 10 | std::uintptr_t TramPtr{ 0x0 }; 11 | }; 12 | ``` 13 | 14 | ## Control 15 | 16 | `HookHandle` can be used to enable/disable a hook. 17 | 18 | ```cpp 19 | HookHandle handle = SomeDKUtilHookAPI(); 20 | 21 | // hook control 22 | handle->Enable(); 23 | handle->Disable(); 24 | ``` 25 | 26 | ## Derived Cast 27 | 28 | To cast into derived types of specific hook API: 29 | 30 | ```cpp 31 | HookHandle handle = SomeDKUtilHookAPI(); 32 | ASMPatchHandle asmHandle = handle->As(); 33 | ``` 34 | 35 | This is useful when you only declare a base `HookHandle` object before commiting any hook operation, then downcast it. 36 | 37 | For related information, see each API's own section. 38 | 39 | ## Internal Trampoline 40 | 41 | You can write data directly to `HookHandle` internal `TramPtr`, this normally points to next available memory address in trampoline. However, unless you know what you are doing, you shouldn't be doing it. 42 | 43 | ```cpp 44 | HookHandle handle = SomeDKUtilHookAPI(); 45 | handle->Write(0x100); // write Imm32 0x100 in trampoline 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/hooks/hooking-with-dkutil.md: -------------------------------------------------------------------------------- 1 | # DKUtil::Hook 2 | 3 | A hook module that DKUtil project originally started with. 4 | 5 | ## Usage 6 | 7 | The entire module is behind the namespace `DKUtil::Hook` or `dku::Hook`. To use, include the header: 8 | 9 | ```cpp 10 | #include "DKUtil/Hook.hpp" 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/hooks/iat-swap.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottyduke/DKUtil/31d0bdf36d0c3979c346e6f20735f2cbb763d419/docs/hooks/iat-swap.md -------------------------------------------------------------------------------- /docs/hooks/lto-enabled-hook.md: -------------------------------------------------------------------------------- 1 | # LTO.what() 2 | 3 | When the target program has interprocedural optimizations enabled for their builds, it often translates to link time optimizations, and for MSVC mainstream, it's `/LTCG` and `/GL`, a.k.a. link time code generation and whole program optimizations. This technique feeds the compiler with extra information that allows it make interprocedural optimizations. 4 | 5 | How does it affect our hooking methods and common approaches? 6 | 7 | For MSVC x64 windows targets, we hook by complying with [x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention). This is under the assumptions that caller handles the shadow stack and volatile register are safe to use with callee. 8 | 9 | The x64 MSVC ABI considers `rax`, `rcx`, `rdx`, `r8`, `r9`, `r10`, `r11` and `xmm0` to `xmm5` as volatile. 10 | 11 | ## LTO.why() 12 | 13 | Consider the following call example: 14 | 15 | ```asm 16 | mov rdx, [rcx] // a2 17 | mov rcx, r8 // use r8 value here as a1 18 | call my_func(rcx, rdx) // call boundary 19 | mov r8, 0x100 // using r8 as a free register now 20 | ``` 21 | 22 | After returning from `my_func`, compiler will consider all volatile registers value changed, thus compiler will not reuse `rcx` or `rdx` registers. 23 | 24 | **However**, with LTO enabled targets, it may look like this: 25 | 26 | ```asm 27 | mov rdx, [rcx] // a2 28 | mov rcx, r8 // use r8 value here as a1 29 | call my_func(rcx, rdx) // call boundary 30 | test r8, r8 // keep using r8 31 | ``` 32 | 33 | With the PGO information from linker, compiler knows that `my_func` did not change `r8`, or it can be optimized to not change `r8`, so compiler lets the caller use `r8` across the call boundary. This effectively reduces register preserving/stack usage, thus optimizations. 34 | 35 | When implementing the hook functions for our plugins, normally there's no way of knowing if all the hooks will change specific registers, and that information cannot be accounted for when dealing with LTO targets. 36 | 37 | ## Syntax 38 | 39 | Currently, `dku::Hook` offers `write_call` variant, `write_call_ex`. This API is designated for preserving regular/sse registers across a hook call boundary and keeping the original LTO code executing. 40 | 41 | ```cpp 42 | auto write_call_ex( 43 | const dku_memory auto src, 44 | F dst, 45 | enumeration regs = { Register::NONE }, 46 | enumeration simd = { SIMD::NONE } 47 | ); 48 | ``` 49 | 50 | ## Parameter 51 | 52 | + `src` : address of the target callsite 53 | + `dst` : hook function 54 | + `regs` : regular registers to preserve as non volatile 55 | + `simd` : sse registers to preserve as non volatile 56 | 57 | You can set multiple registers by passing `{Register, Register, ...}`. 58 | 59 | ## Example 60 | 61 | ```cpp{17,20,23,26} 62 | using namespace DKUtil::Alias; 63 | 64 | // hook function 65 | bool Hook_123456(void* a_gameInstance) 66 | { 67 | return func(a_gameInstance); 68 | } 69 | 70 | // original function 71 | static inline std::add_pointer_t func; 72 | 73 | // callsite 74 | auto addr = 0x7FF712345678; 75 | 76 | // preserve rdx, r9 77 | func = dku::Hook::write_call_ex<5>(addr, Hook_123456, 78 | { Reg::RDX, Reg::R9 }); 79 | // preserve xmm0, xmm2 80 | func = dku::Hook::write_call_ex<5>(addr, Hook_123456, 81 | { Reg::NONE }, { Xmm::XMM0, Xmm::XMM2 }); 82 | // preserve rdx, r9, and xmm0, xmm2 83 | func = dku::Hook::write_call_ex<5>(addr, Hook_123456, 84 | { Reg::RDX, Reg::R9 }, { Xmm::XMM0, Xmm::XMM2 }); 85 | // preserve all 86 | func = dku::Hook::write_call_ex<5>(addr, Hook_123456, 87 | { Reg::ALL }, { Xmm::ALL }); 88 | ``` 89 | 90 | ## Non-Volatile Patch 91 | 92 | Sometimes you may not need to commit a `write_call_ex` hook, but could use some boilerplate assemblies for preserving registers and restoring them. 93 | 94 | ```cpp 95 | auto [prolog, epilog] = dku::Hook::JIT::MakeNonVolatilePatch({Reg::ALL}); 96 | Patch userPatch; 97 | // wrap user patch in non-volatile patch generated by DKUtil 98 | userPatch = prolog.Append(userPatch).Append(epilog); 99 | ``` 100 | -------------------------------------------------------------------------------- /docs/hooks/memory-edit.md: -------------------------------------------------------------------------------- 1 | # Memory Editing 2 | 3 | `DKUtil::Hook` offers some memory writing methods like other library does. 4 | 5 | ::: code-group 6 | 7 | ```cpp [Arbitrary Data] 8 | void WriteData(std::uintptr_t dst, const void* data, const std::size_t size); 9 | ``` 10 | 11 | ```cpp [Immediate Value] 12 | // any trivial, integral or standard layout(pod) types 13 | void WriteImm(std::uintptr_t dst, const dku_h_pod_t auto& data); 14 | ``` 15 | 16 | ```cpp [DKUtil Patch] 17 | void WritePatch(std::uintptr_t dst, const Patch* patch); 18 | void WritePatch(std::uintptr_t dst, const Xbyak::CodeGenerator* xbyak); 19 | ``` 20 | 21 | ::: 22 | 23 | ## Patch Structure 24 | 25 | Anything that has data pointer and data size are accepted. Xbyak is also supported. 26 | 27 | `DKUtil::Patch` is a wrapper for data pointer and data size, and provides extended functionalities. 28 | 29 | ```cpp 30 | struct Patch 31 | { 32 | const void* Data; 33 | const size_t Size; 34 | bool Managed; // ignore unless you know what you are doing 35 | } 36 | ``` 37 | 38 | The `Managed` field indicates whether or not this Patch owns the `Data` resource. This field should not be changed, it's auto set on internal operations. 39 | 40 | ## Common Practices 41 | 42 | ::: code-group 43 | 44 | ```cpp [Hex String] 45 | using namespace DKUtil::Alias; 46 | 47 | static constexpr Patch RadiusPatch{ 48 | // lahf 49 | "\x9F\x50" 50 | // mov rdx, rdi 51 | "\x48\x89\xFA" 52 | // movss xmm2, xmm6 53 | "\xF3\x0F\x10\xD6" 54 | // mov r9, rbp 55 | "\x49\x89\xE9" 56 | // mov [rsp-0x8], rbx 57 | "\x48\x89\x5C\x24\xF8", 58 | 17 59 | }; 60 | ``` 61 | 62 | ```cpp [Read Memory] 63 | using namespace DKUtil::Alias; 64 | 65 | Patch RadiusPatch{ 66 | AsPointer(mem_address), // macro provided by DKUtil 67 | 10 // read 10 bytes from mem_address 68 | }; 69 | ``` 70 | 71 | ```cpp [Xbyak] 72 | struct MyPatch : 73 | public Xbyak::CodeGenerator 74 | { 75 | MyPatch() 76 | { 77 | lahf(); 78 | mov(rdx, rdi); 79 | movss(xmm2, xmm6); 80 | mov(r9, rbp); 81 | mov(ptr[rsp-0x8], rbx); 82 | } 83 | }; 84 | ``` 85 | 86 | ```cpp [Raw Patch] 87 | std::array RawPatch{ 0x48, 0x8B, 0x4C, 0x24, 0x20 }; 88 | ``` 89 | 90 | ::: 91 | 92 | ## Combining Patches 93 | 94 | ```cpp 95 | Patch prolog; 96 | Patch epilog; 97 | Patch extra; 98 | prolog.Append(epilog).Append(extra); 99 | ``` 100 | -------------------------------------------------------------------------------- /docs/hooks/references.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottyduke/DKUtil/31d0bdf36d0c3979c346e6f20735f2cbb763d419/docs/hooks/references.md -------------------------------------------------------------------------------- /docs/hooks/relocation.md: -------------------------------------------------------------------------------- 1 | # Relocation 2 | 3 | Replace `call/jmp` instruction with a hook. 4 | 5 | ## Syntax 6 | 7 | ```cpp 8 | RelHookHandle Hook::AddRelHook(address, function); 9 | ``` 10 | 11 | ## Parameter 12 | 13 | + `N` : length of target instruction, e.g. `call Game.exe+0x1999DF` is `E8 0C 77 FA FF`, which is 5 bytes in length. `FF 15 12 34 56 78` is 6 bytes. 14 | + `bool` : whether to return or not, `call` returns, `jmp` branches thus no return. 15 | + `address` : target instruction address. 16 | + `function` : hook function. 17 | 18 | ## HookHandle 19 | 20 | A `RelHookHandle` object will be returned: 21 | 22 | ```cpp 23 | class RelHookHandle 24 | { 25 | const std::size_t OpSeqSize; 26 | const Imm64 OriginalFunc; 27 | Imm64 Destination; 28 | std::vector OldBytes{}; 29 | std::vector Detour{}; 30 | }; 31 | ``` 32 | 33 | `RelHookHandle` can be implicitly convereted to address of original function. 34 | 35 | ## Wrapper 36 | 37 | For the ease of use, you can use `Hook::write_call` and `Hook::write_branch` for convenience. These wrappers will enable itself and return the original function address. However, you lose control of disabling the hook. 38 | 39 | ## Example 40 | 41 | Given target assembly: 42 | 43 | ```asm 44 | 0x140345675: mov rcx, rax 45 | 0x140345678: call Game.exe+0x123456 46 | ``` 47 | 48 | We want to replace the `call Game.exe+0x123456` to `call HookFunc`: 49 | 50 | ::: code-group 51 | 52 | ```cpp [Class Style] 53 | class Hook 54 | { 55 | // hook function 56 | static bool Hook_123456(void* a_instance) 57 | { 58 | // do something 59 | return func(a_instance); 60 | } 61 | 62 | // original function 63 | static inline std::add_pointer_t func; 64 | 65 | public: 66 | static void Install() 67 | { 68 | // absolute 69 | auto addr = 0x7FF712345678; 70 | // or offset from module base 71 | auto addr = dku::Hook::Module::get().base() + 0x345678; 72 | 73 | // save original function 74 | func = dku::Hook::write_call<5>(addr, Hook_123456); 75 | } 76 | }; 77 | ``` 78 | 79 | ```cpp [Free Functions] 80 | // forward 81 | bool Hook_123456(void* a_instance); 82 | // original function 83 | std::add_pointer_t func; 84 | 85 | bool Hook_123456(void* a_instance) 86 | { 87 | // do something 88 | return func(a_instance); 89 | } 90 | 91 | auto addr = dku::Hook::Module::get().base() + 0x345678; 92 | // save original function 93 | func = dku::Hook::write_call<5>(addr, Hook_123456); 94 | ``` 95 | 96 | ::: 97 | -------------------------------------------------------------------------------- /docs/hooks/trampoline.md: -------------------------------------------------------------------------------- 1 | # Trampoline 2 | 3 | A trampoline is often used internally, it leverages many limitations in memory related operations, common scenarios are: 4 | 5 | + Detouring a function call, because of rip addressing mode is within -/+2GiB range, DKUtil will utilize a trampoline to extend the call indirection. 6 | + Writing assembly patches, trampoline serves as a block of memory to put assembly patches in. 7 | + Allocating custom data. 8 | 9 | ## Workflow 10 | 11 | `Target Code Region` <-**within -/+2GiB**-> `Trampoline` <-> `Our Code Region` 12 | 13 | ## Allocating Trampoline 14 | 15 | Before committing **any** action that requires a trampoline (e.g. detour hooks, cave hooks, asm patches with exceeding sizes), `DKUtil::Hook` requires a manual `DKUtil::Hook::Trampoline::AllocTrampoline(size)` call to initiate a page allocation for trampoline to use. This should be called **only once**, with sufficient size. 16 | 17 | ```cpp 18 | BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) 19 | { 20 | DKUtil::Logger::Init(Plugin::NAME, REL::Module::get().version().string()); 21 | 22 | DKUtil::Hook::Trampoline::AllocTrampoline(1 << 10); // [!code focus] 23 | 24 | Configs::Load(); 25 | Hooks::Install(); 26 | } 27 | ``` 28 | 29 | ::: warning Planned Change 30 | This API has been planned for breaking changes that will auto allocate trampoline and extend its size accordingly. 31 | ::: 32 | 33 | ## Determine Proper Size 34 | 35 | Allocating large enough memory for trampoline is good to begin with, the actual trampoline usage are always logged for debug builds, from there an appropriate sufficient size can be determined for release builds. 36 | 37 | ## Allocate Memory 38 | 39 | To allocate memory **from** trampoline: 40 | 41 | ```cpp 42 | auto& trampoline = dku::Hook::Trampoline::GetTrampoline(); 43 | 44 | struct CustomData; 45 | CustomData* data = trampoline.allocate(); 46 | void* sized_data = trampoline.allocate(0x100); 47 | ``` 48 | 49 | ## SKSE / SFSE / F4SE 50 | 51 | For SKSE, SFSE, and F4SE plugin projects, `DKUtil::Hook` will use commonlib's trampoline interface instead of its internal trampoline. However, `CommonLib::AllocTrampoline` still needs to be called. 52 | -------------------------------------------------------------------------------- /docs/hooks/useful-helpers.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottyduke/DKUtil/31d0bdf36d0c3979c346e6f20735f2cbb763d419/docs/hooks/useful-helpers.md -------------------------------------------------------------------------------- /docs/hooks/vtable-swap.md: -------------------------------------------------------------------------------- 1 | # Virtual Method Table Swap 2 | 3 | Swaps a virtual method table function with hook function. 4 | 5 | ## Syntax 6 | 7 | ```cpp 8 | VMTHookHandle AddVMTHook( 9 | void* vtbl, 10 | std::uint16_t index, 11 | FuncInfo funcInfo, 12 | Patch* patch = nullptr 13 | ); 14 | ``` 15 | 16 | ## Parameter 17 | 18 | + `vtbl` : pointer to virtual method table (a.k.a pointer to class object). 19 | + `index` : **index** of the virtual function in the virtual method table. 20 | + `funcInfo` : FUNC_INFO macro wrapper of hook function. 21 | + `patch` : **optional**, prolog patch before detouring to hook function. 22 | 23 | ## HookHandle 24 | 25 | A `VMTHookHandle` object will be returned: 26 | 27 | ```cpp 28 | class VMTHookHandle 29 | { 30 | template 31 | F GetOldFunction(); 32 | 33 | std::uintptr_t OldAddress; 34 | }; 35 | ``` 36 | 37 | ## Example 38 | 39 | Given base class `Dummy` with two functions: 40 | 41 | ```cpp 42 | class Dummy 43 | { 44 | public: 45 | void MsgA() { INFO("Called MsgA"sv); } // 0 46 | void MsgB() { INFO("Called MsgB"sv); } // 1 47 | }; 48 | 49 | // target dummy vtbl, it's implicitly at 50 | Dummy* dummy = new Dummy(); 51 | ``` 52 | 53 | First translate class methods to __cdecl convention: 54 | 55 | ```cpp 56 | using namespace DKUtil::Alias; 57 | 58 | // first parameter is this pointer 59 | using MsgFunc_t = std::add_pointer_t; 60 | MsgFunc_t oldMsgA; 61 | MsgFunc_t oldMsgB; 62 | ``` 63 | 64 | To swap the functions with our own: 65 | 66 | ```cpp 67 | // hook function 68 | void MsgC(Dummy* a_this) { 69 | INFO("Called MsgC"sv); 70 | // call original function 71 | oldMsgA(a_this); 72 | oldMsgB(a_this); 73 | } 74 | 75 | auto Hook_MsgA = dku::Hook::AddVMTHook(dummy, 0, FUNC_INFO(MsgC)); 76 | auto Hook_MsgB = dku::Hook::AddVMTHook(dummy, 1, FUNC_INFO(MsgC)); 77 | 78 | // save original function 79 | oldMsgA = Hook_MsgA->GetOldFunction(); 80 | oldMsgB = Hook_MsgB->GetOldFunction(); 81 | 82 | Hook_MsgA->Enable(); 83 | Hook_MsgB->Enable(); 84 | // now MsgA and MsgB will both be detoured to MsgC instead 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | title: DKUtil 4 | 5 | hero: 6 | name: DKUtil 7 | text: Header Library for x64 Native Plugin Development 8 | actions: 9 | - theme: brand 10 | text: Get Started 11 | link: /dkutil/about 12 | - theme: alt 13 | text: View on GitHub 14 | link: https://github.com/gottyduke/dkutil 15 | - theme: alt 16 | text: PluginTemplate 17 | link: https://github.com/gottyduke/plugintemplate 18 | 19 | features: 20 | - icon: 📝 21 | title: Make Plugins Easier 22 | details: Logging, hooking, config files, all features needed for a plugin skeleton is here. Plus more useful API and neat functions. 23 | - icon: 🚀 24 | title: Used by Many 25 | details: Bundled with PluginTemplate, a lot of plugin projects start up with DKUtil. 26 | - icon: ⚙ 27 | title: Actively Maintained 28 | details: Actively maintained, DKUtil improves with feedback and contributions. 29 | --- 30 | 31 | -------------------------------------------------------------------------------- /docs/logger/macros.md: -------------------------------------------------------------------------------- 1 | # Macros 2 | 3 | | Macro | Level | 4 | | ----- | --------------------------------------------- | 5 | | TRACE | `trace`, enabled on debug builds | 6 | | DEBUG | `debug`, enabled on debug builds | 7 | | INFO | `info`, enabled on all builds | 8 | | WARN | `warn`, enabled on all builds | 9 | | ERROR | `err`, message box pop-up, returnable | 10 | | FATAL | `critical`, message box pop-up, halts process | 11 | 12 | ## Logging Level 13 | 14 | To change the current logging level, use `Logger::SetLevel`: 15 | 16 | ```cpp 17 | DKUtil::Logger::SetLevel(spdlog::level::level_enums); 18 | ``` 19 | 20 | Convenient switches for disabling/enabling all DEBUG macros: 21 | 22 | ```cpp 23 | ENABLE_DEBUG 24 | // debug logs from here will be printed 25 | DISABLE_DEBUG 26 | // debug logs from here will be omitted 27 | ``` 28 | 29 | ## Formatting 30 | 31 | `DKUtil::Logger` uses `spdlog` as backend, thus powered by `fmt`, for detailed syntax, refer to [fmtlib syntax](https://fmt.dev/latest/syntax.html). 32 | 33 | ```cpp 34 | INFO("{} - {} = {}", 10, 5, 10 - 5); 35 | INFO("addr: {:X}", &pointer); 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/logger/setup.md: -------------------------------------------------------------------------------- 1 | # DKUtil::Logger 2 | 3 | Some macro style loggers with `spdlog` backend. 4 | 5 | ## Setup PROJECT_NAME 6 | 7 | For backwards compatibility reasons, DKUtil requires `PROJECT_NAME` definition prior to including any DKUtil files. 8 | 9 | ```cpp 10 | #define PROJECT_NAME "YourPluginName" 11 | ``` 12 | 13 | ::: tip 14 | This is already configured if using [PluginTemplate](https://github.com/gottyduke/plugintemplate). 15 | ::: 16 | 17 | ## Init Logger 18 | 19 | By default the log file outputs to the current working path. 20 | The relative path `LOG_PATH` can be defined before including. 21 | 22 | ```cpp{4} 23 | #define LOG_PATH "logs\\" // optional 24 | #include "DKUtil/Logger.hpp" 25 | 26 | DKUtil::Logger::Init(PROJECT_NAME, VersionString); 27 | ``` 28 | 29 | ## Internal Debugging 30 | 31 | DKUtil extensively logs its internal operations when building with `Debug` configuration. To disable this behavior, define `DKU_L_DISABLE_INTERNAL_DEBUGGING` explicitly prior to including any DKUtil files. 32 | 33 | ```cpp 34 | #define DKU_L_DISABLE_INTERNAL_DEBUGGING 35 | #include "DKUtil/Logger.hpp" 36 | ``` 37 | 38 | ::: tip Release Builds 39 | `DKU_L_DISABLE_INTERNAL_DEBUGGING` is enabled on release builds. 40 | ::: 41 | -------------------------------------------------------------------------------- /docs/utils/enumeration.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottyduke/DKUtil/31d0bdf36d0c3979c346e6f20735f2cbb763d419/docs/utils/enumeration.md -------------------------------------------------------------------------------- /docs/utils/numbers.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottyduke/DKUtil/31d0bdf36d0c3979c346e6f20735f2cbb763d419/docs/utils/numbers.md -------------------------------------------------------------------------------- /docs/utils/string.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottyduke/DKUtil/31d0bdf36d0c3979c346e6f20735f2cbb763d419/docs/utils/string.md -------------------------------------------------------------------------------- /docs/utils/templates.md: -------------------------------------------------------------------------------- 1 |

DKUtil::Utility

2 | 3 | Some helper functions used within other DKUtil headers. 4 | + function 5 | + `consteval` helper functions retrieving the argument count of a function. 6 | + model 7 | + `Singleton` data model abstract class to save boiler plater code. 8 | + `enumeration` addition to the original `RE::stl::enumeration`. 9 | + static reflection for enum name, type name and value name, support value_type(`n`) and flag_type(`1< static reflection is not implemented using external lib, DKUtil wraps a lightweight compile time **nasty** macro inside. 29 | 30 | ```C++ 31 | enum class Color : std::uint32_t 32 | { 33 | red, 34 | yellow, 35 | white, 36 | }; 37 | 38 | // print 39 | Color redColor = Color::red; 40 | INFO("this enum is {}", dku::print_enum(redColor)); 41 | 42 | // cast 43 | std::string colorStr = "yellow"; 44 | auto& colorTbl = dku::static_enum(); 45 | Color yellowColor = colorTbl.from_string(colorStr); 46 | 47 | // iterate 48 | for (const Color c : colorTbl.value_range(Color::red, Color::white)) { 49 | INFO("color is {}", colorTbl.to_string(c)); 50 | } 51 | 52 | // iterate flag type enum ( 1 << 1, 1 << 2 etc..) 53 | enum class ColorFlag : std::uint32_t 54 | { 55 | red = 1 << 1, 56 | yellow = 1 << 2, 57 | white = 1 << 3, 58 | }; 59 | auto& flagTbl = dku::static_enum(); 60 | 61 | for (const ColorFlag c : flagTbl.flag_range(ColorFlag::red, ColorFlag::white)) { 62 | INFO("color is {}", flagTbl.to_string(c)); 63 | } 64 | ``` 65 | 66 | --- 67 | ## struct_cast, tuple_cast, concepts 68 | compile time conversion for same aligned struct/tuple 69 | ```C++ 70 | struct AggregateType 71 | { 72 | int i; 73 | std::string s; 74 | char c; 75 | bool b; 76 | }; 77 | 78 | auto tv = dku::model::tuple_cast(AggregateType{}); 79 | static_assert(std::is_same_v>); 80 | auto sv = dku::model::struct_cast(tv); 81 | static_assert(std::is_same_v); 82 | 83 | // bindables 84 | static_assert(dku::model::number_of_bindables() == 4); 85 | static_assert(dku::model::number_of_bindables() == 4); 86 | static_assert(dku::model::number_of_bindables() == 4); 87 | 88 | // concepts 89 | int av[] = { 1, 2, 3, 4 }; 90 | static_assert(dku::model::concepts::dku_aggregate); 91 | static_assert(dku::model::concepts::dku_bindable); 92 | static_assert(dku::model::concepts::dku_ranges); 93 | static_assert(dku::model::concepts::dku_trivial_ranges); 94 | ``` 95 | 96 | --- 97 | ## `dku::string` 98 | + iequal 99 | + icontains 100 | + istarts_with 101 | + iends_with 102 | + lexical_cast 103 | + remove_non_alphanumeric 104 | + remove_non_numeric 105 | + replace_nth_occurrence 106 | + split (support multiple delimiters) 107 | + join 108 | + trim 109 | + static_string 110 | 111 | --- 112 | -------------------------------------------------------------------------------- /format-all.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 5 2 | 3 | # args 4 | param ( 5 | [string]$CF = $env:clang_format_instance 6 | ) 7 | 8 | $CF 9 | 10 | if (!(Test-Path $CF -PathType Leaf)) { 11 | "Failed to locate clang-format.exe" 12 | Exit 13 | } 14 | 15 | $headers = Get-ChildItem "$PSScriptRoot\include" -Recurse -File -ErrorAction SilentlyContinue | ? { $_.DirectoryName -inotmatch "external" } 16 | $src = Get-ChildItem "$PSScriptRoot\test" -Recurse -File -ErrorAction SilentlyContinue | ? { $_.DirectoryName -inotmatch "configs" } 17 | 18 | foreach ($file in $headers) { 19 | $file.BaseName 20 | & $CF -i -style=file $file 21 | } 22 | 23 | foreach ($file in $src) { 24 | $file.BaseName 25 | & $CF -i -style=file $file 26 | } 27 | 28 | "Formatted $($headers.Length) header files" 29 | "Formatted $($src.Length) source files" -------------------------------------------------------------------------------- /generate-sln.ps1: -------------------------------------------------------------------------------- 1 | Remove-Item $PSScriptRoot/build -Recurse -Force -ErrorAction:SilentlyContinue -Confirm:$False | Out-Null 2 | & cmake -B $PSScriptRoot/build -S $PSScriptRoot --preset=REL -DPLUGIN_MODE:BOOL=TRUE -------------------------------------------------------------------------------- /include/DKUtil/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * 1.2.0 5 | * Added Schema parser; 6 | * 7 | * 1.1.6 8 | * SFSE integration; 9 | * 10 | * 1.1.5 11 | * Removed DataManager layer between raw data and proxy; 12 | * 13 | * 1.1.4 14 | * Adaptaion of file structural changes; 15 | * 16 | * 1.1.3 17 | * out-of-bound adjusts; 18 | * 19 | * 1.1.2 20 | * constexpr specifiers; 21 | * 22 | * 1.1.1 23 | * Added alias for config proxies; 24 | * Minor logging practices; 25 | * 26 | * 1.1.0 27 | * NG-update; 28 | * Added clamp range; 29 | * Removed Proxy isLoaded check; 30 | * 31 | * 1.0.2 32 | * F4SE integration; 33 | * 34 | * 1.0.1 35 | * Fixed toml empty array crash; 36 | * Removed explicit identifier on copy constructor; 37 | * 38 | * 39 | * 1.0.0 40 | * Proxy, Bind and Load APIs for ini, json and toml type config files; 41 | * 42 | */ 43 | 44 | #define DKU_C_VERSION_MAJOR 1 45 | #define DKU_C_VERSION_MINOR 2 46 | #define DKU_C_VERSION_REVISION 0 47 | 48 | #pragma warning(push) 49 | #pragma warning(disable: 4244) 50 | 51 | #include "Impl/pch.hpp" 52 | 53 | #if !defined(CONFIG_ENTRY) 54 | 55 | # if defined(F4SEAPI) 56 | # define CONFIG_ENTRY "Data\\F4SE\\Plugins\\" 57 | # elif defined(SKSEAPI) 58 | # define CONFIG_ENTRY "Data\\SKSE\\Plugins\\" 59 | # elif defined(SFSEAPI) 60 | # define CONFIG_ENTRY "Data\\SFSE\\Plugins\\" 61 | # else 62 | # define CONFIG_ENTRY "" 63 | # endif 64 | 65 | #endif 66 | 67 | namespace DKUtil 68 | { 69 | constexpr auto DKU_C_VERSION = DKU_C_VERSION_MAJOR * 10000 + DKU_C_VERSION_MINOR * 100 + DKU_C_VERSION_REVISION; 70 | } // namespace DKUtil 71 | 72 | #include "Impl/Config/shared.hpp" 73 | 74 | #include "Impl/Config/data.hpp" 75 | 76 | #include "Impl/Config/ini.hpp" 77 | #include "Impl/Config/json.hpp" 78 | #include "Impl/Config/schema.hpp" 79 | #include "Impl/Config/toml.hpp" 80 | 81 | #include "Impl/Config/proxy.hpp" 82 | 83 | namespace DKUtil::Config 84 | { 85 | /** \brief Parse a string into user defined struct 86 | * \brief e.g. CustomData d = ParseSchemaString(line, delim...) 87 | * \brief This API is an alias of global schema parser for ease of use 88 | * \param a_str : non-empty formatted schema string 89 | * \param a_delimiters : one or multiple string delimiters used to make segments 90 | * \return user defined struct 91 | */ 92 | template 93 | requires(model::concepts::dku_aggregate) 94 | inline static SchemaData ParseSchemaString(std::string a_str, const std::convertible_to auto&... a_delimiters) noexcept 95 | { 96 | return detail::Schema::ParseString(a_str, std::forward(a_delimiters)...); 97 | } 98 | 99 | /** \brief Parse a string to user defined series of segments 100 | * \brief e.g. auto [a, b, c, d] = ParseSchemaString(line, delim...) 101 | * \brief This API is an alias of global schema parser for ease of use 102 | * \param a_str : non-empty formatted schema string 103 | * \param a_delimiters : one or multiple string delimiters used to make segments 104 | * \return std::tuple of user defined segments 105 | */ 106 | template 107 | requires(sizeof...(SchemaSegment) > 1) 108 | inline static std::tuple ParseSchemaString(std::string a_str, const std::convertible_to auto&... a_delimiters) noexcept 109 | { 110 | return detail::Schema::ParseString>(a_str, std::forward(a_delimiters)...); 111 | } 112 | } // namespace DKUtil::Config 113 | 114 | namespace DKUtil::Alias 115 | { 116 | using Boolean = DKUtil::Config::detail::AData; 117 | using Integer = DKUtil::Config::detail::AData; 118 | using Double = DKUtil::Config::detail::AData; 119 | using String = DKUtil::Config::detail::AData>; 120 | 121 | using IniConfig = DKUtil::Config::Proxy; 122 | using JsonConfig = DKUtil::Config::Proxy; 123 | using TomlConfig = DKUtil::Config::Proxy; 124 | using SchemaConfig = DKUtil::Config::Proxy; 125 | using DynamicConfig = DKUtil::Config::Proxy; 126 | } // namespace DKUtil::Alias 127 | 128 | #pragma warning(pop) 129 | -------------------------------------------------------------------------------- /include/DKUtil/Extra.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define DKU_E_VERSION_MAJOR 1 4 | #define DKU_E_VERSION_MINOR 0 5 | #define DKU_E_VERSION_REVISION 0 6 | 7 | #if defined(SKSEAPI) 8 | 9 | # include 10 | # include 11 | # include 12 | 13 | # include "Impl/pch.hpp" 14 | 15 | # include "Impl/Extra/xconsole.hpp" 16 | # include "Impl/Extra/xserialize.hpp" 17 | 18 | # define DKU_X_VERSION_MAJOR 1 19 | # define DKU_X_VERSION_MINOR 0 20 | # define DKU_X_VERSION_REVISION 0 21 | 22 | namespace DKUtil 23 | { 24 | constexpr auto DKU_X_VERSION = DKU_X_VERSION_MAJOR * 10000 + DKU_X_VERSION_MINOR * 100 + DKU_X_VERSION_REVISION; 25 | } // namespace DKUtil 26 | 27 | namespace DKUtil 28 | { 29 | template 30 | using serializable = DKUtil::serialization::Serializable; 31 | 32 | using ResolveType = DKUtil::serialization::ResolveOrder; 33 | } // DKUtil 34 | 35 | #endif -------------------------------------------------------------------------------- /include/DKUtil/Hook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * 2.6.6 5 | * Added KMP version of search_pattern. 6 | * 7 | * 2.6.5 8 | * Added AssumeNonVolatile helper for preserving registers across call boundaries; 9 | * 10 | * 2.6.4 11 | * Separated internal implementations details into individual headers; 12 | * 13 | * 2.6.3 14 | * SFSEAPI integration; 15 | * Fixed wrong Hook indirect call layout 16 | * 17 | * 2.6.2 18 | * Added GetDisp helper function; 19 | * 20 | * 2.6.1 21 | * Assert trampoline in range; 22 | * 23 | * 2.6.0 24 | * Added pattern scan modules; 25 | * Completed trampoline implementation; 26 | * 27 | * 2.5.3 28 | * Fixed stack alignment, force 0x20 allocation; 29 | * 30 | * 2.5.2 31 | * Adaptation of file structural changes; 32 | * 33 | * 2.5.1 34 | * Fixed the issue where CavePtr hasn't been forwarded but calculation is done; 35 | * Fixed the issue kSkipNOP not actually working; 36 | * 37 | * 2.5.0 38 | * Added new assembly structs; 39 | * Removed redundant structs; 40 | * Removed CAVE_MAXIMUM_BYTES; 41 | * Added register preserve functions; 42 | * Changed CaveHookFlag to HookFlag; 43 | * 44 | * 2.4.1 45 | * Added runtime counterparts for NG; 46 | * 47 | * 2.4.0 48 | * Minor formatting and refractoring for NG; 49 | * 50 | * 2.3.3 51 | * F4SE integration; 52 | * CaveHook auto patch the stolen opcodes; 53 | * 54 | * 2.3.2 55 | * Minor formatting changes; 56 | * 57 | * 2.3.1 58 | * CaveHookHandle changed base class; 59 | * 60 | * 2.3.0 61 | * Added assembly patch; 62 | * Improved cave hook; 63 | * 64 | * 2.2.0 65 | * Added import address table hook; 66 | * 67 | * 2.1.0 68 | * Added virtual method table hook; 69 | * 70 | * 2.0.0 71 | * Code partial redone; wtf are those macros; 72 | * Added stack buffer to help with stack unwindability; 73 | * Replaced old detour method ( rax call ) with ( call rip ) so DKU_H does not dirty register; 74 | * 75 | * 1.0.0 ~ 1.9.2 76 | * CMake integration, inter-library integration; 77 | * Fixed a misrelocation where trampoline ptr may not be pointed to correct address within cave; 78 | * Fixed an error where branch may not be correctly initiated; 79 | * Fixed an misorder where flags may be disabled inappropriately; 80 | * Reordered the cave code layout; 81 | * Added SMART_ALLOC and CAVE related conditional macro, if enabled: 82 | * - Attempt to write patches into code cave to reduce trampoline load if cave size satisfies; 83 | * - Attempt to skip trampoline allocation if hook function is null and cave size satisfies; 84 | * - Attempt to write rax-clean instructions into code cave if a_preserve and cave size satisfies; 85 | * Removed success checks during the writing procedure, due to F4SE64 implementation has no returning value; 86 | * Restructured two separate implementations into conditional compilation; 87 | * Resolve address differently based on which implementation is used; 88 | * Added derived prototype of BranchToFunction<...>(...) without address library usage; 89 | * Added support for ( a_hookFunc = 0 ) so that patches will be applied without branching to any function; 90 | * Added default value for patch related parameters, if branching is done without any patches; 91 | * Fixed various rvalue const cast errors within F4SE64 implementation; 92 | * Added prototype of GetCurrentPtr(); 93 | * Added prototype of ResetPtr(); 94 | * Renamed some template parameters; 95 | * Changed some local variables to constexpr; 96 | * Added F4SE64 implementation; 97 | * Added VERBOSE conditional to log each step for better debugging; 98 | * Moved predefined values into Impl namespace; 99 | * Changed above values from const to constexpr; 100 | * Renamed InjectAt<...>(...) to BranchToFunction<...>(...); 101 | * Changed patch type from ( const char* ) to ( const void* ); 102 | * Removed strlen(...) usage; 103 | * Added additional parameter into InjectAt<...>( a_hookFunc, a_prePatch, a_prePatchSize, a_postPatch, a_postPatchSize ); 104 | * Removed BranchAt<...>(...); 105 | * Integrated CodeGenerator; 106 | * Implemented InjectAt<...>(...); 107 | * Added prototype of bool : BranchAt< a_hookFunc >( a_prePatch, a_postPatch ); 108 | * Added prototype of InjectAt< ID, START, END >( a_hookFunc, a_prePatch, a_postPatch ); 109 | */ 110 | 111 | #define DKU_H_VERSION_MAJOR 2 112 | #define DKU_H_VERSION_MINOR 6 113 | #define DKU_H_VERSION_REVISION 4 114 | 115 | #pragma warning(push) 116 | #pragma warning(disable: 4244) 117 | 118 | #include "Impl/pch.hpp" 119 | 120 | namespace DKUtil 121 | { 122 | constexpr auto DKU_H_VERSION = DKU_H_VERSION_MAJOR * 10000 + DKU_H_VERSION_MINOR * 100 + DKU_H_VERSION_REVISION; 123 | } // namespace DKUtil 124 | 125 | #include "Impl/Hook/shared.hpp" 126 | 127 | #include "Impl/Hook/api.hpp" 128 | 129 | namespace DKUtil::Alias 130 | { 131 | using Patch = DKUtil::Hook::Patch; 132 | using HookHandle = std::unique_ptr; 133 | using CaveHandle = DKUtil::Hook::CaveHookHandle; 134 | using VMTHandle = DKUtil::Hook::VMTHookHandle; 135 | using IATHandle = DKUtil::Hook::IATHookHandle; 136 | 137 | using Reg = DKUtil::Hook::Assembly::Register; 138 | using Xmm = DKUtil::Hook::Assembly::SIMD; 139 | template 140 | using Pattern = DKUtil::Hook::Assembly::Pattern::PatternMatcher; 141 | 142 | using HookFlag = DKUtil::Hook::HookFlag; 143 | } // namespace DKUtil::Alias 144 | 145 | #pragma warning(pop) 146 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Config/Data.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | namespace DKUtil::Config::detail 6 | { 7 | enum class DataType 8 | { 9 | kBoolean, 10 | kDouble, 11 | kInteger, 12 | kString, 13 | 14 | kError, 15 | }; 16 | 17 | template 18 | struct data_trait 19 | { 20 | using type = DataType; 21 | static constexpr bool is_bool = std::is_same_v; 22 | static constexpr bool is_double = std::is_same_v; 23 | static constexpr bool is_integer = std::is_same_v; 24 | static constexpr bool is_string = std::is_same_v>; 25 | static constexpr auto value() 26 | { 27 | if constexpr (is_bool) 28 | return type::kBoolean; 29 | if constexpr (is_double) 30 | return type::kDouble; 31 | if constexpr (is_integer) 32 | return type::kInteger; 33 | if constexpr (is_string) 34 | return type::kString; 35 | return type::kError; 36 | }; 37 | }; 38 | template 39 | static constexpr auto data_trait_v = data_trait::value(); 40 | 41 | class IData 42 | { 43 | public: 44 | constexpr IData(DataType a_type) noexcept : 45 | _type(a_type) 46 | {} 47 | virtual constexpr ~IData() {} 48 | 49 | [[nodiscard]] constexpr auto get_type() const noexcept { return _type; } 50 | 51 | template 52 | [[nodiscard]] auto* As(); 53 | 54 | protected: 55 | DataType _type; 56 | }; 57 | 58 | // automatic data with collection enabled 59 | template < 60 | typename data_t, 61 | DataType TYPE = data_trait_v> 62 | class AData : public IData 63 | { 64 | using collection = std::vector; 65 | using IData::IData; 66 | 67 | public: 68 | constexpr AData() noexcept = delete; 69 | constexpr AData(const std::string& a_key, const std::string& a_section = {}) : 70 | IData(TYPE), _key(std::move(a_key)), _section(a_section.empty() ? "Global" : std::move(a_section)) 71 | {} 72 | 73 | constexpr AData(const AData&) noexcept = delete; 74 | constexpr AData(AData&&) noexcept = delete; 75 | constexpr ~AData() = default; 76 | 77 | // return the front if out of bounds 78 | [[nodiscard]] auto& operator[](const std::size_t a_index) noexcept 79 | requires(!std::is_same_v) 80 | { 81 | if (_isCollection) { 82 | return a_index < _collection->size() ? _collection->at(a_index) : *_collection->end(); 83 | } else { 84 | return _data; 85 | } 86 | } 87 | [[nodiscard]] constexpr operator bool() noexcept 88 | requires(std::is_same_v) 89 | { 90 | return _data; 91 | } 92 | [[nodiscard]] constexpr auto& operator*() noexcept { return _data; } 93 | [[nodiscard]] constexpr std::string_view get_key() const noexcept { return _key; } 94 | [[nodiscard]] constexpr std::string_view get_section() const noexcept { return _section; } 95 | [[nodiscard]] constexpr auto is_collection() const noexcept { return _isCollection; } 96 | 97 | [[nodiscard]] constexpr const auto get_data() const noexcept { return _data; } 98 | [[nodiscard]] constexpr const auto get_collection() const noexcept 99 | { 100 | if (_isCollection) { 101 | return *_collection; 102 | } else { 103 | // force singular data into size-1 collection 104 | return collection{ _data }; 105 | } 106 | } 107 | [[nodiscard]] constexpr auto get_size() const noexcept { return _isCollection ? _collection->size() : 0; } 108 | [[nodiscard]] constexpr auto get_type() const noexcept { return typeid(data_t).name(); } 109 | constexpr void debug_dump() const noexcept 110 | { 111 | #ifndef NDEBUG 112 | if (_isCollection) { 113 | std::ranges::for_each(*_collection, [&](data_t val) { 114 | __DEBUG("Setting collection value [{}] to [{}]", val, _key); 115 | }); 116 | } else { 117 | __DEBUG("Setting value [{}] to [{}]", _data, _key); 118 | } 119 | #endif 120 | } 121 | 122 | constexpr void set_data(data_t a_value) noexcept 123 | { 124 | _isCollection = false; 125 | _data = a_value; 126 | 127 | clamp(); 128 | debug_dump(); 129 | } 130 | 131 | constexpr void set_data(const std::initializer_list& a_list) 132 | { 133 | _collection.reset(); 134 | 135 | _isCollection = (a_list.size() > 1); 136 | if (_isCollection) { 137 | _collection = std::make_unique(a_list); 138 | } 139 | 140 | _data = *a_list.begin(); 141 | 142 | clamp(); 143 | debug_dump(); 144 | } 145 | 146 | constexpr void set_data(const collection& a_collection) 147 | { 148 | _collection.reset(); 149 | 150 | _isCollection = (a_collection.size() > 1); 151 | if (_isCollection) { 152 | _collection = std::make_unique(std::move(a_collection)); 153 | _data = _collection->front(); 154 | } 155 | 156 | clamp(); 157 | debug_dump(); 158 | } 159 | 160 | constexpr void set_range(std::pair a_range) 161 | { 162 | if constexpr (model::concepts::dku_numeric) { 163 | _range = a_range; 164 | } 165 | } 166 | 167 | constexpr void clamp() 168 | { 169 | if (!model::concepts::dku_numeric || _range.first > _range.second) { 170 | return; 171 | } 172 | 173 | auto single_clamp = [this](data_t data) { 174 | return data < _range.first ? 175 | _range.first : 176 | (data > _range.second ? _range.second : data); 177 | }; 178 | 179 | if (_isCollection) { 180 | *_collection = std::move(*_collection | std::views::transform(single_clamp) | std::ranges::to()); 181 | _data = _collection->front(); 182 | } else { 183 | _data = single_clamp(_data); 184 | } 185 | } 186 | 187 | private: 188 | const std::string _key; 189 | const std::string _section; 190 | bool _isCollection = false; 191 | data_t _data; 192 | std::pair _range; 193 | std::unique_ptr _collection{ nullptr }; 194 | }; 195 | 196 | template 197 | auto* IData::As() 198 | { 199 | return dynamic_cast*>(this); 200 | } 201 | 202 | extern template class AData; 203 | extern template class AData; 204 | extern template class AData; 205 | extern template class AData>; 206 | } // namespace DKUtil::Config::detail 207 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Config/Ini.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "data.hpp" 4 | 5 | #include "SimpleIni.h" 6 | 7 | namespace DKUtil::Config::detail 8 | { 9 | class Ini final : public IParser 10 | { 11 | public: 12 | using IParser::IParser; 13 | 14 | void Parse(const char* a_data) noexcept override 15 | { 16 | _ini.SetUnicode(); 17 | auto result = a_data ? _ini.LoadData(a_data) : _ini.LoadFile(_filepath.c_str()); 18 | if (result < 0) { 19 | FATAL("DKU_C: Parser#{}: Loading failed! -> {}\n{}", _id, _filepath.c_str(), err_getmsg()); 20 | } 21 | 22 | CSimpleIniA::TNamesDepend sections; 23 | _ini.GetAllSections(sections); 24 | 25 | for (auto& section : sections) { 26 | CSimpleIniA::TNamesDepend keys; 27 | _ini.GetAllKeys(section.pItem, keys); 28 | 29 | for (auto& key : keys) { 30 | const char* value = _ini.GetValue(section.pItem, key.pItem); 31 | if (!value) { 32 | continue; 33 | } 34 | 35 | if (_manager.contains(std::make_pair(key.pItem, section.pItem))) { 36 | std::string raw{ value }; 37 | 38 | auto& data = _manager.at(std::make_pair(key.pItem, section.pItem)); 39 | switch (data->get_type()) { 40 | case DataType::kBoolean: 41 | { 42 | if (raw == "0" || dku::string::iequals(raw, "false")) { 43 | data->As()->set_data(false); 44 | } else if (raw == "1" || dku::string::iequals(raw, "true")) { 45 | data->As()->set_data(true); 46 | } else { 47 | err_mismatch("Invalid bool input", key.pItem, "Boolean", value); 48 | } 49 | 50 | break; 51 | } 52 | case DataType::kDouble: 53 | { 54 | auto sv = dku::string::split(raw, ",", " "); 55 | try { 56 | if (sv.size() <= 1) { 57 | data->As()->set_data(std::stod(value)); 58 | } else { 59 | auto tv = sv | std::views::transform([](std::string str) { return std::stod(str); }); 60 | data->As()->set_data({ tv.begin(), tv.end() }); 61 | } 62 | } catch (const std::exception& e) { 63 | err_mismatch(e.what(), key.pItem, "Double", value); 64 | } 65 | 66 | break; 67 | } 68 | case DataType::kInteger: 69 | { 70 | auto sv = dku::string::split(raw, ",", " "); 71 | try { 72 | if (sv.size() <= 1) { 73 | data->As()->set_data(std::stoll(value)); 74 | } else { 75 | auto tv = sv | std::views::transform([](std::string& str) { return std::stoll(str); }); 76 | data->As()->set_data({ tv.begin(), tv.end() }); 77 | } 78 | } catch (const std::exception& e) { 79 | err_mismatch(e.what(), key.pItem, "Double", value); 80 | } 81 | 82 | break; 83 | } 84 | case DataType::kString: 85 | { 86 | std::string old{ raw }; 87 | dku::string::replace_nth_occurrence(raw, 0, "\\,", "_dku_comma_"); 88 | dku::string::replace_nth_occurrence(raw, 0, "\\\"", "_dku_quote_"); 89 | 90 | auto sv = dku::string::split(raw, ","); 91 | try { 92 | if (sv.size() <= 1) { 93 | old = dku::string::trim(old); 94 | dku::string::replace_nth_occurrence(old, 0, "\""); 95 | data->As>()->set_data(old); 96 | } else { 97 | std::ranges::for_each(sv, [](std::string& str) { 98 | dku::string::replace_nth_occurrence(str, 0, "\""); 99 | dku::string::replace_nth_occurrence(str, 0, "_dku_comma_", "\\,"); 100 | dku::string::replace_nth_occurrence(str, 0, "_dku_quote_", "\\\""); 101 | str = dku::string::trim(str); 102 | }); 103 | data->As>()->set_data(sv); 104 | } 105 | } catch (const std::exception& e) { 106 | err_mismatch(e.what(), key.pItem, "String", value); 107 | } 108 | 109 | break; 110 | } 111 | case DataType::kError: 112 | default: 113 | continue; 114 | } 115 | } 116 | } 117 | } 118 | 119 | auto sr = _ini.Save(_content); 120 | if (sr < 0) { 121 | ERROR("DKU_C: Parser#{}: Saving data failed!\nFile: {}\n{}", _id, _filepath, err_getmsg()); 122 | } 123 | 124 | __DEBUG("DKU_C: Parser#{}: Parsing finished", _id); 125 | } 126 | 127 | void Write(const std::string_view a_filePath) noexcept override 128 | { 129 | auto result = a_filePath.empty() ? _ini.SaveFile(_filepath.c_str()) : _ini.SaveFile(a_filePath.data()); 130 | if (result < 0) { 131 | ERROR("DKU_C: Parser#{}: Writing file failed!\nFile: {}\n{}", _id, _filepath, err_getmsg()); 132 | } 133 | 134 | __DEBUG("DKU_C: Parser#{}: Writing finished", _id); 135 | } 136 | 137 | void Generate() noexcept override 138 | { 139 | CSimpleIniA::TNamesDepend sections; 140 | _ini.GetAllSections(sections); 141 | 142 | for (auto& section : sections) { 143 | _ini.Delete(section.pItem, nullptr); 144 | } 145 | 146 | for (auto& [key, data] : _manager) { 147 | auto sanitized = key.second.empty() ? "Global"sv : key.second; 148 | std::string raw{}; 149 | switch (data->get_type()) { 150 | case DataType::kBoolean: 151 | { 152 | raw = data->As()->get_data() ? "true" : "false"; 153 | break; 154 | } 155 | case DataType::kDouble: 156 | { 157 | raw = std::to_string(data->As()->get_data()); 158 | break; 159 | } 160 | case DataType::kInteger: 161 | { 162 | raw = std::to_string(data->As()->get_data()); 163 | break; 164 | } 165 | case DataType::kString: 166 | { 167 | auto* str = data->As>(); 168 | if (str->is_collection()) { 169 | raw = dku::string::join(str->get_collection(), ", "sv); 170 | } else { 171 | raw = str->get_data(); 172 | } 173 | 174 | break; 175 | } 176 | case DataType::kError: 177 | default: 178 | continue; 179 | } 180 | 181 | auto rc = _ini.SetValue(sanitized.data(), key.first.data(), raw.data()); 182 | if (rc == SI_FAIL) { 183 | ERROR( 184 | "DKU_C: Parser#{}: failed generating default value\n" 185 | "File: {}\nKey: {}, Section: {}\nType: {}\nValue: {}", 186 | _id, _filepath.data(), key.first, sanitized, dku::print_enum(data->get_type()), raw.data()); 187 | } 188 | } 189 | 190 | __DEBUG("DKU_C: Parser#{}: Generating finished", _id); 191 | } 192 | 193 | private: 194 | const char* err_getmsg() noexcept 195 | { 196 | std::ranges::fill(errmsg, 0); 197 | strerror_s(errmsg, errno); 198 | return errmsg; 199 | } 200 | 201 | void err_mismatch(std::string_view a_key, std::string_view a_type, std::string_view a_value, std::string_view a_what) noexcept 202 | { 203 | ERROR("DKU_C: Parser#{}: {}\nValue type mismatch!\nFile: {}\nKey: {}, Expected: {}, Value: {}", _id, a_what, _filepath.c_str(), a_key, a_type, a_value); 204 | } 205 | 206 | CSimpleIniA _ini; 207 | char errmsg[72]; 208 | }; 209 | } // namespace DKUtil::Config 210 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Config/Json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "data.hpp" 4 | 5 | #include "nlohmann/json.hpp" 6 | 7 | namespace DKUtil::Config::detail 8 | { 9 | class Json final : public IParser 10 | { 11 | public: 12 | using IParser::IParser; 13 | using json = nlohmann::json; 14 | 15 | void Parse(const char* a_data) noexcept override 16 | { 17 | if (a_data) { 18 | _json = json::parse(a_data); 19 | } else { 20 | std::basic_ifstream file{ _filepath }; 21 | if (!file.is_open()) { 22 | FATAL("DKU_C: Parser#{}: Loading failed! -> {}", _id, _filepath.c_str()); 23 | } 24 | 25 | file >> _json; 26 | file.close(); 27 | } 28 | 29 | for (auto& [key, data] : _manager) { 30 | auto raw = _json.find(key.first.data()); 31 | if (raw == _json.end()) { 32 | ERROR("DKU_C: Parser#{}: Retrieving config failed!\nFile: {}\nKey: {}", _id, _filepath.c_str(), key.first); 33 | } 34 | 35 | switch (data->get_type()) { 36 | case DataType::kBoolean: 37 | { 38 | data->As()->set_data(raw->get()); 39 | break; 40 | } 41 | case DataType::kDouble: 42 | { 43 | if (raw->type() == json::value_t::array) { 44 | data->As()->set_data(raw->get>()); 45 | } else { 46 | data->As()->set_data(raw->get()); 47 | } 48 | break; 49 | } 50 | case DataType::kInteger: 51 | { 52 | if (raw->type() == json::value_t::array) { 53 | data->As()->set_data(raw->get>()); 54 | } else { 55 | data->As()->set_data(raw->get()); 56 | } 57 | break; 58 | } 59 | case DataType::kString: 60 | { 61 | if (raw->type() == json::value_t::array) { 62 | data->As>()->set_data(raw->get>>()); 63 | } else { 64 | data->As>()->set_data(raw->get>()); 65 | } 66 | break; 67 | } 68 | case DataType::kError: 69 | default: 70 | continue; 71 | } 72 | } 73 | 74 | _content = std::move(_json.dump()); 75 | __DEBUG("DKU_C: Parser#{}: Parsing finished", _id); 76 | } 77 | 78 | void Write(const std::string_view a_filePath) noexcept override 79 | { 80 | auto* filePath = a_filePath.empty() ? _filepath.data() : a_filePath.data(); 81 | std::basic_ofstream file{ filePath }; 82 | if (!file.is_open() || !file) { 83 | ERROR("DKU_C: Parser#{}: Writing file failed! -> {}\nofstream cannot be opened", _id, filePath); 84 | } 85 | 86 | file << _json.dump(4); 87 | file.close(); 88 | 89 | __DEBUG("DKU_C: Parser#{}: Writing finished", _id); 90 | } 91 | 92 | void Generate() noexcept override 93 | { 94 | _json.clear(); 95 | for (auto& [key, data] : _manager) { 96 | switch (data->get_type()) { 97 | case DataType::kBoolean: 98 | { 99 | _json[key.first.data()] = data->As()->get_data(); 100 | break; 101 | } 102 | case DataType::kDouble: 103 | { 104 | if (auto* raw = data->As(); raw->is_collection()) { 105 | _json[key.first.data()] = raw->get_collection(); 106 | } else { 107 | _json[key.first.data()] = raw->get_data(); 108 | } 109 | break; 110 | } 111 | case DataType::kInteger: 112 | { 113 | if (auto* raw = data->As(); raw->is_collection()) { 114 | _json[key.first.data()] = raw->get_collection(); 115 | } else { 116 | _json[key.first.data()] = raw->get_data(); 117 | } 118 | break; 119 | } 120 | case DataType::kString: 121 | { 122 | if (auto* raw = data->As>(); raw->is_collection()) { 123 | _json[key.first.data()] = raw->get_collection(); 124 | } else { 125 | _json[key.first.data()] = raw->get_data(); 126 | } 127 | break; 128 | } 129 | case DataType::kError: 130 | default: 131 | continue; 132 | } 133 | } 134 | } 135 | 136 | private: 137 | json _json; 138 | }; 139 | } // namespace DKUtil::Config 140 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Config/Proxy.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #define __eval_helper(SRC) DKUtil::Config::EvaluateConfig([]() { return SRC; }) 6 | // compile-time evaluation 7 | #define COMPILE_PROXY(SRC) DKUtil::Config::Proxy<__eval_helper(SRC)>(SRC) 8 | // runtime dynamic 9 | #define RUNTIME_PROXY(SRC) DKUtil::Config::Proxy(SRC) 10 | // schema configs 11 | #define SCHEMA_PROXY(SRC) DKUtil::Config::Proxy(SRC) 12 | 13 | namespace DKUtil::Config 14 | { 15 | enum class FileType 16 | { 17 | kDynamic = 0, 18 | kIni, 19 | kJson, 20 | kToml, 21 | kSchema, 22 | 23 | kError 24 | }; 25 | 26 | // compile-time file type evaluation from literal/literal view 27 | template 28 | consteval FileType EvaluateConfig(const input_string_t a_file) 29 | { 30 | constexpr std::basic_string_view file = a_file(); 31 | static_assert(!file.empty(), "Empty filename passed"); 32 | static_assert(file.size() > 4, "Filename too short"); 33 | 34 | // where is case insensitive consteval compare?! 35 | constexpr auto extension = file.substr(file.size() - 4); 36 | if constexpr (extension[0] == '.' && 37 | (extension[1] == 'i' || extension[1] == 'I') && 38 | (extension[2] == 'n' || extension[2] == 'N') && 39 | (extension[3] == 'i' || extension[3] == 'I')) { 40 | return FileType::kIni; 41 | } 42 | if constexpr ((extension[0] == 'j' || extension[0] == 'J') && 43 | (extension[1] == 's' || extension[1] == 'S') && 44 | (extension[2] == 'o' || extension[2] == 'O') && 45 | (extension[3] == 'n' || extension[3] == 'N')) { 46 | return FileType::kJson; 47 | } 48 | if constexpr ((extension[0] == 't' || extension[0] == 'T') && 49 | (extension[1] == 'o' || extension[1] == 'O') && 50 | (extension[2] == 'm' || extension[2] == 'M') && 51 | (extension[3] == 'l' || extension[3] == 'L')) { 52 | return FileType::kToml; 53 | } 54 | 55 | return FileType::kError; 56 | } 57 | 58 | template 59 | requires(ConfigFileType != FileType::kError) 60 | class Proxy 61 | { 62 | // clang-format off 63 | using parser_t = 64 | std::conditional_t>>>; 68 | // clang-format on 69 | public: 70 | // compile defined 71 | constexpr explicit Proxy(const std::string_view a_file) noexcept 72 | requires(ConfigFileType != FileType::kDynamic) 73 | : 74 | _id(detail::_Count++), 75 | _filename(a_file), 76 | _type(ConfigFileType), _parser(std::make_unique(a_file, _id, _manager)) 77 | { 78 | __DEBUG("DKU_C: Proxy#{}: Compile -> {}", _id, _filename); 79 | } 80 | 81 | // runtime defined 82 | constexpr explicit Proxy(const std::string_view a_file) noexcept 83 | requires(ConfigFileType == FileType::kDynamic) 84 | : 85 | _id(detail::_Count++), 86 | _filename(a_file) 87 | { 88 | if (dku::string::iends_with(a_file, "ini")) { 89 | _parser = std::make_unique(a_file, _id, _manager); 90 | } else if (dku::string::iends_with(a_file, "json")) { 91 | _parser = std::make_unique(a_file, _id, _manager); 92 | } else if (dku::string::iends_with(a_file, "toml")) { 93 | _parser = std::make_unique(a_file, _id, _manager); 94 | } else { 95 | ERROR("DKU_C: Proxy#{}: No suitable parser found for file -> {}", _id, a_file); 96 | } 97 | 98 | __DEBUG("DKU_C: Proxy#{}: Runtime -> {}", _id, _filename); 99 | } 100 | 101 | Proxy() = default; 102 | Proxy(const Proxy&) = delete; 103 | Proxy(Proxy&&) = default; 104 | ~Proxy() = default; 105 | 106 | Proxy& operator=(const Proxy&) = delete; 107 | Proxy& operator=(Proxy&&) = default; 108 | 109 | void Load(const char* a_data = nullptr) noexcept 110 | { 111 | __DEBUG("DKU_C: Proxy#{}: Loading -> {}", _id, _filename); 112 | 113 | if (GenerateIfMissing()) { 114 | _parser->Parse(a_data); 115 | } 116 | } 117 | 118 | void Write(const std::string_view a_file = {}) noexcept 119 | { 120 | __DEBUG("DKU_C: Proxy#{}: Writing -> {}", _id, _filename); 121 | 122 | _parser->Write(a_file); 123 | } 124 | 125 | template < 126 | const double min = 1., 127 | const double max = 0., 128 | typename data_t> 129 | constexpr void Bind(detail::AData& a_data, const std::convertible_to auto&... a_value) noexcept 130 | { 131 | static_assert(ConfigFileType != FileType::kSchema, "Schema parser cannot use regular data bindings!"); 132 | 133 | _manager.try_emplace(std::make_pair(a_data.get_key(), a_data.get_section()), std::addressof(a_data)); 134 | a_data.set_range({ min, max }); 135 | a_data.set_data({ static_cast(a_value)... }); 136 | } 137 | 138 | constexpr void Generate() noexcept 139 | { 140 | __DEBUG("DKU_C: Proxy#{}: Generating -> {}", _id, _filename); 141 | 142 | _parser->Generate(); 143 | } 144 | 145 | constexpr bool GenerateIfMissing() noexcept 146 | { 147 | auto found = std::filesystem::exists(_parser->filepath()); 148 | 149 | if (!found) { 150 | Generate(); 151 | Write(); 152 | } 153 | 154 | return found; 155 | } 156 | 157 | [[nodiscard]] constexpr auto get_id() const noexcept { return _id; } 158 | [[nodiscard]] constexpr auto get_filename() const noexcept { return _filename; } 159 | [[nodiscard]] constexpr auto get_type() const noexcept { return _type; } 160 | [[nodiscard]] constexpr auto& get_parser() const noexcept { return *_parser; } 161 | [[nodiscard]] constexpr auto* data() noexcept { return _parser->data(); } 162 | 163 | private: 164 | const std::uint32_t _id; 165 | const std::string _filename; 166 | FileType _type; 167 | std::unique_ptr _parser; 168 | detail::manager _manager; // key, 169 | }; 170 | } // namespace DKUtil::Config 171 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Config/Shared.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DKUtil/Impl/pch.hpp" 4 | #include "DKUtil/Logger.hpp" 5 | #include "DKUtil/Utility.hpp" 6 | 7 | namespace DKUtil::Config 8 | { 9 | inline auto GetPath(const std::string_view a_file) noexcept 10 | { 11 | std::filesystem::path dir{ CONFIG_ENTRY }; 12 | if (dir.is_relative() || dir.empty()) { 13 | dir = std::filesystem::current_path() / dir; 14 | } 15 | 16 | std::filesystem::path file{ a_file.data() }; 17 | if (!a_file.empty() && file.is_relative()) { 18 | file = dir / file; 19 | } 20 | 21 | return file.string(); 22 | } 23 | 24 | template 25 | inline std::vector GetAllFiles(std::string_view a_path = {}, std::string_view a_ext = {}, std::string_view a_prefix = {}, std::string_view a_suffix = {}) noexcept 26 | { 27 | using dir_iterator = std::conditional_t; 28 | 29 | std::vector files; 30 | auto file_iterator = [&](const std::filesystem::directory_entry& a_file) { 31 | if (a_file.exists() && 32 | !a_file.path().empty()) { 33 | if (!a_ext.empty() && a_file.path().extension() != a_ext) { 34 | return; 35 | } 36 | 37 | const auto path = a_file.path().string(); 38 | 39 | if (!a_prefix.empty() && path.find(a_prefix) != std::string::npos) { 40 | files.push_back(path); 41 | } else if (!a_suffix.empty() && path.rfind(a_suffix) != std::string::npos) { 42 | files.push_back(path); 43 | } else if (a_prefix.empty() && a_suffix.empty()) { 44 | files.push_back(path); 45 | } 46 | } 47 | }; 48 | 49 | std::string dir(MAX_PATH + 1, ' '); 50 | auto res = GetModuleFileNameA(nullptr, dir.data(), MAX_PATH + 1); 51 | if (res == 0) { 52 | ERROR("DKU_C: Unable to acquire valid path using default null path argument!\nExpected: Current directory\nResolved: NULL"); 53 | } 54 | 55 | auto eol = dir.find_last_of("\\/"); 56 | dir = dir.substr(0, eol); 57 | 58 | auto path = a_path.empty() ? std::filesystem::path{ dir } : std::filesystem::path{ a_path }; 59 | if (!is_directory(path.parent_path())) { 60 | path = dir / path; 61 | } 62 | 63 | std::ranges::for_each(dir_iterator(path), file_iterator); 64 | std::ranges::sort(files); 65 | 66 | return files; 67 | } 68 | 69 | namespace detail 70 | { 71 | struct section_key_hash 72 | { 73 | size_t operator()(const std::pair& key) const 74 | { 75 | // Hash combining algorithm taken from boost::hash_combine 76 | auto hash = std::hash()(key.first); 77 | hash ^= std::hash()(key.second) + 0x9e3779b9 + (hash << 6) + (hash >> 2); 78 | return hash; 79 | } 80 | }; 81 | 82 | class IData; 83 | // , data* 84 | using manager = std::unordered_map, detail::IData*, section_key_hash>; 85 | 86 | inline static std::uint32_t _Count{ 0 }; 87 | 88 | class IParser 89 | { 90 | public: 91 | explicit IParser(std::string_view a_file, const std::uint32_t a_id, manager& a_manager) : 92 | _filename(Logger::detail::short_file(a_file.data())), _filepath(GetPath(a_file)), _id(a_id), _manager(a_manager) 93 | {} 94 | 95 | constexpr IParser() = delete; 96 | constexpr IParser(const IParser&) noexcept = default; 97 | constexpr IParser(IParser&&) noexcept = default; 98 | constexpr virtual ~IParser() = default; 99 | 100 | // accessor 101 | [[nodiscard]] constexpr auto* data() noexcept { return _content.data(); } 102 | [[nodiscard]] constexpr auto& content() const noexcept { return _content; } 103 | [[nodiscard]] constexpr std::string_view filename() const noexcept { return _filename; } 104 | [[nodiscard]] constexpr std::string_view filepath() const noexcept { return _filepath; } 105 | 106 | virtual void Parse(const char* = nullptr) noexcept = 0; 107 | virtual void Write(const std::string_view) noexcept = 0; 108 | virtual void Generate() noexcept = 0; 109 | 110 | protected: 111 | const std::uint32_t _id; 112 | const std::string _filepath; 113 | const std::string _filename; 114 | std::string _content; 115 | const manager& _manager; 116 | }; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Config/Toml.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "data.hpp" 4 | 5 | #define TOML_EXCEPTIONS 0 6 | #include "external/toml.hpp" 7 | 8 | namespace DKUtil::Config::detail 9 | { 10 | class Toml final : public IParser 11 | { 12 | public: 13 | using IParser::IParser; 14 | 15 | void Parse(const char* a_data) noexcept override 16 | { 17 | auto result = a_data ? toml::parse(a_data) : toml::parse_file(_filepath); 18 | if (!result) { 19 | FATAL("DKU_C: Parser#{}: Parsing failed!\nFile: {}\nDesc: {}", _id, *result.error().source().path.get(), result.error().description()); 20 | } 21 | 22 | _toml = std::move(result).table(); 23 | for (auto& [section, table] : _toml) { 24 | if (!table.is_table()) { 25 | __INFO("DKU_C: WARNING\nParser#{}: Sectionless configuration present and skipped.\nPossible inappropriate formatting at [{}]", _id, section.str()); 26 | continue; 27 | } else { 28 | for (auto& [key, data] : _manager) { 29 | if (section != key.second) { 30 | continue; 31 | } 32 | 33 | auto raw = table.as_table()->find(key.first.data()); 34 | if (table.as_table()->begin() != table.as_table()->end() && 35 | raw == table.as_table()->end()) { 36 | continue; 37 | } 38 | 39 | switch (data->get_type()) { 40 | case DataType::kBoolean: 41 | { 42 | if (raw->second.as_boolean()) { 43 | data->As()->set_data(raw->second.as_boolean()->get()); 44 | } 45 | break; 46 | } 47 | case DataType::kDouble: 48 | { 49 | double input; 50 | if (raw->second.is_array() && raw->second.as_array()) { 51 | if (raw->second.as_array()->size() == 1 && 52 | raw->second.as_array()->front().value()) { 53 | input = raw->second.as_array()->front().value().value(); 54 | } else if (raw->second.as_array()->size()) { 55 | std::vector array; 56 | for (auto& node : *raw->second.as_array()) { 57 | // default to 0 for numeric types 58 | array.push_back(node.value_or(0.0)); 59 | } 60 | 61 | data->As()->set_data(array); 62 | break; 63 | } 64 | } else { 65 | input = raw->second.value_or(0.0); 66 | } 67 | 68 | data->As()->set_data(input); 69 | break; 70 | } 71 | case DataType::kInteger: 72 | { 73 | std::int64_t input; 74 | if (raw->second.is_array() && raw->second.as_array()) { 75 | if (raw->second.as_array()->size() == 1) { 76 | auto& front = raw->second.as_array()->front(); 77 | // downcast 78 | input = front.value() ? 79 | front.value().value() : 80 | front.value_or(0); 81 | } else if (raw->second.as_array()->size() > 1) { 82 | std::vector array; 83 | for (auto& node : *raw->second.as_array()) { 84 | // default to 0 for numeric types 85 | // downcast 86 | input = node.value() ? 87 | node.value().value() : 88 | node.value_or(0); 89 | array.push_back(input); 90 | } 91 | 92 | data->As()->set_data(array); 93 | break; 94 | } 95 | } else { 96 | // downcast 97 | input = raw->second.value() ? 98 | raw->second.value().value() : 99 | raw->second.value_or(0); 100 | } 101 | 102 | data->As()->set_data(input); 103 | break; 104 | } 105 | case DataType::kString: 106 | { 107 | std::basic_string input; 108 | if (raw->second.is_array() && raw->second.as_array()) { 109 | if (raw->second.as_array()->size() == 1 && 110 | raw->second.as_array()->front().as_string()) { 111 | input = raw->second.as_array()->front().as_string()->get(); 112 | } else if (raw->second.as_array()->size()) { 113 | std::vector> array; 114 | for (auto& node : *raw->second.as_array()) { 115 | if (node.as_string()) { 116 | array.push_back(node.as_string()->get()); 117 | } 118 | } 119 | 120 | data->As>()->set_data(array); 121 | break; 122 | } 123 | } else { 124 | if (raw->second.as_string()) { 125 | input = raw->second.as_string()->get(); 126 | } 127 | } 128 | 129 | data->As>()->set_data(input); 130 | break; 131 | } 132 | case DataType::kError: 133 | default: 134 | continue; 135 | } 136 | } 137 | } 138 | } 139 | 140 | std::stringstream os{}; 141 | os << _toml; 142 | _content = std::move(os.str()); 143 | 144 | __DEBUG("DKU_C: Parser#{}: Parsing finished", _id); 145 | } 146 | 147 | void Write(const std::string_view a_filePath) noexcept override 148 | { 149 | auto filePath = a_filePath.empty() ? _filepath.c_str() : a_filePath.data(); 150 | std::basic_ofstream file{ filePath }; 151 | if (!file.is_open() || !file) { 152 | ERROR("DKU_C: Parser#{}: Writing file failed! -> {}\nofstream cannot be opened", _id, filePath); 153 | } 154 | 155 | file << _toml; 156 | file.close(); 157 | 158 | __DEBUG("DKU_C: Parser#{}: Writing finished", _id); 159 | } 160 | 161 | void Generate() noexcept override 162 | { 163 | auto tableGnt = [](RNG&& a_rng) { 164 | toml::array collection; 165 | std::ranges::for_each(a_rng, [&collection](auto value) { 166 | collection.push_back(value); 167 | }); 168 | return collection; 169 | }; 170 | 171 | _toml.clear(); 172 | for (auto& [key, data] : _manager) { 173 | std::string sanitized = key.second.empty() ? "Global" : key.second.data(); 174 | auto [section, success] = _toml.insert(sanitized, toml::table{}); 175 | auto* table = section->second.as_table(); 176 | 177 | switch (data->get_type()) { 178 | case DataType::kBoolean: 179 | { 180 | table->insert(key.first, data->As()->get_data()); 181 | break; 182 | } 183 | case DataType::kDouble: 184 | { 185 | if (auto* raw = data->As(); raw->is_collection()) { 186 | table->insert(key.first, tableGnt(raw->get_collection())); 187 | } else { 188 | table->insert(key.first, raw->get_data()); 189 | } 190 | break; 191 | } 192 | case DataType::kInteger: 193 | { 194 | if (auto* raw = data->As(); raw->is_collection()) { 195 | table->insert(key.first, tableGnt(raw->get_collection())); 196 | } else { 197 | table->insert(key.first, raw->get_data()); 198 | } 199 | break; 200 | } 201 | case DataType::kString: 202 | { 203 | if (auto* raw = data->As>(); raw->is_collection()) { 204 | table->insert(key.first, tableGnt(raw->get_collection())); 205 | } else { 206 | table->insert(key.first, raw->get_data()); 207 | } 208 | break; 209 | } 210 | case DataType::kError: 211 | default: 212 | continue; 213 | } 214 | } 215 | } 216 | 217 | private: 218 | toml::table _toml; 219 | }; 220 | } // namespace detail 221 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Extra/Serialization/exception.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | namespace DKUtil::serialization 6 | { 7 | namespace exception 8 | { 9 | enum class code : index_type 10 | { 11 | failed_to_open_record, 12 | failed_to_write_data, 13 | failed_to_read_data, 14 | failed_to_resolve_formId, 15 | failed_to_revert, 16 | 17 | unexpected_type_mismatch, 18 | bindable_type_unpackable, 19 | internal_buffer_overflow, 20 | invalid_skse_interface, 21 | }; 22 | 23 | #define DKU_XS_EXCEPTION_FMT \ 24 | "Serialization error\n[{}]\n\nrecord: {}\nversion: {}\ntype: {}\n\n{}\n\n", \ 25 | dku::print_enum(a_code), a_header.name, a_header.version, typeid(T).name(), a_fmt 26 | 27 | template 28 | void report( 29 | code a_code, 30 | std::string_view a_fmt = {}, 31 | ISerializable::Header a_header = { "DKU_X: ", 0x1234, DKU_XS_VERSION }) 32 | { 33 | if constexpr (PROMPT) { 34 | ERROR(DKU_XS_EXCEPTION_FMT); 35 | } else { 36 | __WARN(DKU_XS_EXCEPTION_FMT); 37 | } 38 | } 39 | } // namespace exception 40 | } // namespace DKUtil::serialization -------------------------------------------------------------------------------- /include/DKUtil/Impl/Extra/Serialization/interface.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "exception.hpp" 4 | #include "shared.hpp" 5 | 6 | namespace DKUtil::serialization 7 | { 8 | namespace api 9 | { 10 | namespace detail 11 | { 12 | inline auto* check_skse_intfc(SKSE::SerializationInterface* a_intfc = nullptr) 13 | { 14 | const auto* intfc = a_intfc ? a_intfc : SKSE::GetSerializationInterface(); 15 | 16 | if (!a_intfc && !intfc) { 17 | exception::report(exception::code::invalid_skse_interface); 18 | } 19 | 20 | return intfc; 21 | } 22 | 23 | void save_all(SKSE::SerializationInterface* a_intfc) 24 | { 25 | #ifndef DKU_X_MOCK 26 | check_skse_intfc(a_intfc); 27 | 28 | for (auto* serializable : ISerializable::ManagedSerializables) { 29 | if (!a_intfc->OpenRecord(serializable->header.hash, serializable->header.version)) { 30 | exception::report(exception::code::failed_to_open_record, 31 | fmt::format("type: {}", serializable->header.typeInfo)); 32 | continue; 33 | } else { 34 | serializable->try_save(); 35 | } 36 | } 37 | #endif 38 | } 39 | 40 | void load_all(SKSE::SerializationInterface* a_intfc) 41 | { 42 | #ifndef DKU_X_MOCK 43 | check_skse_intfc(a_intfc); 44 | 45 | std::uint32_t hash, version, length; 46 | while (a_intfc->GetNextRecordInfo(hash, version, length)) { 47 | for (auto* serializable : ISerializable::ManagedSerializables) { 48 | serializable->try_load(hash, version); 49 | } 50 | } 51 | #endif 52 | } 53 | 54 | void revert_all(SKSE::SerializationInterface* a_intfc) 55 | { 56 | for (auto* serializable : ISerializable::ManagedSerializables) { 57 | serializable->try_revert(); 58 | } 59 | } 60 | } // namespace detail 61 | 62 | #ifndef DKU_X_MOCK 63 | 64 | # define DKU_X_WRITE(D, L, T) api::write(D, L, a_res.header) 65 | # define DKU_X_WRITE_SIZE(D) DKU_X_WRITE(std::addressof(D), sizeof(D), decltype(data)) 66 | # define DKU_X_READ(D, L, T) api::read(D, L, a_res.header) 67 | # define DKU_X_READ_SIZE(D) DKU_X_READ(std::addressof(D), sizeof(D), decltype(data)) 68 | # define DKU_X_REPORT() api::report() 69 | # define DKU_X_FORMID(F) api::resolveFormId(F) 70 | 71 | template 72 | inline void write(const void* a_buf, size_type a_length, const ISerializable::Header& a_header) noexcept 73 | { 74 | const auto* intfc = detail::check_skse_intfc(); 75 | if (!intfc->WriteRecordData(a_buf, a_length)) { 76 | exception::report(exception::code::failed_to_write_data, 77 | fmt::format("size: {}B", a_length), a_header); 78 | } 79 | } 80 | 81 | template 82 | inline void read(void* a_buf, size_type a_length, const ISerializable::Header& a_header) noexcept 83 | { 84 | const auto* intfc = detail::check_skse_intfc(); 85 | if (!intfc->ReadRecordData(a_buf, a_length)) { 86 | exception::report(exception::code::failed_to_read_data, 87 | fmt::format("size: {}B", a_length), a_header); 88 | } 89 | } 90 | 91 | inline RE::FormID resolveFormId(RE::FormID a_form) noexcept 92 | { 93 | RE::FormID newForm; 94 | const auto* intfc = detail::check_skse_intfc(); 95 | 96 | if (!intfc->ResolveFormID(a_form, newForm) && !newForm) { 97 | exception::report(exception::code::failed_to_resolve_formId, 98 | fmt::format("old formId: {:X} | resolved: {:X}", a_form, newForm)); 99 | } 100 | 101 | return newForm; 102 | } 103 | 104 | inline void report() noexcept 105 | { 106 | const auto* intfc = detail::check_skse_intfc(); 107 | __DEBUG("\nSKSE::SerializationInterface version: {}\nDKU_X_SERIALIZE version: {}", intfc->Version(), DKU_XS_VERSION); 108 | } 109 | #endif 110 | 111 | inline void RegisterSerializable(std::string a_ref = {}) noexcept 112 | { 113 | const auto* intfc = detail::check_skse_intfc(); 114 | 115 | a_ref += "_DKU_XS"; 116 | auto&& [key, hash] = colliding::make_hash_key(a_ref.data()); 117 | 118 | intfc->SetUniqueID(hash); 119 | intfc->SetSaveCallback(detail::save_all); 120 | intfc->SetLoadCallback(detail::load_all); 121 | intfc->SetRevertCallback(detail::revert_all); 122 | 123 | __DEBUG("DKU_XS: Registered serializables\nplugin handle {}\nplugin hash {}", key, hash); 124 | DKU_X_REPORT(); 125 | } 126 | } // DKUtil::serialization::api 127 | } // DKUtil::serialization 128 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Extra/Serialization/mock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "exception.hpp" 4 | #include "shared.hpp" 5 | 6 | #if defined(DKU_X_MOCK) 7 | # include 8 | 9 | # define DKU_X_WRITE(D, L, T) mock::write(D, L) 10 | # define DKU_X_WRITE_SIZE(D) DKU_X_WRITE(std::addressof(D), sizeof(D), decltype(D)) 11 | # define DKU_X_READ(D, L, T) mock::read(D, L) 12 | # define DKU_X_READ_SIZE(D) DKU_X_READ(std::addressof(D), sizeof(D), decltype(D)) 13 | # define DKU_X_REPORT() mock::report() 14 | # define DKU_X_FORMID(F) F 15 | 16 | namespace DKUtil::serialization 17 | { 18 | namespace mock 19 | { 20 | inline static std::array Buffer; 21 | inline static size_type ReadPos = 0; 22 | inline static size_type WritePos = 0; 23 | 24 | inline void read(void* a_buf, size_type a_length) noexcept 25 | { 26 | std::memcpy(a_buf, Buffer.data() + ReadPos, a_length); 27 | ReadPos += a_length; 28 | __INFO(fmt::format( 29 | fmt::bg(fmt::terminal_color::green) | fmt::fg(fmt::terminal_color::black), 30 | "[mock] read {}B", a_length)); 31 | } 32 | 33 | inline void write(const void* a_buf, size_type a_length) noexcept 34 | { 35 | std::memcpy(Buffer.data() + WritePos, a_buf, a_length); 36 | WritePos += a_length; 37 | __INFO(fmt::format( 38 | fmt::bg(fmt::terminal_color::cyan) | fmt::fg(fmt::terminal_color::black), 39 | "[mock] write {}B", a_length)); 40 | } 41 | 42 | inline void clear() noexcept 43 | { 44 | Buffer.fill(std::byte{ 0 }); 45 | ReadPos = 0; 46 | WritePos = 0; 47 | } 48 | 49 | inline void report() noexcept 50 | { 51 | __INFO("[mock] current read {}B", ReadPos); 52 | __INFO("[mock] current write {}B", WritePos); 53 | } 54 | } // namespace mock 55 | } // namespace DKUtil::serialization 56 | #endif 57 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Extra/Serialization/shared.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DKUtil/Logger.hpp" 4 | #include "DKUtil/Utility.hpp" 5 | 6 | #define DKU_X_SERIALIZE_MAJOR 1 7 | #define DKU_X_SERIALIZE_MINOR 0 8 | #define DKU_X_SERIALIZE_REVISION 0 9 | 10 | namespace DKUtil 11 | { 12 | constexpr auto DKU_XS_VERSION = DKU_X_SERIALIZE_MAJOR * 10000 + DKU_X_SERIALIZE_MINOR * 100 + DKU_X_SERIALIZE_REVISION; 13 | } // namespace DKUtil 14 | 15 | namespace DKUtil::serialization 16 | { 17 | using size_type = std::uint32_t; 18 | using index_type = std::uint32_t; 19 | using key_type = std::string; 20 | using hash_type = std::uint32_t; 21 | using version_type = std::uint32_t; 22 | 23 | namespace colliding 24 | { 25 | inline static constexpr hash_type KnownHash[] = { 26 | 'COMP', // CompletionistNG 27 | 'ISCR', // IndividualSoutCooldownRemake 28 | 'SAMS', // achievement system for skyrim 29 | 'SPIS', // SplitItemStacks 30 | 0xFD34899E, // NPCsUsePotions & AlchemyExpansion 31 | 0x68ED6325, // DiseaseOverhaul 32 | }; 33 | 34 | inline static index_type HashIndex = 0; 35 | 36 | inline constexpr auto make_hash_key(const char* a_key) 37 | { 38 | key_type key = dku::string::join({ PROJECT_NAME, a_key }, "_"); 39 | 40 | while (std::ranges::contains(KnownHash, dku::numbers::FNV_1A_32(key) + HashIndex)) { 41 | key += "_"; 42 | HashIndex++; 43 | } 44 | 45 | return std::make_pair(key, dku::numbers::FNV_1A_32(key) + HashIndex); 46 | } 47 | } // namespace colliding 48 | 49 | enum class ResolveOrder : std::uint32_t 50 | { 51 | kSave = 0, 52 | kLoad, 53 | kRevert, 54 | kInternal, 55 | }; 56 | 57 | struct ISerializable 58 | { 59 | struct Header 60 | { 61 | key_type name; 62 | hash_type hash{ 0 }; 63 | version_type version{ 0 }; 64 | std::string typeInfo; 65 | }; 66 | 67 | virtual void enable() noexcept 68 | { 69 | disable(); 70 | ManagedSerializables.emplace_back(this); 71 | } 72 | 73 | virtual void disable() noexcept 74 | { 75 | std::erase_if(ManagedSerializables, [this](auto* a_ptr) { return a_ptr == this; }); 76 | } 77 | 78 | virtual ~ISerializable() noexcept 79 | { 80 | disable(); 81 | } 82 | 83 | virtual void try_save() noexcept = 0; 84 | virtual void try_load(hash_type, version_type) noexcept = 0; 85 | virtual void try_revert() noexcept = 0; 86 | 87 | inline static std::vector ManagedSerializables = {}; 88 | 89 | Header header; 90 | }; 91 | } // namespace DKUtil::Serialization 92 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Extra/Serialization/variant.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottyduke/DKUtil/31d0bdf36d0c3979c346e6f20735f2cbb763d419/include/DKUtil/Impl/Extra/Serialization/variant.hpp -------------------------------------------------------------------------------- /include/DKUtil/Impl/Extra/xconsole.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DKUtil/Logger.hpp" 4 | #include "DKUtil/Utility.hpp" 5 | 6 | #define CONSOLE(...) \ 7 | { \ 8 | if (auto* console = RE::ConsoleLog::GetSingleton()) { \ 9 | auto fmt = fmt::format(__VA_ARGS__); \ 10 | __INFO("[console] {}", fmt); \ 11 | console->Print(fmt::format("{}->{}", PROJECT_NAME, fmt).c_str()); \ 12 | } \ 13 | } 14 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Extra/xserialize.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * 5 | * 1.0.0 6 | * Basic serialization and deserialization; 7 | * Support (should) all types that are commonly used; 8 | * Flatten user defined types for thorough serialization; 9 | * Concept constrained auto templates; 10 | * 11 | */ 12 | 13 | #include "DKUtil/Logger.hpp" 14 | #include "DKUtil/Utility.hpp" 15 | 16 | //#define DKU_X_MOCK 17 | 18 | #ifndef DKU_X_STRICT_SERIALIZATION 19 | # define DKU_X_STRICT_SERIALIZATION false 20 | #endif 21 | 22 | #include "Serialization/exception.hpp" 23 | #include "Serialization/interface.hpp" 24 | #include "Serialization/mock.hpp" 25 | #include "Serialization/resolver.hpp" 26 | #include "Serialization/shared.hpp" 27 | #include "Serialization/variant.hpp" 28 | 29 | namespace DKUtil::serialization 30 | { 31 | template 32 | struct Serializable : ISerializable 33 | { 34 | using type = std::remove_cvref_t; 35 | using resolver_func_t = std::add_pointer_t; 36 | 37 | constexpr Serializable() noexcept 38 | { 39 | auto&& [name, hash] = colliding::make_hash_key(HEADER.c); 40 | 41 | header.name = name; 42 | header.hash = hash; 43 | header.version = VERSION; 44 | header.typeInfo = typeid(type).name(); 45 | 46 | ISerializable::enable(); 47 | } 48 | 49 | constexpr Serializable(const type& a_data) noexcept : 50 | Serializable() 51 | { 52 | _data = a_data; 53 | } 54 | 55 | constexpr Serializable(type&& a_data) noexcept : 56 | Serializable() 57 | { 58 | _data = std::move(a_data); 59 | } 60 | 61 | constexpr Serializable& operator=(const type& a_data) noexcept 62 | { 63 | _data = a_data; 64 | return *this; 65 | } 66 | 67 | constexpr Serializable& operator=(type&& a_data) noexcept 68 | { 69 | _data = std::move(a_data); 70 | return *this; 71 | } 72 | 73 | ~Serializable() noexcept = default; 74 | 75 | constexpr auto* operator->() noexcept { return std::addressof(_data); } 76 | constexpr auto& operator*() noexcept { return _data; } 77 | constexpr auto get() noexcept { return _data; } 78 | 79 | constexpr void add_resolver(resolver_func_t a_func) noexcept 80 | { 81 | _resolvers.emplace_back(a_func); 82 | } 83 | 84 | virtual void try_save() noexcept override 85 | { 86 | __DEBUG("saving {}...", header.name); 87 | 88 | resolver::resolve_save(header, _data); 89 | 90 | for (auto& resolver : _resolvers) { 91 | resolver(_data, ResolveOrder::kSave); 92 | } 93 | 94 | __DEBUG("...done saving {}", header.name); 95 | } 96 | 97 | virtual void try_load(hash_type a_hash, version_type a_version) noexcept override 98 | { 99 | if (a_hash != header.hash) { 100 | return; 101 | } 102 | 103 | if (a_version != header.version) { 104 | // TODO: handle version variant 105 | return; 106 | } 107 | 108 | __DEBUG("loading {}...", header.name); 109 | 110 | _data = resolver::resolve_load(header, _data); 111 | 112 | for (auto& resolver : _resolvers) { 113 | resolver(_data, ResolveOrder::kLoad); 114 | } 115 | 116 | __DEBUG("...done loading {}", header.name); 117 | } 118 | 119 | virtual void try_revert() noexcept override 120 | { 121 | __DEBUG("reverting {}...", header.name); 122 | 123 | _data = type{}; 124 | 125 | for (auto& resolver : _resolvers) { 126 | resolver(_data, ResolveOrder::kRevert); 127 | } 128 | 129 | __DEBUG("...done reverting {}", header.name); 130 | } 131 | 132 | private: 133 | type _data; 134 | std::vector _resolvers; 135 | }; 136 | } // namespace DKUtil::serialization 137 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Hook/API.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "assembly.hpp" 4 | #include "internal.hpp" 5 | #include "shared.hpp" 6 | #include "trampoline.hpp" 7 | 8 | namespace DKUtil::Hook 9 | { 10 | using namespace model::concepts; 11 | 12 | /** \brief Apply assembly patch in the body of execution 13 | * \param a_address : Memory address of the BEGINNING of target function 14 | * \param a_offset : Offset pairs for of cave entry from the head of function 15 | * \param a_patch : Assembly patch 16 | * \param a_forward : Skip the rest of NOPs until next valid opcode 17 | * \return ASMPatchHandle 18 | */ 19 | inline auto AddASMPatch( 20 | const std::uintptr_t a_address, 21 | const offset_pair a_offset, 22 | const Xbyak::CodeGenerator* a_xbyak, 23 | const bool a_forward = true) noexcept 24 | { 25 | return AddASMPatch(a_address, a_offset, std::make_pair(a_xbyak->getCode(), a_xbyak->getSize()), a_forward); 26 | } 27 | 28 | /** \brief Apply assembly patch in the body of execution 29 | * \param a_address : Memory address of the BEGINNING of target function 30 | * \param a_offset : Offset pairs for of cave entry from the head of function 31 | * \param a_patch : Assembly patch 32 | * \param a_forward : Skip the rest of NOPs until next valid opcode 33 | * \return ASMPatchHandle 34 | */ 35 | inline auto AddASMPatch( 36 | const std::uintptr_t a_address, 37 | const offset_pair a_offset, 38 | const Patch* a_patch, 39 | const bool a_forward = true) noexcept 40 | { 41 | return AddASMPatch(a_address, a_offset, std::make_pair(a_patch->Data, a_patch->Size), a_forward); 42 | } 43 | 44 | /** \brief Branch to hook function in the body of execution from target function. 45 | * \param a_offset : Offset pairs for of cave entry from the head of function 46 | * \param a_address : Memory address of the BEGINNING of target function 47 | * \param a_funcInfo : FUNC_INFO or RT_INFO wrapper of hook function 48 | * \param a_prolog : Prolog patch before detouring to hook function 49 | * \param a_epilog : Epilog patch after returning from hook function 50 | * \param a_flag : Specifies operation on cave hook 51 | * \return CaveHookHandle 52 | */ 53 | inline auto AddCaveHook( 54 | const std::uintptr_t a_address, 55 | const offset_pair a_offset, 56 | const FuncInfo a_funcInfo, 57 | const Xbyak::CodeGenerator* a_prolog, 58 | const Xbyak::CodeGenerator* a_epilog, 59 | model::enumeration a_flag = HookFlag::kSkipNOP) noexcept 60 | { 61 | return AddCaveHook( 62 | a_address, a_offset, a_funcInfo, 63 | a_prolog ? std::make_pair(a_prolog->getCode(), a_prolog->getSize()) : std::make_pair(nullptr, 0), 64 | a_epilog ? std::make_pair(a_epilog->getCode(), a_epilog->getSize()) : std::make_pair(nullptr, 0), 65 | a_flag); 66 | } 67 | 68 | /** \brief Branch to hook function in the body of execution from target function. 69 | * \param a_offset : Offset pairs for of cave entry from the head of function 70 | * \param a_address : Memory address of the BEGINNING of target function 71 | * \param a_funcInfo : FUNC_INFO or RT_INFO wrapper of hook function 72 | * \param a_prolog : Prolog patch before detouring to hook function 73 | * \param a_epilog : Epilog patch after returning from hook function 74 | * \param a_flag : Specifies operation on cave hook 75 | * \return CaveHookHandle 76 | */ 77 | inline auto AddCaveHook( 78 | const std::uintptr_t a_address, 79 | const offset_pair a_offset, 80 | const FuncInfo a_funcInfo, 81 | const Patch* a_prolog, 82 | const Patch* a_epilog, 83 | model::enumeration a_flag = HookFlag::kSkipNOP) noexcept 84 | { 85 | return AddCaveHook( 86 | a_address, a_offset, a_funcInfo, 87 | a_prolog ? std::make_pair(a_prolog->Data, a_prolog->Size) : std::make_pair(nullptr, 0), 88 | a_epilog ? std::make_pair(a_epilog->Data, a_epilog->Size) : std::make_pair(nullptr, 0), 89 | a_flag); 90 | } 91 | 92 | inline auto AddVMTHook( 93 | void* a_vtbl, 94 | const std::uint16_t a_index, 95 | const FuncInfo a_funcInfo, 96 | const Xbyak::CodeGenerator* a_xbyak) noexcept 97 | { 98 | return AddVMTHook(a_vtbl, a_index, a_funcInfo, std::make_pair(a_xbyak->getCode(), a_xbyak->getSize())); 99 | } 100 | 101 | inline auto AddVMTHook( 102 | void* a_vtbl, 103 | const std::uint16_t a_index, 104 | const FuncInfo a_funcInfo, 105 | const Patch* a_patch) noexcept 106 | { 107 | return AddVMTHook(a_vtbl, a_index, a_funcInfo, std::make_pair(a_patch->Data, a_patch->Size)); 108 | } 109 | 110 | inline auto AddIATHook( 111 | std::string_view a_moduleName, 112 | std::string_view a_libraryName, 113 | std::string_view a_importName, 114 | const FuncInfo a_funcInfo, 115 | const Xbyak::CodeGenerator* a_xbyak) noexcept 116 | { 117 | return AddIATHook(a_moduleName, a_libraryName, a_importName, a_funcInfo, std::make_pair(a_xbyak->getCode(), a_xbyak->getSize())); 118 | } 119 | 120 | inline auto AddIATHook( 121 | std::string_view a_moduleName, 122 | std::string_view a_libraryName, 123 | std::string_view a_importName, 124 | const FuncInfo a_funcInfo, 125 | const Patch* a_patch) noexcept 126 | { 127 | return AddIATHook(a_moduleName, a_libraryName, a_importName, a_funcInfo, std::make_pair(a_patch->Data, a_patch->Size)); 128 | } 129 | 130 | /** \brief Relocate a jmpsite with target hook function 131 | * \brief This API exists for compatiblity reason with CLib-style invocations, hook enabled by default 132 | * \param : Length of source instruction 133 | * \param a_src : Address of jmp instruction 134 | * \param a_dst : Destination function 135 | * \return Transitive RelHookHandle that can be converted to F 136 | */ 137 | template 138 | requires(dku_memory) 139 | inline auto write_branch( 140 | const std::uintptr_t a_src, 141 | F a_dst) noexcept 142 | { 143 | auto handle = AddRelHook(a_src, unrestricted_cast(a_dst)); 144 | handle->Enable(); 145 | return std::move(*handle.get()); 146 | } 147 | 148 | /** \brief Relocate a callsite with target hook function 149 | * \brief This API exists for compatiblity reason with CLib-style invocations, hook enabled by default 150 | * \param : Length of source instruction 151 | * \param a_src : Address of call instruction 152 | * \param a_dst : Destination function 153 | * \return Transitive RelHookHandle that can be converted to F 154 | */ 155 | template 156 | requires(dku_memory) 157 | inline auto write_call( 158 | const std::uintptr_t a_src, 159 | F a_dst) noexcept 160 | { 161 | auto handle = AddRelHook(a_src, unrestricted_cast(a_dst)); 162 | handle->Enable(); 163 | return std::move(*handle.get()); 164 | } 165 | 166 | /** \brief Relocate a callsite with target hook function 167 | * \brief This API preserves regular and sse registers across non-volatile call boundaries 168 | * \param : Length of source instruction 169 | * \param a_src : Address of call instruction 170 | * \param a_dst : Destination function 171 | * \param a_regs : Regular registers to preserve as non volatile 172 | * \param a_simd : SSE registers to preserve as non volatile 173 | * \return F, the CaveHookHandle is discarded 174 | */ 175 | template 176 | inline auto write_call_ex( 177 | const std::uintptr_t a_src, 178 | F a_dst, 179 | enumeration a_regs = { Register::NONE }, 180 | enumeration a_simd = { SIMD::NONE }) noexcept 181 | { 182 | dku_assert(a_regs.none(Register::NONE) || a_simd.none(SIMD::NONE), 183 | "DKU_H: Cannot write_call_ex with empty flags"); 184 | 185 | // get original disp call 186 | auto func = GetDisp(a_src); 187 | 188 | // transform into CaveHook 189 | auto [prolog1, epilog1] = JIT::MakeNonVolatilePatch(a_regs); 190 | auto [prolog2, epilog2] = JIT::MakeNonVolatilePatch(a_simd); 191 | 192 | // combine patches 193 | if (prolog2.Data && epilog2.Data) { 194 | prolog1.Append(prolog2); 195 | epilog2.Append(epilog1); 196 | } 197 | 198 | auto handle = AddCaveHook( 199 | a_src, 200 | { 0, N }, 201 | RT_INFO(a_dst, fmt::format("write_call_ex<{}>", N)), 202 | &prolog1, 203 | &epilog2); 204 | handle->Enable(); 205 | 206 | return std::bit_cast(func); 207 | } 208 | } // namespace DKUtil::Hook 209 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Hook/Internal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "assembly.hpp" 4 | #include "jit.hpp" 5 | #include "trampoline.hpp" 6 | 7 | namespace DKUtil::Hook 8 | { 9 | using namespace Assembly; 10 | 11 | class HookHandle 12 | { 13 | public: 14 | virtual ~HookHandle() = default; 15 | 16 | virtual void Enable() noexcept = 0; 17 | virtual void Disable() noexcept = 0; 18 | 19 | template derived_t> 20 | constexpr derived_t* As() noexcept 21 | { 22 | return dynamic_cast(this); 23 | } 24 | 25 | // write directly to internal trampoline ptr 26 | template 27 | requires(!std::is_pointer_v) 28 | void Write(T a_in) noexcept 29 | { 30 | WriteData(TramPtr, std::addressof(a_in), sizeof(a_in), true); 31 | TramPtr += sizeof(a_in); 32 | } 33 | 34 | void Write(const void* a_src, std::size_t a_size) noexcept 35 | { 36 | WriteData(TramPtr, a_src, a_size, true); 37 | TramPtr += a_size; 38 | } 39 | 40 | const std::uintptr_t Address; 41 | const std::uintptr_t TramEntry; 42 | std::uintptr_t TramPtr{ 0x0 }; 43 | 44 | protected: 45 | HookHandle(const std::uintptr_t a_address, const std::uintptr_t a_tramEntry) : 46 | Address(a_address), TramEntry(a_tramEntry), TramPtr(a_tramEntry) 47 | {} 48 | }; 49 | } // namespace DKUtil::Hook 50 | 51 | #define DKU_H_INTERNAL_IMPORTS DKU_H_VERSION_MAJOR 52 | 53 | #include "Internal/ASMPatch.hpp" 54 | #include "Internal/CaveHook.hpp" 55 | #include "Internal/IATHook.hpp" 56 | #include "Internal/RelHook.hpp" 57 | #include "Internal/VMTHook.hpp" 58 | 59 | #undef DKU_H_INTERNAL_IMPORTS 60 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Hook/Internal/ASMPatch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(DKU_H_INTERNAL_IMPORTS) 4 | # error Incorrect DKUtil::Hook internal import order. 5 | #endif 6 | 7 | namespace DKUtil::Hook 8 | { 9 | class ASMPatchHandle : public HookHandle 10 | { 11 | public: 12 | // execution address, 13 | ASMPatchHandle( 14 | const std::uintptr_t a_address, 15 | const offset_pair a_offset) noexcept : 16 | HookHandle(a_address, a_address + a_offset.first), 17 | Offset(a_offset), PatchSize(a_offset.second - a_offset.first) 18 | { 19 | OldBytes.resize(PatchSize); 20 | PatchBuf.resize(PatchSize, NOP); 21 | std::memcpy(OldBytes.data(), AsPointer(TramEntry), PatchSize); 22 | 23 | __DEBUG("DKU_H: Patch capacity: {} bytes\nPatch entry @ {:X}", PatchSize, TramEntry); 24 | } 25 | 26 | // TramEntry is the CaveEntry for asm patch 27 | void Enable() noexcept override 28 | { 29 | WriteData(TramEntry, PatchBuf.data(), PatchSize, false); 30 | __DEBUG("DKU_H: Enabled ASM patch @ {:X}", TramEntry); 31 | } 32 | 33 | void Disable() noexcept override 34 | { 35 | WriteData(TramEntry, OldBytes.data(), PatchSize, false); 36 | __DEBUG("DKU_H: Disabled ASM patch @ {:X}", TramEntry); 37 | } 38 | 39 | const offset_pair Offset; 40 | const std::size_t PatchSize; 41 | std::vector OldBytes{}; 42 | std::vector PatchBuf{}; 43 | }; 44 | 45 | /** \brief Apply assembly patch in the body of execution 46 | * \param a_address : Memory address of the BEGINNING of target function 47 | * \param a_offset : Offset pairs for of cave entry from the head of function 48 | * \param a_patch : Assembly patch 49 | * \param a_forward : Skip the rest of NOPs until next valid opcode 50 | * \return ASMPatchHandle 51 | */ 52 | [[nodiscard]] inline auto AddASMPatch( 53 | const std::uintptr_t a_address, 54 | const offset_pair a_offset, 55 | const unpacked_data a_patch = std::make_pair(nullptr, 0), 56 | const bool a_forward = true) noexcept 57 | { 58 | dku_assert(a_address && a_patch.first && a_patch.second, 59 | "DKU_H: Invalid ASM patch"); 60 | 61 | auto handle = std::make_unique(a_address, a_offset); 62 | 63 | if (a_patch.second > (a_offset.second - a_offset.first)) { 64 | __DEBUG("DKU_H: ASM patch size exceeds the patch capacity, enabled trampoline"); 65 | dku_assert((a_offset.second - a_offset.first) >= sizeof(JmpRel), 66 | "DKU_H: ASM patch size exceeds the patch capacity & cannot fulfill the minimal trampoline requirement"); 67 | 68 | JmpRel asmDetour; // cave -> tram 69 | JmpRel asmReturn; // tram -> cave 70 | 71 | handle->TramPtr = TRAM_ALLOC(0); 72 | __DEBUG("DKU_H: ASM patch trampoline entry -> {:X}", handle->TramPtr); 73 | 74 | std::ptrdiff_t disp = handle->TramPtr - handle->TramEntry - sizeof(asmDetour); 75 | assert_trampoline_range(disp); 76 | 77 | asmDetour.Disp = static_cast(disp); 78 | AsMemCpy(handle->PatchBuf.data(), asmDetour); 79 | 80 | handle->Write(a_patch.first, a_patch.second); 81 | 82 | if (a_forward) { 83 | asmReturn.Disp = static_cast(handle->TramEntry + handle->PatchSize - handle->TramPtr - sizeof(asmReturn)); 84 | } else { 85 | asmReturn.Disp = static_cast(handle->TramEntry + a_patch.second - handle->TramPtr - sizeof(asmReturn)); 86 | } 87 | 88 | handle->Write(asmReturn); 89 | } else { 90 | std::memcpy(handle->PatchBuf.data(), a_patch.first, a_patch.second); 91 | 92 | if (a_forward && handle->PatchSize > (a_patch.second * ASM_MINIMUM_SKIP + sizeof(JmpRel))) { 93 | JmpRel asmForward; 94 | 95 | asmForward.Disp = static_cast(handle->TramEntry + handle->PatchSize - handle->TramEntry - a_patch.second - sizeof(asmForward)); 96 | AsMemCpy(handle->PatchBuf.data() + a_patch.second, asmForward); 97 | __DEBUG("DKU_H: ASM patch forwarded"); 98 | } 99 | } 100 | 101 | return std::move(handle); 102 | } 103 | } // namespace DKUtil::Hook 104 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Hook/Internal/CaveHook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(DKU_H_INTERNAL_IMPORTS) 4 | # error Incorrect DKUtil::Hook internal import order. 5 | #endif 6 | 7 | namespace DKUtil::Hook 8 | { 9 | class CaveHookHandle : public HookHandle 10 | { 11 | public: 12 | // execution address, trampoline address, 13 | CaveHookHandle( 14 | const std::uintptr_t a_address, 15 | const std::uintptr_t a_tramPtr, 16 | const offset_pair a_offset) noexcept : 17 | HookHandle(a_address, a_tramPtr), 18 | Offset(a_offset), CaveSize(a_offset.second - a_offset.first), CaveEntry(Address + a_offset.first), CavePtr(Address + a_offset.first) 19 | { 20 | OldBytes.resize(CaveSize); 21 | CaveBuf.resize(CaveSize, NOP); 22 | std::memcpy(OldBytes.data(), AsPointer(CaveEntry), CaveSize); 23 | 24 | __DEBUG( 25 | "DKU_H: Cave capacity: {} bytes\n" 26 | "cave entry : {:X}\n" 27 | "tram entry : {:X}", 28 | CaveSize, CaveEntry, TramEntry); 29 | } 30 | 31 | void Enable() noexcept override 32 | { 33 | WriteData(CavePtr, CaveBuf.data(), CaveSize, false); 34 | CavePtr += CaveSize; 35 | __DEBUG("DKU_H: Enabled cave hook @ {:X}", CaveEntry); 36 | } 37 | 38 | void Disable() noexcept override 39 | { 40 | WriteData(CavePtr - CaveSize, OldBytes.data(), CaveSize, false); 41 | CavePtr -= CaveSize; 42 | __DEBUG("DKU_H: Disabled cave hook @ {:X}", CaveEntry); 43 | } 44 | 45 | const offset_pair Offset; 46 | const std::size_t CaveSize; 47 | const std::uintptr_t CaveEntry; 48 | std::uintptr_t CavePtr{ 0x0 }; 49 | std::vector OldBytes{}; 50 | std::vector CaveBuf{}; 51 | }; 52 | 53 | /** \brief Branch to hook function in the body of execution from target function. 54 | * \param a_offset : Offset pairs for of cave entry from the head of function 55 | * \param a_address : Memory address of the BEGINNING of target function 56 | * \param a_funcInfo : FUNC_INFO or RT_INFO wrapper of hook function 57 | * \param a_prolog : Prolog patch before detouring to hook function 58 | * \param a_epilog : Epilog patch after returning from hook function 59 | * \param a_flag : Specifies operation on cave hook 60 | * \return CaveHookHandle 61 | */ 62 | [[nodiscard]] inline auto AddCaveHook( 63 | const std::uintptr_t a_address, 64 | const offset_pair a_offset, 65 | const FuncInfo a_funcInfo, 66 | const unpacked_data a_prolog = std::make_pair(nullptr, 0), 67 | const unpacked_data a_epilog = std::make_pair(nullptr, 0), 68 | model::enumeration a_flag = HookFlag::kSkipNOP) noexcept 69 | { 70 | if (a_offset.second - a_offset.first == 5) { 71 | a_flag.reset(HookFlag::kSkipNOP); 72 | } 73 | 74 | JmpRel asmDetour; // cave -> tram 75 | JmpRel asmReturn; // tram -> cave 76 | SubRsp asmSub; 77 | AddRsp asmAdd; 78 | CallRip asmBranch; 79 | 80 | // trampoline layout 81 | // [qword imm64] <- tram entry after this 82 | // [stolen] <- kRestoreBeforeProlog 83 | // [prolog] <- cave detour entry 84 | // [stolen] <- kRestoreAfterProlog 85 | // [alloc stack] 86 | // [call qword ptr [rip + disp]] 87 | // [dealloc stack] 88 | // [stolen] <- kRestoreBeforeEpilog 89 | // [epilog] 90 | // [stolen] <- kRestoreAfterEpilog 91 | // [jmp rel32] 92 | auto tramPtr = TRAM_ALLOC(0); 93 | 94 | // tram entry 95 | WriteImm(tramPtr, a_funcInfo.address(), true); 96 | tramPtr += sizeof(a_funcInfo.address()); 97 | __DEBUG( 98 | "DKU_H: Detouring...\n" 99 | "from : {}.{:X}\n" 100 | "to : {} @ {}.{:X}", 101 | GetModuleName(), a_address + a_offset.first, a_funcInfo.name(), PROJECT_NAME, a_funcInfo.address()); 102 | 103 | auto handle = std::make_unique(a_address, tramPtr, a_offset); 104 | 105 | std::ptrdiff_t disp = handle->TramPtr - handle->CavePtr - sizeof(asmDetour); 106 | assert_trampoline_range(disp); 107 | 108 | asmDetour.Disp = static_cast(disp); 109 | AsMemCpy(handle->CaveBuf.data(), asmDetour); 110 | 111 | if (a_flag.any(HookFlag::kRestoreBeforeProlog)) { 112 | handle->Write(handle->OldBytes.data(), handle->CaveSize); 113 | asmBranch.Disp -= static_cast(handle->CaveSize); 114 | 115 | a_flag.reset(HookFlag::kRestoreBeforeProlog); 116 | } 117 | 118 | if (a_prolog.first && a_prolog.second) { 119 | handle->Write(a_prolog.first, a_prolog.second); 120 | asmBranch.Disp -= static_cast(a_prolog.second); 121 | } 122 | 123 | if (a_flag.any(HookFlag::kRestoreAfterProlog)) { 124 | handle->Write(handle->OldBytes.data(), handle->CaveSize); 125 | asmBranch.Disp -= static_cast(handle->CaveSize); 126 | 127 | a_flag.reset(HookFlag::kRestoreBeforeEpilog, HookFlag::kRestoreAfterEpilog); 128 | } 129 | 130 | // alloc stack space 131 | asmSub.Size = ASM_STACK_ALLOC_SIZE; 132 | asmAdd.Size = ASM_STACK_ALLOC_SIZE; 133 | 134 | handle->Write(asmSub); 135 | asmBranch.Disp -= static_cast(sizeof(asmSub)); 136 | 137 | // write call 138 | asmBranch.Disp -= static_cast(sizeof(Imm64)); 139 | asmBranch.Disp -= static_cast(sizeof(asmBranch)); 140 | handle->Write(asmBranch); 141 | 142 | // dealloc stack space 143 | handle->Write(asmAdd); 144 | 145 | if (a_flag.any(HookFlag::kRestoreBeforeEpilog)) { 146 | handle->Write(handle->OldBytes.data(), handle->CaveSize); 147 | a_flag.reset(HookFlag::kRestoreAfterEpilog); 148 | } 149 | 150 | if (a_epilog.first && a_epilog.second) { 151 | handle->Write(a_epilog.first, a_epilog.second); 152 | } 153 | 154 | if (a_flag.any(HookFlag::kRestoreAfterEpilog)) { 155 | handle->Write(handle->OldBytes.data(), handle->CaveSize); 156 | } 157 | 158 | if (a_flag.any(HookFlag::kSkipNOP)) { 159 | asmReturn.Disp = static_cast(handle->Address + handle->Offset.second - handle->TramPtr - sizeof(asmReturn)); 160 | } else { 161 | asmReturn.Disp = static_cast(handle->CavePtr + sizeof(asmDetour) - handle->TramPtr - sizeof(asmReturn)); 162 | } 163 | 164 | handle->Write(asmReturn); 165 | 166 | return std::move(handle); 167 | } 168 | } // namespace DKUtil::Hook 169 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Hook/Internal/IATHook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(DKU_H_INTERNAL_IMPORTS) 4 | # error Incorrect DKUtil::Hook internal import order. 5 | #endif 6 | 7 | namespace DKUtil::Hook 8 | { 9 | class IATHookHandle : public HookHandle 10 | { 11 | public: 12 | // IAT address, target func address, IAT func name, target func name 13 | IATHookHandle( 14 | const std::uintptr_t a_address, 15 | const std::uintptr_t a_tramEntry, 16 | std::string_view a_importName, 17 | std::string_view a_funcName) noexcept : 18 | HookHandle(a_address, a_tramEntry), 19 | OldAddress(*dku::Hook::unrestricted_cast(Address)) 20 | { 21 | __DEBUG( 22 | "DKU_H: IAT @ {:X}\n" 23 | "old : {} @ {:X}\n" 24 | "new : {} @ {}.{:X}", 25 | a_address, a_importName, OldAddress, a_funcName, PROJECT_NAME, a_tramEntry); 26 | } 27 | 28 | void Enable() noexcept override 29 | { 30 | WriteImm(Address, TramEntry, false); 31 | __DEBUG("DKU_H: Enabled IAT hook"); 32 | } 33 | 34 | void Disable() noexcept override 35 | { 36 | WriteImm(Address, OldAddress, false); 37 | __DEBUG("DKU_H: Disabled IAT hook"); 38 | } 39 | 40 | const std::uintptr_t OldAddress; 41 | }; 42 | 43 | /** Swaps a import address table method with target function 44 | * \param a_moduleName : Name of the target module that import address table resides 45 | * \param a_methodName : Name of the target method to be swapped 46 | * \param a_funcInfo : FUNC_INFO or RT_INFO wrapped function 47 | * \param a_patch : Prolog patch before detouring to target function 48 | * @return IATHookHandle 49 | */ 50 | [[nodiscard]] inline auto AddIATHook( 51 | std::string_view a_moduleName, 52 | std::string_view a_libraryName, 53 | std::string_view a_importName, 54 | const FuncInfo a_funcInfo, 55 | const unpacked_data a_patch = std::make_pair(nullptr, 0)) noexcept 56 | { 57 | const auto iat = AsAddress(GetImportAddress(a_moduleName, a_libraryName, a_importName)); 58 | 59 | if (a_patch.first && a_patch.second) { 60 | auto tramPtr = TRAM_ALLOC(0); 61 | 62 | CallRip asmBranch; 63 | 64 | WriteImm(tramPtr, a_funcInfo.address(), true); 65 | tramPtr += sizeof(a_funcInfo.address()); 66 | asmBranch.Disp -= static_cast(sizeof(Imm64)); 67 | 68 | auto handle = std::make_unique(iat, tramPtr, a_importName, a_funcInfo.name().data()); 69 | 70 | handle->Write(a_patch.first, a_patch.second); 71 | asmBranch.Disp -= static_cast(a_patch.second); 72 | 73 | asmBranch.Disp -= static_cast(sizeof(asmBranch)); 74 | handle->Write(asmBranch); 75 | 76 | return std::move(handle); 77 | } else { 78 | auto handle = std::make_unique(iat, a_funcInfo.address(), a_importName, a_funcInfo.name().data()); 79 | return std::move(handle); 80 | } 81 | 82 | FATAL("DKU_H: IAT reached the end of table\n\nMethod {} not found", a_importName); 83 | } 84 | } // namespace DKUtil::Hook 85 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Hook/Internal/RelHook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(DKU_H_INTERNAL_IMPORTS) 4 | # error Incorrect DKUtil::Hook internal import order. 5 | #endif 6 | 7 | namespace DKUtil::Hook 8 | { 9 | class RelHookHandle : public HookHandle 10 | { 11 | public: 12 | RelHookHandle( 13 | const std::uintptr_t a_callsite, 14 | const std::uintptr_t a_tramPtr, 15 | const std::uintptr_t a_dstAddr, 16 | const std::size_t a_opSize) noexcept : 17 | HookHandle(a_callsite, a_tramPtr), 18 | OpSeqSize(a_opSize), 19 | OriginalFunc(GetDisp(a_callsite)), 20 | Destination(a_dstAddr) 21 | { 22 | __DEBUG( 23 | "DKU_H: Relocation<{}>\n" 24 | "from : {}.{:X}\n" 25 | "call : {:X}\n" 26 | "to : {}.{:X}", 27 | OpSeqSize, GetModuleName(), Address, OriginalFunc, PROJECT_NAME, Destination); 28 | } 29 | 30 | void Enable() noexcept override 31 | { 32 | WriteData(Address, Detour.data(), Detour.size(), false); 33 | __DEBUG("DKU_H: Enabled relocation hook @ {:X}", Address); 34 | } 35 | 36 | void Disable() noexcept override 37 | { 38 | WriteData(Address, OldBytes.data(), OldBytes.size(), false); 39 | __DEBUG("DKU_H: Disabled relocation hook @ {:X}", Address); 40 | } 41 | 42 | template 43 | requires(model::concepts::dku_memory) 44 | constexpr operator F() const noexcept 45 | { 46 | return std::bit_cast(OriginalFunc); 47 | } 48 | 49 | const std::size_t OpSeqSize; 50 | const Imm64 OriginalFunc; 51 | Imm64 Destination; 52 | std::vector OldBytes{}; 53 | std::vector Detour{}; 54 | }; 55 | 56 | /** \brief Relocate a call/jmp site with target hook function 57 | * \param : Length of source instruction 58 | * \param : Return or branch (call/jmp) 59 | * \param a_src : Address of call/jmp instruction 60 | * \param a_dst : Destination function 61 | * \return RelHookHandle 62 | */ 63 | template 64 | inline auto AddRelHook( 65 | std::uintptr_t a_src, 66 | std::uintptr_t a_dst) // noexcept 67 | { 68 | static_assert(N == 5 || N == 6, "unsupported instruction size"); 69 | using DetourAsm = std::conditional_t, _BranchIndirect>; 70 | 71 | auto tramPtr = TRAM_ALLOC(0); 72 | 73 | // tram entry 74 | WriteImm(tramPtr, a_dst, true); 75 | tramPtr += sizeof(a_dst); 76 | 77 | // handle 78 | auto handle = std::make_unique(a_src, tramPtr, a_dst, N); 79 | handle->OldBytes.resize(N); 80 | std::memcpy(handle->OldBytes.data(), AsPointer(a_src), N); 81 | handle->Detour.resize(N, NOP); 82 | 83 | // detour 84 | DetourAsm asmDetour{}; 85 | std::ptrdiff_t disp = tramPtr - a_src - sizeof(asmDetour); 86 | if constexpr (N == 6) { 87 | disp -= sizeof(a_dst); 88 | } 89 | assert_trampoline_range(disp); 90 | 91 | asmDetour.Disp = static_cast(disp); 92 | AsMemCpy(handle->Detour.data(), asmDetour); 93 | 94 | if constexpr (N == 5) { 95 | // branch 96 | JmpRip asmBranch; 97 | asmBranch.Disp -= static_cast(sizeof(Imm64)); 98 | asmBranch.Disp -= static_cast(sizeof(asmBranch)); 99 | 100 | handle->Write(asmBranch); 101 | } 102 | 103 | return std::move(handle); 104 | } 105 | } // namespace DKUtil::Hook 106 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Hook/Internal/VMTHook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if !defined(DKU_H_INTERNAL_IMPORTS) 4 | # error Incorrect DKUtil::Hook internal import order. 5 | #endif 6 | 7 | namespace DKUtil::Hook 8 | { 9 | 10 | class VMTHookHandle : public HookHandle 11 | { 12 | public: 13 | // VTBL address, target func address, VTBL func index 14 | VMTHookHandle( 15 | const std::uintptr_t a_address, 16 | const std::uintptr_t a_tramEntry, 17 | const std::uint16_t a_index) noexcept : 18 | HookHandle(TblToAbs(a_address, a_index), a_tramEntry), 19 | OldAddress(*std::bit_cast(Address)) 20 | { 21 | __DEBUG("DKU_H: VMT @ {:X} [{}]\nOld entry @ {:X} | New entry @ {:X}", a_address, a_index, OldAddress, TramEntry); 22 | } 23 | 24 | void Enable() noexcept override 25 | { 26 | WriteImm(Address, TramEntry, false); 27 | __DEBUG("DKU_H: Enabled VMT hook"); 28 | } 29 | 30 | void Disable() noexcept override 31 | { 32 | WriteImm(Address, OldAddress, false); 33 | __DEBUG("DKU_H: Disabled VMT hook"); 34 | } 35 | 36 | template 37 | F GetOldFunction() noexcept 38 | { 39 | return std::bit_cast(OldAddress); 40 | } 41 | 42 | const std::uintptr_t OldAddress; 43 | }; 44 | 45 | /** Swaps a virtual method table function with target function 46 | * \param a_vtbl : Pointer to virtual method table (base address of class object) 47 | * \param a_index : Index of the virtual function in the virtual method table 48 | * \param a_funcInfo : FUNC_INFO or RT_INFO wrapped function 49 | * \param a_patch : Prolog patch before detouring to target function 50 | * @return VMTHookHandle 51 | */ 52 | [[nodiscard]] inline auto AddVMTHook( 53 | const void* a_vtbl, 54 | const std::uint16_t a_index, 55 | const FuncInfo a_funcInfo, 56 | const unpacked_data a_patch = std::make_pair(nullptr, 0)) noexcept 57 | { 58 | if (!a_funcInfo.address()) { 59 | ERROR("DKU_H: VMT hook must have a valid function pointer"); 60 | } 61 | __DEBUG("DKU_H: Detour -> {} @ {}.{:X}", a_funcInfo.name().data(), PROJECT_NAME, a_funcInfo.address()); 62 | 63 | if (a_patch.first && a_patch.second) { 64 | auto tramPtr = TRAM_ALLOC(0); 65 | 66 | CallRip asmBranch; 67 | 68 | WriteImm(tramPtr, a_funcInfo.address(), true); 69 | tramPtr += sizeof(a_funcInfo.address()); 70 | asmBranch.Disp -= static_cast(sizeof(Imm64)); 71 | 72 | auto handle = std::make_unique(*std::bit_cast(a_vtbl), tramPtr, a_index); 73 | 74 | handle->Write(a_patch.first, a_patch.second); 75 | asmBranch.Disp -= static_cast(a_patch.second); 76 | 77 | asmBranch.Disp -= static_cast(sizeof(asmBranch)); 78 | handle->Write(asmBranch); 79 | 80 | return std::move(handle); 81 | } else { 82 | auto handle = std::make_unique(*std::bit_cast(a_vtbl), a_funcInfo.address(), a_index); 83 | return std::move(handle); 84 | } 85 | } 86 | } // namespace DKUtil::Hook 87 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Hook/Trampoline.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | namespace DKUtil::Hook::Trampoline 6 | { 7 | // partially taken from CommonLibSSE 8 | class Trampoline : public model::Singleton 9 | { 10 | public: 11 | // https://stackoverflow.com/a/54732489/17295222 12 | std::byte* PageAlloc(const std::size_t a_size, std::uintptr_t a_from = 0) noexcept 13 | { 14 | release(); 15 | 16 | constexpr std::size_t gigabyte = static_cast(1) << 30; 17 | constexpr std::size_t minRange = gigabyte * 2; 18 | constexpr std::uintptr_t maxAddr = std::numeric_limits::max(); 19 | 20 | ::DWORD granularity; 21 | ::SYSTEM_INFO si; 22 | ::GetSystemInfo(&si); 23 | granularity = si.dwAllocationGranularity; 24 | 25 | if (!a_from) { 26 | const auto textx = Module::get().section(Module::Section::textx); 27 | a_from = textx.first + textx.second; 28 | } 29 | 30 | std::uintptr_t min = a_from >= minRange ? numbers::roundup(a_from - minRange, granularity) : 0; 31 | const std::uintptr_t max = a_from < (maxAddr - minRange) ? numbers::rounddown(a_from + minRange, granularity) : maxAddr; 32 | std::uintptr_t addr = 0; 33 | 34 | ::MEMORY_BASIC_INFORMATION mbi; 35 | do { 36 | if (!::VirtualQuery(AsPointer(min), &mbi, sizeof(mbi))) { 37 | break; 38 | } 39 | 40 | min = AsAddress(mbi.BaseAddress) + mbi.RegionSize; 41 | 42 | if (mbi.State == MEM_FREE) { 43 | addr = numbers::roundup(AsAddress(mbi.BaseAddress), granularity); 44 | 45 | if (addr < min && a_size <= (min - addr)) { 46 | if (auto* data = ::VirtualAlloc(AsPointer(addr), a_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) { 47 | _capacity = a_size; 48 | _data = static_cast(data); 49 | break; 50 | } 51 | } 52 | } 53 | 54 | } while (min < max); 55 | 56 | if (!_data || !_capacity) { 57 | release(); 58 | FATAL("DKU_H: PageAlloc failed with code: 0x{:08X}"sv, ::GetLastError()); 59 | } 60 | 61 | return _data; 62 | } 63 | 64 | void set_trampoline(void* a_mem, std::size_t a_size) 65 | { 66 | release(); 67 | 68 | _data = static_cast(a_mem); 69 | _capacity = a_size; 70 | } 71 | 72 | [[nodiscard]] void* allocate(std::size_t a_size) 73 | { 74 | auto result = do_allocate(a_size); 75 | log_stats(); 76 | return result; 77 | } 78 | 79 | template 80 | [[nodiscard]] T* allocate() 81 | { 82 | return static_cast(allocate(sizeof(T))); 83 | } 84 | 85 | constexpr void release() noexcept 86 | { 87 | _data = nullptr; 88 | _capacity = 0; 89 | _used = 0; 90 | } 91 | 92 | [[nodiscard]] constexpr bool empty() const noexcept { return _capacity == 0; } 93 | [[nodiscard]] constexpr std::size_t capacity() const noexcept { return _capacity; } 94 | [[nodiscard]] constexpr std::size_t consumed() const noexcept { return _used; } 95 | [[nodiscard]] constexpr std::size_t free_size() const noexcept { return _capacity - _used; } 96 | 97 | private: 98 | [[nodiscard]] void* do_allocate(std::size_t a_size) 99 | { 100 | if (a_size > free_size()) { 101 | FATAL("Failed to handle allocation request"); 102 | } 103 | 104 | auto mem = _data + _used; 105 | _used += a_size; 106 | 107 | return mem; 108 | } 109 | 110 | void log_stats() const noexcept 111 | { 112 | auto pct = (static_cast(_used) / static_cast(_capacity)) * 100.0; 113 | __DEBUG("Trampoline => {}B / {}B ({:05.2f}%)", _used, _capacity, pct); 114 | } 115 | 116 | std::byte* _data{ nullptr }; 117 | std::size_t _capacity{ 0 }; 118 | std::size_t _used{ 0 }; 119 | }; 120 | 121 | inline Trampoline& GetTrampoline() noexcept 122 | { 123 | return *Trampoline::GetSingleton(); 124 | } 125 | 126 | inline Trampoline& AllocTrampoline(std::size_t a_size) 127 | { 128 | auto& trampoline = GetTrampoline(); 129 | if (!trampoline.capacity()) { 130 | trampoline.release(); 131 | trampoline.PageAlloc(a_size); 132 | } 133 | return trampoline; 134 | } 135 | 136 | inline void* Allocate(std::size_t a_size) 137 | { 138 | auto& trampoline = GetTrampoline(); 139 | return trampoline.allocate(a_size); 140 | } 141 | } // namespace DKUtil::Hook 142 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/PCH.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // c 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | // cxx 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | 100 | using namespace std::literals; 101 | 102 | // winnt 103 | #define WIN32_LEAN_AND_MEAN 104 | 105 | #define NOGDICAPMASKS 106 | #define NOVIRTUALKEYCODES 107 | //#define NOWINMESSAGES 108 | #define NOWINSTYLES 109 | #define NOSYSMETRICS 110 | #define NOMENUS 111 | #define NOICONS 112 | #define NOKEYSTATES 113 | #define NOSYSCOMMANDS 114 | #define NORASTEROPS 115 | #define NOSHOWWINDOW 116 | #define OEMRESOURCE 117 | #define NOATOM 118 | #define NOCLIPBOARD 119 | #define NOCOLOR 120 | //#define NOCTLMGR 121 | #define NODRAWTEXT 122 | #define NOGDI 123 | #define NOKERNEL 124 | //#define NOUSER 125 | #define NONLS 126 | //#define NOMB 127 | #define NOMEMMGR 128 | #define NOMETAFILE 129 | #define NOMINMAX 130 | //#define NOMSG 131 | #define NOOPENFILE 132 | #define NOSCROLL 133 | #define NOSERVICE 134 | #define NOSOUND 135 | #define NOTEXTMETRIC 136 | #define NOWH 137 | #define NOWINOFFSETS 138 | #define NOCOMM 139 | #define NOKANJI 140 | #define NOHELP 141 | #define NOPROFILER 142 | #define NODEFERWINDOWPOS 143 | #define NOMCX 144 | #include 145 | #include 146 | 147 | #undef min 148 | #undef max 149 | 150 | namespace DKUtil 151 | {}; 152 | namespace dku = DKUtil; 153 | 154 | #define dku_assert(cond, ...) \ 155 | { \ 156 | if (!((cond))) { \ 157 | FATAL(__VA_ARGS__); \ 158 | std::unreachable(); \ 159 | } \ 160 | } 161 | 162 | #define dku_cassert(constexpr_cond, ...) \ 163 | { \ 164 | if constexpr (!((constexpr_cond))) { \ 165 | FATAL(__VA_ARGS__); \ 166 | std::unreachable(); \ 167 | } \ 168 | } 169 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Utility/numbers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace DKUtil::numbers 4 | { 5 | class RNG 6 | { 7 | public: 8 | static RNG* GetSingleton() 9 | { 10 | static RNG singleton; 11 | return &singleton; 12 | } 13 | 14 | template ::value>::type> 15 | T Generate(T a_min, T a_max) 16 | { 17 | if constexpr (std::is_integral_v) { 18 | std::uniform_int_distribution distr(a_min, a_max); 19 | return distr(twister); 20 | } else { 21 | std::uniform_real_distribution distr(a_min, a_max); 22 | return distr(twister); 23 | } 24 | } 25 | 26 | private: 27 | RNG() : 28 | twister(std::random_device{}()) 29 | {} 30 | RNG(RNG const&) = delete; 31 | RNG(RNG&&) = delete; 32 | ~RNG() = default; 33 | 34 | RNG& operator=(RNG const&) = delete; 35 | RNG& operator=(RNG&&) = delete; 36 | 37 | std::mt19937 twister; 38 | }; 39 | 40 | // https://stackoverflow.com/questions/48896142 41 | template 42 | [[nodiscard]] inline constexpr std::uint32_t FNV_1A_32(const Str& a_str, const std::uint32_t a_prime = 2166136261u) noexcept 43 | { 44 | return (a_str[0] == '\0') ? a_prime : FNV_1A_32(&a_str[1], (a_prime ^ static_cast(a_str[0])) * 16777619u); 45 | } 46 | 47 | template 48 | [[nodiscard]] inline constexpr std::uint64_t FNV_1A_64(const Str& a_str, const std::uint64_t a_prime = 14695981039346656037ull) noexcept 49 | { 50 | return (a_str[0] == '\0') ? a_prime : FNV_1A_64(&a_str[1], (a_prime ^ static_cast(a_str[0])) * 1099511628211ull); 51 | } 52 | 53 | // taken from CommonLibSSE-Util 54 | static constexpr float EPSILON = std::numeric_limits::epsilon(); 55 | 56 | [[nodiscard]] inline bool approximately_equal(float a, float b) 57 | { 58 | return fabs(a - b) <= ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * EPSILON); 59 | } 60 | 61 | [[nodiscard]] inline bool essentially_equal(float a, float b) 62 | { 63 | return fabs(a - b) <= ((fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * EPSILON); 64 | } 65 | 66 | [[nodiscard]] inline bool definitely_greater_than(float a, float b) 67 | { 68 | return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * EPSILON); 69 | } 70 | 71 | [[nodiscard]] inline bool definitely_less_than(float a, float b) 72 | { 73 | return (b - a) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * EPSILON); 74 | } 75 | 76 | [[nodiscard]] inline constexpr std::size_t roundup(std::size_t a_number, std::size_t a_multiple) noexcept 77 | { 78 | if (a_multiple == 0) { 79 | return 0; 80 | } 81 | 82 | const auto remainder = a_number % a_multiple; 83 | return remainder == 0 ? 84 | a_number : 85 | a_number + a_multiple - remainder; 86 | } 87 | 88 | [[nodiscard]] inline constexpr std::size_t rounddown(std::size_t a_number, std::size_t a_multiple) noexcept 89 | { 90 | if (a_multiple == 0) { 91 | return 0; 92 | } 93 | 94 | const auto remainder = a_number % a_multiple; 95 | return remainder == 0 ? 96 | a_number : 97 | a_number - remainder; 98 | } 99 | 100 | [[nodiscard]] inline consteval std::size_t kilobyte(std::size_t a_quantity) noexcept 101 | { 102 | return static_cast(1 << 10) * a_quantity; 103 | } 104 | 105 | [[nodiscard]] inline consteval std::size_t megabyte(std::size_t a_quantity) noexcept 106 | { 107 | return static_cast(1 << 20) * a_quantity; 108 | } 109 | 110 | [[nodiscard]] inline consteval std::size_t gigabyte(std::size_t a_quantity) noexcept 111 | { 112 | return static_cast(1 << 30) * a_quantity; 113 | } 114 | } // namespace DKUtil::numbers 115 | 116 | #include "string.hpp" 117 | 118 | namespace DKUtil::numbers 119 | { 120 | // a hex number 121 | class hex 122 | { 123 | public: 124 | using numeric_base_t = std::uint64_t; 125 | 126 | // 1) 127 | constexpr hex() noexcept = default; 128 | 129 | // 2) 130 | hex(numeric_base_t a_val) noexcept : 131 | _base(a_val) 132 | {} 133 | 134 | // 3) 135 | hex(const char* a_str) noexcept 136 | { 137 | auto trim = string::trim_copy(a_str); 138 | int pos{ string::istarts_with(trim, "0x") ? 2 : 0 }; 139 | if (std::all_of(trim.begin() + pos, trim.end(), [](char c) { 140 | return std::isxdigit(static_cast(c)); 141 | })) { 142 | _base = std::stoull(trim, nullptr, 16); 143 | } 144 | } 145 | 146 | // 4) 147 | hex(std::string a_str) noexcept : 148 | hex(a_str.data()) 149 | {} 150 | 151 | // 5) 152 | hex(std::string_view a_str) noexcept : 153 | hex(a_str.data()) 154 | {} 155 | 156 | constexpr operator numeric_base_t() const noexcept 157 | { 158 | return _base; 159 | } 160 | 161 | constexpr auto number() const noexcept 162 | { 163 | return _base; 164 | } 165 | 166 | const auto string(std::string_view a_prefix = "0x"sv) const noexcept 167 | { 168 | return fmt::format("{}{:x}", a_prefix, _base); 169 | } 170 | 171 | private: 172 | numeric_base_t _base{ 0 }; 173 | }; 174 | } // namespace DKUtil::numbers 175 | 176 | #if defined(FMT_API) 177 | template <> 178 | struct fmt::formatter 179 | { 180 | template 181 | constexpr auto parse(ParseContext& a_ctx) 182 | { 183 | return a_ctx.begin(); 184 | } 185 | 186 | template 187 | constexpr auto format(DKUtil::numbers::hex const& a_hex, FormatContext& a_ctx) 188 | { 189 | return fmt::format_to(a_ctx.out(), "0x{:x}", a_hex.number()); 190 | } 191 | }; 192 | #endif 193 | -------------------------------------------------------------------------------- /include/DKUtil/Impl/Utility/shared.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /include/DKUtil/Utility.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * 1.0.1 5 | * Extended PARG_MACRO expansion to 19; 6 | * 7 | * 1.0.0 8 | * Adaptation of file structural changes; 9 | * 10 | */ 11 | 12 | #define DKU_U_VERSION_MAJOR 1 13 | #define DKU_U_VERSION_MINOR 0 14 | #define DKU_U_VERSION_REVISION 1 15 | 16 | #include "Impl/pch.hpp" 17 | #include "Logger.hpp" 18 | 19 | /** Bunch of stuff taken from CommonLibSSE-Util */ 20 | 21 | namespace std::ranges::views 22 | { 23 | inline constexpr auto drop_last = [](std::size_t count) { return std::views::reverse | std::views::drop(count) | std::views::reverse; }; 24 | inline constexpr auto take_last = [](std::size_t count) { return std::views::reverse | std::views::take(count) | std::views::reverse; }; 25 | } // namepsace std::views 26 | 27 | namespace DKUtil 28 | { 29 | constexpr auto DKU_U_VERSION = DKU_U_VERSION_MAJOR * 10000 + DKU_U_VERSION_MINOR * 100 + DKU_U_VERSION_REVISION; 30 | } // namespace DKUtil 31 | 32 | #include "Impl/Utility/enumeration.hpp" 33 | #include "Impl/Utility/numbers.hpp" 34 | #include "Impl/Utility/shared.hpp" 35 | #include "Impl/Utility/string.hpp" 36 | #include "Impl/Utility/templates.hpp" 37 | 38 | namespace DKUtil 39 | { 40 | template 41 | using Singleton = model::Singleton; 42 | 43 | template 44 | using scope_exit = model::scope_exit; 45 | 46 | template > 47 | using enumeration = model::enumeration; 48 | 49 | template >, 52 | std::uint32_t, std::underlying_type_t>> 53 | inline constexpr auto& static_enum() noexcept 54 | { 55 | static model::enumeration instance; 56 | return instance; 57 | } 58 | 59 | template 60 | inline constexpr std::string print_enum(Enum a_enum) noexcept 61 | { 62 | auto& tbl = static_enum(); 63 | return tbl.to_string(a_enum); 64 | } 65 | } // namespace DKUtil::Alias 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "scripts": { 4 | "docs:build": "vitepress build docs", 5 | "docs:preview": "vitepress preview docs", 6 | "docs:rebuild": "vitepress build docs && vitepress preview docs" 7 | }, 8 | "dependencies": { 9 | "vitepress": "^1.0.0-rc.45" 10 | } 11 | } -------------------------------------------------------------------------------- /test/ConfigTest.h: -------------------------------------------------------------------------------- 1 | #if defined(NDEBUG) 2 | # define BUILD_TYPE "Release" 3 | #else 4 | # define BUILD_TYPE "Debug" 5 | #endif 6 | 7 | #define CONFIG_ENTRY "..\\..\\bin\\" BUILD_TYPE 8 | 9 | #include "DKUtil/Config.hpp" 10 | 11 | namespace Test::Config 12 | { 13 | using namespace DKUtil::Alias; 14 | 15 | void TestConfig() 16 | { 17 | static Integer iA{ "iAwesome", "Awesome" }; 18 | static String sA{ "sAwesome", "Awesome" }; 19 | static Boolean bA{ "bAwesome", "Awesome" }; 20 | static Double dA{ "dAwesome", "Awesome" }; 21 | 22 | auto MainIni = COMPILE_PROXY("DKUtilDebugger.ini"sv); 23 | auto MainJson = COMPILE_PROXY("DKUtilDebugger.json"sv); 24 | auto MainToml = COMPILE_PROXY("DKUtilDebugger.toml"sv); 25 | 26 | MainToml.Bind(iA, 10); 27 | MainToml.Bind(sA, "First", "Second", "Third"); 28 | MainToml.Bind(bA, true); 29 | MainToml.Bind(dA, 114.514); 30 | 31 | MainIni.Load(); 32 | MainJson.Load(); 33 | MainToml.Load(); 34 | 35 | INFO("{} {} {} {}", *iA, *sA, *bA, *dA); 36 | for (auto f : dA.get_collection()) { 37 | INFO("{}", f); 38 | } 39 | 40 | std::string someRandomeName = DKUtil::Config::GetPath("AnotherBiteTheConfig.ini"); 41 | static auto runtime = RUNTIME_PROXY(someRandomeName); 42 | 43 | runtime.Load(); 44 | INFO("ini#{} json#{} toml#{} runtime#{}", MainIni.get_id(), MainJson.get_id(), MainToml.get_id(), runtime.get_id()); 45 | 46 | auto f = dku::Config::GetAllFiles({}, ".ini"sv); 47 | for (auto& fi : f) { 48 | INFO("File -> {}", fi); 49 | } 50 | } 51 | 52 | struct CustomData 53 | { 54 | dku::numbers::hex form; 55 | std::string name; 56 | std::string payload; 57 | bool excluded; 58 | }; 59 | 60 | void TestSchema() 61 | { 62 | auto sc = SCHEMA_PROXY("Schema_SC.txt"); 63 | sc.Load(); 64 | auto& p = sc.get_parser(); 65 | 66 | auto d = p.ParseNextLine("|").value(); 67 | INFO("{} {} {} {}", d.form, d.name, d.payload, d.excluded); 68 | 69 | auto d2 = dku::Config::ParseSchemaString("0x1234|BUSTED|random payload |true", "|"); 70 | INFO("{} {} {} {}", d2.form, d2.name, d2.payload, d2.excluded); 71 | } 72 | 73 | void Run() 74 | { 75 | //TestConfig(); 76 | TestSchema(); 77 | } 78 | } // namespace Test::Config 79 | -------------------------------------------------------------------------------- /test/LoggerTest.h: -------------------------------------------------------------------------------- 1 | #include "DKUtil/Extra.hpp" 2 | #include "DKUtil/Logger.hpp" 3 | 4 | namespace Test::Logger 5 | { 6 | bool bT = true, bF = false; 7 | int siP = 200, siN = -119; 8 | std::uint32_t underflowUL = static_cast(-1ul), overflowUL = 0xFFFFFFFFul + 1ul; 9 | 10 | void Run() 11 | { 12 | INFO("This is info message: {:X}", 0x124); 13 | } 14 | 15 | void RunError() 16 | { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/PCH.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // c 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | // cxx 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | 100 | // clib 101 | #include 102 | #include 103 | #include 104 | 105 | // winnt 106 | #include 107 | 108 | using namespace std::literals; 109 | using namespace REL::literals; 110 | 111 | #define DLLEXPORT extern "C" [[maybe_unused]] __declspec(dllexport) 112 | 113 | // Plugin 114 | #include "Plugin.h" 115 | 116 | // DKUtil 117 | #include "DKUtil/Logger.hpp" 118 | 119 | #define __do_test_run(HEADER) \ 120 | { \ 121 | INFO("++++++ Running library: {}", #HEADER); \ 122 | auto start = std::chrono::steady_clock::now(); \ 123 | Test::HEADER::Run(); \ 124 | auto end = std::chrono::steady_clock::now(); \ 125 | std::chrono::duration elapsed = end - start; \ 126 | INFO("++++++ Testing complete in {:.5f}ms", elapsed.count() * 1000); \ 127 | } 128 | -------------------------------------------------------------------------------- /test/PluginTest.cpp: -------------------------------------------------------------------------------- 1 | #include "ConfigTest.h" 2 | #include "HookTest.h" 3 | #include "LoggerTest.h" 4 | #include "SSEExtraTest.h" 5 | #include "UtilityTest.h" 6 | 7 | //#define TEST_CONFIG 8 | #define TEST_HOOK 9 | //#define TEST_LOGGER 10 | //#define TEST_UTILITY 11 | //#define TEST_CUSTOM 12 | #define TEST_AND_EXIT 13 | 14 | namespace 15 | { 16 | void MsgCallback(SKSE::MessagingInterface::Message* a_msg) noexcept 17 | { 18 | #ifdef TEST_CONFIG 19 | 20 | #endif 21 | 22 | #ifdef TEST_HOOK 23 | 24 | #endif 25 | 26 | #ifdef TEST_LOGGER 27 | 28 | #endif 29 | 30 | #ifdef TEST_UTILITY 31 | 32 | #endif 33 | } 34 | } 35 | 36 | int main() 37 | { 38 | #ifdef TEST_CONFIG 39 | __do_test_run(Config); 40 | #endif 41 | 42 | #ifdef TEST_HOOK 43 | __do_test_run(Hook); 44 | #endif 45 | 46 | #ifdef TEST_LOGGER 47 | __do_test_run(Logger); 48 | #endif 49 | 50 | #ifdef TEST_UTILITY 51 | __do_test_run(Utility); 52 | #endif 53 | 54 | #ifdef TEST_CUSTOM 55 | __do_test_run(Extra); 56 | #endif 57 | 58 | #ifdef TEST_AND_EXIT 59 | std::exit('EXIT'); 60 | #endif 61 | } 62 | 63 | DLLEXPORT bool SKSEAPI SKSEPlugin_Load(const SKSE::LoadInterface* a_skse) 64 | { 65 | DKUtil::Logger::Init(Plugin::NAME, REL::Module::get().version().string()); 66 | 67 | SKSE::Init(a_skse); 68 | INFO("{} v{} loaded", Plugin::NAME, Plugin::Version); 69 | 70 | SKSE::GetMessagingInterface()->RegisterListener(MsgCallback); 71 | 72 | main(); 73 | 74 | return true; 75 | } 76 | -------------------------------------------------------------------------------- /test/SSEExtraTest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DKUtil/Extra.hpp" 4 | 5 | namespace Test::Extra 6 | { 7 | struct ActorDeathInfo 8 | { 9 | int actor; 10 | std::string reason; 11 | }; 12 | 13 | void Run() 14 | { 15 | /***/ 16 | dku::serializable, "MapInfo"> actorDeathInfoMapSerial({ 17 | { 0, { 11, "testA" } }, 18 | { 1, { 22, "testB" } }, 19 | { 2, { 33, "testC" } }, 20 | }); /***/ 21 | dku::serializable actorDeathInfoSerial({ 1234, "test" }); 22 | 23 | /*** 24 | dku::serializable, "MapInfo"> actorDeathInfoMapSerial({ 25 | { 0, 1 }, 26 | { 1, 2 }, 27 | { 2, 3 }, 28 | }); /***/ 29 | 30 | std::tuple testTuple(nullptr, "test"); 31 | std::pair as = std::make_tuple(1, 2); 32 | 33 | for (auto& [f, s] : *actorDeathInfoMapSerial) { 34 | INFO("{} {}", f, s.reason); 35 | } 36 | INFO("{}", actorDeathInfoSerial->reason); 37 | 38 | dku::serialization::api::detail::save_all(nullptr); 39 | dku::serialization::api::detail::revert_all(nullptr); 40 | dku::serialization::api::detail::load_all(nullptr); 41 | 42 | for (auto& [f, s] : *actorDeathInfoMapSerial) { 43 | INFO("{} {}", f, s.reason); 44 | } 45 | INFO("{}", actorDeathInfoSerial->reason); 46 | 47 | constexpr auto ss = dku::model::concepts::dku_aggregate; 48 | } 49 | } // namespace Test::Extra 50 | -------------------------------------------------------------------------------- /test/UtilityTest.h: -------------------------------------------------------------------------------- 1 | #include "DKUtil/Utility.hpp" 2 | 3 | //#define STRONG_TYPED_ENUM 4 | #ifdef STRONG_TYPED_ENUM 5 | # define ENUM_TYPE : std::uint32_t 6 | # define UNDERLYING 7 | #else 8 | # define ENUM_TYPE 9 | # define UNDERLYING , std::uint32_t 10 | #endif 11 | 12 | namespace Test::Utility 13 | { 14 | using namespace DKUtil::Alias; 15 | 16 | namespace Enum 17 | { 18 | enum class ContiguousFlag ENUM_TYPE 19 | { 20 | NONE = 0u, 21 | 22 | RAX = 1u << 0, 23 | RCX = 1u << 1, 24 | RDX = 1u << 2, 25 | RBX = 1u << 3, 26 | RSP = 1u << 4, 27 | RBP = 1u << 5, 28 | RSI = 1u << 6, 29 | RDI = 1u << 7, 30 | RF = 1u << 8, 31 | R8 = 1u << 9, 32 | R9 = 1u << 10, 33 | R10 = 1u << 11, 34 | R11 = 1u << 12, 35 | R12 = 1u << 13, 36 | R13 = 1u << 14, 37 | R14 = 1u << 15, 38 | R15 = 1u << 16, 39 | }; 40 | 41 | enum class SparseFlag ENUM_TYPE 42 | { 43 | NONE = 0u, 44 | 45 | RAX = 1u << 0, 46 | RCX = 1u << 1, 47 | //RDX = 1u << 2, 48 | RBX = 1u << 3, 49 | RSP = 1u << 4, 50 | //RBP = 1u << 5, 51 | RSI = 1u << 6, 52 | RDI = 1u << 7, 53 | RF = 1u << 8, 54 | //R8 = 1u << 9, 55 | R9 = 1u << 10, 56 | //R10 = 1u << 11, 57 | R11 = 1u << 12, 58 | //R12 = 1u << 13, 59 | R13 = 1u << 14, 60 | R14 = 1u << 15, 61 | R15 = 1u << 16, 62 | }; 63 | 64 | enum class ContiguousValue ENUM_TYPE 65 | { 66 | NONE = 0u, 67 | 68 | RAX = 1, 69 | RCX = 2, 70 | RDX = 3, 71 | RBX = 4, 72 | RSP = 5, 73 | RBP = 6, 74 | RSI = 7, 75 | RDI = 8, 76 | RF = 9, 77 | R8 = 10, 78 | R9 = 11, 79 | R10 = 12, 80 | R11 = 13, 81 | R12 = 14, 82 | R13 = 15, 83 | R14 = 16, 84 | R15 = 17, 85 | }; 86 | 87 | enum class SparseValue ENUM_TYPE 88 | { 89 | NONE = 0u, 90 | 91 | RAX = 1, 92 | RCX = 2, 93 | //RDX = 3, 94 | RBX = 4, 95 | RSP = 5, 96 | //RBP = 6, 97 | RSI = 7, 98 | RDI = 8, 99 | RF = 9, 100 | //R8 = 10, 101 | R9 = 11, 102 | //R10 = 12, 103 | R11 = 13, 104 | //R12 = 14, 105 | R13 = 15, 106 | R14 = 16, 107 | R15 = 17, 108 | }; 109 | 110 | void TestEnum() noexcept 111 | { 112 | // concept-restraint auto ctor 113 | dku::enumeration cValues{ 0, 2, 4, 5, 9, 15 }; 114 | dku::enumeration sValues{ 0, 2, 4, 5, 9, 15 }; 115 | dku::enumeration cFlags{ 0, 2, 4, 5, 9, 15 }; 116 | dku::enumeration sFlags{ SparseFlag::NONE, SparseFlag::RCX, SparseFlag::RBX, SparseFlag::RSI, SparseFlag::R9, SparseFlag::R14 }; 117 | 118 | // static reflections 119 | // 1) check for value-type enum reflection 120 | // 2) check for flag-type enum reflection 121 | // 3) check for mixed sparse enum reflection 122 | // 4) check for mixed contiguous enum reflection 123 | // 5) direct invocation between raw underlying value and enum value 124 | INFO("Reflecting enum value names"); 125 | for (auto i : std::views::iota(0, 17)) { 126 | INFO("{}{} {}", cValues.is_flag() ? "1<<" : "", i, cValues.to_string(static_cast(i))); 127 | INFO("{}{} {}", sValues.is_flag() ? "1<<" : "", i, sValues.to_string(i)); 128 | INFO("{}{} {}", cFlags.is_flag() ? "1<<" : "", i, cFlags.to_string(i)); 129 | INFO("{}{} {}", sFlags.is_flag() ? "1<<" : "", i, sFlags.to_string(i)); 130 | } 131 | 132 | // 5) check for enum class name reflection 133 | INFO("Enum name:\n{}\n{}\n{}\n{}", cValues.enum_name(), sValues.enum_name(), cFlags.enum_name(), sFlags.enum_name()); 134 | // 6) check for enum underlying type name 135 | INFO("Type name:\n{}\n{}\n{}\n{}", cValues.type_name(), sValues.type_name(), cFlags.type_name(), sFlags.type_name()); 136 | 137 | // 7) value range iterator 138 | INFO("Iterating by value_range"); 139 | for (const auto e : cValues.value_range(ContiguousValue::NONE, ContiguousValue::R15)) { 140 | INFO("{}", cValues.to_string(e)); 141 | } 142 | 143 | INFO("Iterating by flag_range"); 144 | for (const auto e : cFlags.flag_range(ContiguousFlag::NONE, ContiguousFlag::R15)) { 145 | INFO("{}", cFlags.to_string(e)); 146 | } 147 | 148 | // 8) string to enum cast 149 | std::string nameStr1{ "rsi" }; 150 | std::string nameStr2{ "RaX" }; 151 | std::string nameStr3{ "Rdx" }; 152 | 153 | auto nameEnum1 = cValues.from_string(nameStr1); 154 | auto nameEnum2 = cValues.from_string(nameStr2); 155 | auto nameEnum3 = cValues.from_string(nameStr3); 156 | 157 | INFO("String {} -> Enum {}", nameStr1, std::to_underlying(nameEnum1.value())); 158 | INFO("String {} -> Enum {}", nameStr2, std::to_underlying(nameEnum2.value())); 159 | INFO("String {} -> Enum {}", nameStr3, std::to_underlying(nameEnum3.value())); 160 | ContiguousValue Enum1 = nameEnum1.value(); 161 | } 162 | 163 | void TestEnumBitwidth() noexcept 164 | { 165 | auto& sf = dku::static_enum(); 166 | auto& cf = dku::static_enum(); 167 | 168 | auto str = "RDX"; 169 | auto strEnum = sf.from_string(str); 170 | INFO("{} {}", dku::print_enum(strEnum.value_or(SparseFlag::RAX)), std::to_underlying(strEnum.value_or(SparseFlag::RAX))); 171 | } 172 | } // namespace Enum 173 | 174 | consteval void TestNumbers() noexcept 175 | { 176 | // 64B3BC14 177 | constexpr const char* hashBase32 = "CONSTEXPR_HASH_STRING_32"; 178 | // 7873E548866D72BB 179 | constexpr const char* hashBase64 = "CONSTEXPR_HASH_STRING_64"; 180 | 181 | constexpr auto hash32 = dku::numbers::FNV_1A_32(hashBase32); 182 | constexpr auto hash64 = dku::numbers::FNV_1A_64(hashBase64); 183 | 184 | static_assert(hash32 == 0x64B3BC14U); 185 | static_assert(hash64 == 0x7873E548866D72BBULL); 186 | } 187 | 188 | void TestModel() noexcept 189 | { 190 | struct TestAggregate 191 | { 192 | int i; 193 | std::string s; 194 | char c; 195 | bool b; 196 | }; 197 | 198 | auto tv = dku::model::tuple_cast(TestAggregate{}); 199 | auto sv = dku::model::struct_cast(tv); 200 | int av[] = { 1, 2, 3, 4 }; 201 | 202 | // bindables 203 | static_assert(dku::model::number_of_bindables() == 4); 204 | static_assert(dku::model::number_of_bindables() == 4); 205 | static_assert(dku::model::number_of_bindables() == 4); 206 | 207 | // concepts 208 | static_assert(dku::model::concepts::dku_aggregate); 209 | static_assert(dku::model::concepts::dku_bindable); 210 | static_assert(dku::model::concepts::dku_ranges); 211 | static_assert(dku::model::concepts::dku_trivial_ranges); 212 | } 213 | 214 | void TestString() noexcept 215 | { 216 | auto str = "D:\\WorkSpace\\SKSEPlugins\\Plugins\\EnderalHeroMenu\\src\\LoadGame.cpp:36"sv; 217 | auto pat = "\\"sv; 218 | auto rep = "|"sv; 219 | 220 | // 1) replace_nth 221 | INFO("replace all \\ with |"); 222 | INFO(dku::string::replace_nth_occurrence(str, 0, pat, rep)); 223 | 224 | INFO("remove all \\"); 225 | INFO(dku::string::replace_nth_occurrence(str, 0, pat)); 226 | 227 | INFO("replace 2nd \\ with |"); 228 | INFO(dku::string::replace_nth_occurrence(str, 2, pat, rep)); 229 | 230 | INFO("replace 2nd to last \\ with |"); 231 | INFO(dku::string::replace_nth_occurrence(str, -2, pat, rep)); 232 | 233 | INFO("replace 100th \\ with | (out of bound)"); 234 | INFO(dku::string::replace_nth_occurrence(str, 100, pat, rep)); 235 | 236 | // 2) split, join 237 | constexpr std::string_view words{ "DKUtil|TestUsage|Schema|String 002| That " }; 238 | auto token = dku::string::split(words, "|"); 239 | 240 | for (auto& t : token) { 241 | INFO(t); 242 | } 243 | 244 | INFO(dku::string::join(token, "|")); 245 | } 246 | 247 | void TestTest() 248 | { 249 | } 250 | 251 | void Run() noexcept 252 | { 253 | //TestNumbers(); 254 | //TestModel(); 255 | 256 | TestString(); 257 | 258 | //Enum::TestEnum(); 259 | //Enum::TestEnumBitwidth(); 260 | } 261 | } -------------------------------------------------------------------------------- /test/configs/AnotherBiteTheConfig.ini: -------------------------------------------------------------------------------- 1 | ;Some random Comment 2 | [BaddieB] 3 | iAm = 114514 4 | bIznatch = true 5 | dWeight = 8899.6 6 | sClassic = "Hello World" 7 | -------------------------------------------------------------------------------- /test/configs/DKUtilDebugger.ini: -------------------------------------------------------------------------------- 1 | ;Awesome Comment 2 | [Awesome] 3 | iAwesome = 10 4 | bAwesome = false 5 | dAwesome = 12.2 6 | sAwesome = "I'm awesome" 7 | 8 | ;Gruesome Comment 9 | [Gruesome] 10 | iGruesome = 1114 11 | bGruesome = true 12 | dGruesome = 1677.5 13 | sGruesome = You are gruesome 14 | -------------------------------------------------------------------------------- /test/configs/DKUtilDebugger.json: -------------------------------------------------------------------------------- 1 | { 2 | "iAwesome" : 14, 3 | "bAwesome" : true, 4 | "dAwesome" : 3.1415926535897, 5 | "sAwesome" : [ 6 | "This is json", 7 | "You are gruesome", 8 | "We are awesome" 9 | ] 10 | } -------------------------------------------------------------------------------- /test/configs/DKUtilDebugger.toml: -------------------------------------------------------------------------------- 1 | [Awesome] 2 | iAwesome = [145, 222, 500, 70] 3 | bAwesome = false 4 | dAwesome = 8899.66 5 | sAwesome = "This is toml" 6 | 7 | [Gruesome] 8 | iGruesome = 1114 9 | bGruesome = true 10 | dGruesome = 1677.5 11 | sGruesome = "Not tom" 12 | -------------------------------------------------------------------------------- /test/configs/Schema_SC.txt: -------------------------------------------------------------------------------- 1 | 0x1000 | MyName | EXPLODE | false -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", 3 | "name": "dkutil", 4 | "version-string": "2.0.0", 5 | "description": "An utilitarian library to help with SKSE plugin development.", 6 | "license": "MIT", 7 | "dependencies": [ 8 | "nlohmann-json", 9 | "simpleini", 10 | "spdlog", 11 | "xbyak" 12 | ], 13 | "builtin-baseline": "e60236ee051183f1122066bee8c54a0b47c43a60", 14 | "features": { 15 | "mo2-install": { 16 | "description": "DKUtilDebugger" 17 | } 18 | } 19 | } 20 | --------------------------------------------------------------------------------