From ff3ff53a535feea7402bcf7f92566a930e0beae3 Mon Sep 17 00:00:00 2001 From: DK Date: Wed, 6 Sep 2023 09:58:34 +0800 Subject: [PATCH 01/15] feat: barebone first version --- .gitattributes | 4 + .github/workflows/clang-format.yml | 21 + .github/workflows/sourcegen.yml | 12 + .gitignore | 13 + CommonLibSF/.clang-format | 101 +++ CommonLibSF/.editorconfig | 11 + CommonLibSF/CMakeLists.txt | 166 +++++ CommonLibSF/CMakePresets.json | 52 ++ CommonLibSF/LICENSE | 21 + CommonLibSF/cmake/build_stl_modules.props | 8 + CommonLibSF/cmake/config.cmake.in | 5 + CommonLibSF/cmake/make-directives.ps1 | 54 ++ CommonLibSF/cmake/make-sourcelist.ps1 | 64 ++ CommonLibSF/include/RE/Offsets.h | 7 + CommonLibSF/include/RE/Offsets_NiRTTI.h | 7 + CommonLibSF/include/RE/Offsets_RTTI.h | 7 + CommonLibSF/include/RE/Offsets_VTABLE.h | 7 + CommonLibSF/include/RE/RTTI.h | 246 +++++++ CommonLibSF/include/RE/Starfield.h | 9 + CommonLibSF/include/REL/Relocation.h | 617 +++++++++++++++++ CommonLibSF/include/SFSE/API.h | 22 + CommonLibSF/include/SFSE/IAT.h | 31 + CommonLibSF/include/SFSE/Impl/DInputAPI.h | 571 +++++++++++++++ CommonLibSF/include/SFSE/Impl/PCH.h | 807 ++++++++++++++++++++++ CommonLibSF/include/SFSE/Impl/Stubs.h | 43 ++ CommonLibSF/include/SFSE/Impl/WinAPI.h | 590 ++++++++++++++++ CommonLibSF/include/SFSE/Impl/XInputAPI.h | 84 +++ CommonLibSF/include/SFSE/InputMap.h | 53 ++ CommonLibSF/include/SFSE/Interfaces.h | 161 +++++ CommonLibSF/include/SFSE/Logger.h | 41 ++ CommonLibSF/include/SFSE/SFSE.h | 11 + CommonLibSF/include/SFSE/Trampoline.h | 359 ++++++++++ CommonLibSF/include/SFSE/Version.h | 10 + CommonLibSF/src/RE/Starfield.cpp | 1 + CommonLibSF/src/REL/Relocation.cpp | 86 +++ CommonLibSF/src/SFSE/API.cpp | 131 ++++ CommonLibSF/src/SFSE/IAT.cpp | 119 ++++ CommonLibSF/src/SFSE/Impl/PCH.cpp | 1 + CommonLibSF/src/SFSE/Impl/WinAPI.cpp | 480 +++++++++++++ CommonLibSF/src/SFSE/InputMap.cpp | 232 +++++++ CommonLibSF/src/SFSE/Interfaces.cpp | 101 +++ CommonLibSF/src/SFSE/Logger.cpp | 25 + CommonLibSF/src/SFSE/Trampoline.cpp | 116 ++++ CommonLibSF/vcpkg.json | 19 + generate-sln.ps1 | 2 + 45 files changed, 5528 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/clang-format.yml create mode 100644 .github/workflows/sourcegen.yml create mode 100644 .gitignore create mode 100644 CommonLibSF/.clang-format create mode 100644 CommonLibSF/.editorconfig create mode 100644 CommonLibSF/CMakeLists.txt create mode 100644 CommonLibSF/CMakePresets.json create mode 100644 CommonLibSF/LICENSE create mode 100644 CommonLibSF/cmake/build_stl_modules.props create mode 100644 CommonLibSF/cmake/config.cmake.in create mode 100644 CommonLibSF/cmake/make-directives.ps1 create mode 100644 CommonLibSF/cmake/make-sourcelist.ps1 create mode 100644 CommonLibSF/include/RE/Offsets.h create mode 100644 CommonLibSF/include/RE/Offsets_NiRTTI.h create mode 100644 CommonLibSF/include/RE/Offsets_RTTI.h create mode 100644 CommonLibSF/include/RE/Offsets_VTABLE.h create mode 100644 CommonLibSF/include/RE/RTTI.h create mode 100644 CommonLibSF/include/RE/Starfield.h create mode 100644 CommonLibSF/include/REL/Relocation.h create mode 100644 CommonLibSF/include/SFSE/API.h create mode 100644 CommonLibSF/include/SFSE/IAT.h create mode 100644 CommonLibSF/include/SFSE/Impl/DInputAPI.h create mode 100644 CommonLibSF/include/SFSE/Impl/PCH.h create mode 100644 CommonLibSF/include/SFSE/Impl/Stubs.h create mode 100644 CommonLibSF/include/SFSE/Impl/WinAPI.h create mode 100644 CommonLibSF/include/SFSE/Impl/XInputAPI.h create mode 100644 CommonLibSF/include/SFSE/InputMap.h create mode 100644 CommonLibSF/include/SFSE/Interfaces.h create mode 100644 CommonLibSF/include/SFSE/Logger.h create mode 100644 CommonLibSF/include/SFSE/SFSE.h create mode 100644 CommonLibSF/include/SFSE/Trampoline.h create mode 100644 CommonLibSF/include/SFSE/Version.h create mode 100644 CommonLibSF/src/RE/Starfield.cpp create mode 100644 CommonLibSF/src/REL/Relocation.cpp create mode 100644 CommonLibSF/src/SFSE/API.cpp create mode 100644 CommonLibSF/src/SFSE/IAT.cpp create mode 100644 CommonLibSF/src/SFSE/Impl/PCH.cpp create mode 100644 CommonLibSF/src/SFSE/Impl/WinAPI.cpp create mode 100644 CommonLibSF/src/SFSE/InputMap.cpp create mode 100644 CommonLibSF/src/SFSE/Interfaces.cpp create mode 100644 CommonLibSF/src/SFSE/Logger.cpp create mode 100644 CommonLibSF/src/SFSE/Trampoline.cpp create mode 100644 CommonLibSF/vcpkg.json create mode 100644 generate-sln.ps1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..fd178224 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Auto detect text files and perform LF normalization +* text=auto + +*.as linguist-language=ActionScript diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 00000000..0c556649 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,21 @@ +on: [push] + +jobs: + formatting: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: DoozyX/clang-format-lint-action@v0.16.2 + with: + source: '.' + exclude: './docs' + extensions: 'c,cc,cpp,cxx,h,hpp,hxx,inl,inc,ixx' + clangFormatVersion: 16 + inplace: True + - uses: EndBug/add-and-commit@v9 + with: + author_name: clang-format + message: 'chore: style formatting' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sourcegen.yml b/.github/workflows/sourcegen.yml new file mode 100644 index 00000000..5e58f1ea --- /dev/null +++ b/.github/workflows/sourcegen.yml @@ -0,0 +1,12 @@ +on: [push] + +jobs: + sourcegen: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + + - name: make Starfield.h + shell: pwsh + run: "& ${{ GITHUB_WORKSPACE }}/CommonLibSF/cmake/make-directives.ps1 ${{ GITHUB_WORKSPACE }}/CommonLibSF" diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bddce39e --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/[Bb]uild* +CMakeUserPresets.json +CMakeFiles +CMakeCache.txt +*.cmake +/out* +/.vs* +/.vscode* +/.idea +conan.lock +conanbuildinfo.* +conaninfo.txt +graph_info.json diff --git a/CommonLibSF/.clang-format b/CommonLibSF/.clang-format new file mode 100644 index 00000000..f3d9e4db --- /dev/null +++ b/CommonLibSF/.clang-format @@ -0,0 +1,101 @@ +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: 'false' +AlignConsecutiveBitFields: 'false' +AlignConsecutiveDeclarations: 'true' +AlignConsecutiveMacros: 'false' +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: 'true' +AllowAllArgumentsOnNextLine: 'false' +AllowAllConstructorInitializersOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'false' +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortEnumsOnASingleLine: 'true' +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: 'true' +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'true' +AlwaysBreakTemplateDeclarations: 'Yes' +BinPackArguments: 'true' +BinPackParameters: 'true' +BitFieldColonSpacing: After +BraceWrapping: + AfterCaseLabel: 'true' + AfterClass: 'true' + AfterControlStatement: 'false' + AfterEnum: 'true' + AfterFunction: 'true' + AfterNamespace: 'true' + AfterStruct: 'true' + AfterUnion: 'true' + AfterExternBlock: 'true' + BeforeCatch: 'false' + BeforeElse: 'false' + BeforeLambdaBody: 'false' + BeforeWhile: 'false' + IndentBraces: 'false' + SplitEmptyFunction: 'false' + SplitEmptyRecord: 'false' + SplitEmptyNamespace: 'false' +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: 'false' +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: 'true' +ColumnLimit: 0 +CompactNamespaces: 'false' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: 'false' +DeriveLineEnding: 'true' +DerivePointerAlignment: 'false' +DisableFormat: 'false' +FixNamespaceComments: 'false' +IncludeBlocks: Preserve +IndentCaseBlocks: 'true' +IndentCaseLabels: 'false' +IndentExternBlock: Indent +IndentGotoLabels: 'false' +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: 'true' +KeepEmptyLinesAtTheStartOfBlocks: 'false' +Language: Cpp +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +PointerAlignment: Left +ReflowComments : 'false' +SortIncludes: 'true' +SortUsingDeclarations: 'true' +SpaceAfterCStyleCast: 'false' +SpaceAfterLogicalNot: 'false' +SpaceAfterTemplateKeyword: 'true' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCpp11BracedList: 'false' +SpaceBeforeCtorInitializerColon: 'true' +SpaceBeforeInheritanceColon: 'true' +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: 'true' +SpaceBeforeSquareBrackets: 'false' +SpaceInEmptyBlock: 'false' +SpaceInEmptyParentheses: 'false' +SpacesBeforeTrailingComments: 2 +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInConditionalStatement: 'false' +SpacesInContainerLiterals: 'true' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +Standard: Latest +TabWidth: 4 +UseCRLF: 'true' +UseTab: AlignWithSpaces + +... diff --git a/CommonLibSF/.editorconfig b/CommonLibSF/.editorconfig new file mode 100644 index 00000000..21457d1f --- /dev/null +++ b/CommonLibSF/.editorconfig @@ -0,0 +1,11 @@ +[*] +charset = utf-8 +insert_final_newline = true + +[*.{asm,c,cc,cpp,cxx,h,hpp,hxx,inc,inl,ixx}] +indent_style = tab +indent_size = 4 + +[*.{ini,json,toml,xml}] +indent_style = space +indent_size = 2 diff --git a/CommonLibSF/CMakeLists.txt b/CommonLibSF/CMakeLists.txt new file mode 100644 index 00000000..76f964c4 --- /dev/null +++ b/CommonLibSF/CMakeLists.txt @@ -0,0 +1,166 @@ +cmake_minimum_required(VERSION 3.21) + +# singleton target across multiple projects +if(TARGET CommonLibSF) + return() +endif() + +# options if not defined +option(SFSE_SUPPORT_XBYAK "Enables trampoline support for Xbyak." ON) + +# standards & flags +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUF OFF) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE FILEPATH "") + +# info +project( + CommonLibSF + LANGUAGES CXX + VERSION 1.0.0 +) + +include(GNUInstallDirs) + +# out-of-source builds only +if(${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR}) + message(FATAL_ERROR "In-source builds are not allowed.") +endif() + +# dependencies +find_package(spdlog CONFIG REQUIRED) + +# source files +execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_SOURCE_DIR}/cmake/make-sourcelist.ps1" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") +include(${CMAKE_CURRENT_BINARY_DIR}/sourcelist.cmake) +source_group( + TREE ${CMAKE_CURRENT_SOURCE_DIR} + FILES ${SOURCES} +) + +add_library( + ${PROJECT_NAME} + STATIC + ${SOURCES} + .clang-format +) + +add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + +target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + WINVER=0x0601 # windows 7, minimum supported version by skyrim special edition + _WIN32_WINNT=0x0601 + "$<$:SFSE_SUPPORT_XBYAK=1>" +) + +# FIXME: https://gitlab.kitware.com/cmake/cmake/-/issues/24922 +set_property( + TARGET + ${PROJECT_NAME} + PROPERTY VS_USER_PROPS + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_stl_modules.props" +) + +if (MSVC) + target_compile_options( + ${PROJECT_NAME} + PUBLIC + /bigobj # support large object file format + /utf-8 # assume UTF-8 sources even without a BOM + + # warnings -> errors + /we4715 # 'function' : not all control paths return a value + + # disable warnings + /wd4005 # macro redefinition + /wd4061 # enumerator 'identifier' in switch of enum 'enumeration' is not explicitly handled by a case label + /wd4068 # unknown pragma + /wd4200 # nonstandard extension used : zero-sized array in struct/union + /wd4201 # nonstandard extension used : nameless struct/union + /wd4265 # 'type': class has virtual functions, but its non-trivial destructor is not virtual; instances of this class may not be destructed correctly + /wd4266 # 'function' : no override available for virtual member function from base 'type'; function is hidden + /wd4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data + /wd4371 # 'classname': layout of class may have changed from a previous version of the compiler due to better packing of member 'member' + /wd4514 # 'function' : unreferenced inline function has been removed + /wd4582 # 'type': constructor is not implicitly called + /wd4583 # 'type': destructor is not implicitly called + /wd4623 # 'derived class' : default constructor was implicitly defined as deleted because a base class default constructor is inaccessible or deleted + /wd4625 # 'derived class' : copy constructor was implicitly defined as deleted because a base class copy constructor is inaccessible or deleted + /wd4626 # 'derived class' : assignment operator was implicitly defined as deleted because a base class assignment operator is inaccessible or deleted + /wd4710 # 'function' : function not inlined + /wd4711 # function 'function' selected for inline expansion + /wd4820 # 'bytes' bytes padding added after construct 'member_name' + /wd5026 # 'type': move constructor was implicitly defined as deleted + /wd5027 # 'type': move assignment operator was implicitly defined as deleted + /wd5045 # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified + /wd5053 # support for 'explicit()' in C++17 and earlier is a vendor extension + /wd5204 # 'type-name': class has virtual functions, but its trivial destructor is not virtual; instances of objects derived from this class may not be destructed correctly + /wd5220 # 'member': a non-static data member with a volatile qualified type no longer implies that compiler generated copy / move constructors and copy / move assignment operators are not trivial + /FI${CMAKE_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}.dir/$/cmake_pch.hxx + ) +endif() + +target_include_directories( + ${PROJECT_NAME} + PUBLIC + "$" + "$" +) + +target_link_libraries( + ${PROJECT_NAME} + PUBLIC + spdlog::spdlog + Version.lib +) + +if (SFSE_SUPPORT_XBYAK) +find_package(xbyak CONFIG REQUIRED) + +target_link_libraries( + ${PROJECT_NAME} + PUBLIC + xbyak::xbyak +) +endif() + +target_precompile_headers( + ${PROJECT_NAME} + PRIVATE + include/SFSE/Impl/PCH.h +) + +install( + TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}-targets +) + +install( + EXPORT ${PROJECT_NAME}-targets + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} +) + +configure_file( + cmake/config.cmake.in + ${PROJECT_NAME}Config.cmake + @ONLY +) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} +) + +install( + DIRECTORY + include/RE + include/REL + include/SFSE + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) diff --git a/CommonLibSF/CMakePresets.json b/CommonLibSF/CMakePresets.json new file mode 100644 index 00000000..e551398f --- /dev/null +++ b/CommonLibSF/CMakePresets.json @@ -0,0 +1,52 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "vcpkg", + "hidden": true, + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": { + "type": "STRING", + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + }, + "VCPKG_TARGET_TRIPLET": "x64-windows-static-md" + } + }, + { + "name": "win64", + "hidden": true, + "architecture": "x64", + "cacheVariables": { + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL" + } + }, + { + "name": "msvc", + "hidden": true, + "cacheVariables": { + "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX $penv{CXXFLAGS}" + }, + "generator": "Visual Studio 17 2022", + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "intelliSenseMode": "windows-msvc-x64", + "enableMicrosoftCodeAnalysis": true, + "enableClangTidyCodeAnalysis": true + } + } + }, + { + "name": "REL", + "inherits": [ + "vcpkg", + "win64", + "msvc" + ] + } + ] +} \ No newline at end of file diff --git a/CommonLibSF/LICENSE b/CommonLibSF/LICENSE new file mode 100644 index 00000000..325e2cbb --- /dev/null +++ b/CommonLibSF/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Ryan-rsm-McKenzie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/CommonLibSF/cmake/build_stl_modules.props b/CommonLibSF/cmake/build_stl_modules.props new file mode 100644 index 00000000..a8184de9 --- /dev/null +++ b/CommonLibSF/cmake/build_stl_modules.props @@ -0,0 +1,8 @@ + + + + + false + + + \ No newline at end of file diff --git a/CommonLibSF/cmake/config.cmake.in b/CommonLibSF/cmake/config.cmake.in new file mode 100644 index 00000000..95aea02c --- /dev/null +++ b/CommonLibSF/cmake/config.cmake.in @@ -0,0 +1,5 @@ +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@.cmake") +include(CMakeFindDependencyMacro) + +find_dependency(spdlog CONFIG) diff --git a/CommonLibSF/cmake/make-directives.ps1 b/CommonLibSF/cmake/make-directives.ps1 new file mode 100644 index 00000000..434f446f --- /dev/null +++ b/CommonLibSF/cmake/make-directives.ps1 @@ -0,0 +1,54 @@ +#Requires -Version 5 + +# args +param ( + [string]$PathIn +) + +$ErrorActionPreference = "Stop" + +function Normalize-Path { + param ( + [string]$in + ) + + $out = $in -replace '\\', '/' + while ($out.Contains('//')) { + $out = $out -replace '//', '/' + } + return $out +} + +function Resolve-Files { + process { + Push-Location "$PathIn/include" + $_generated = [System.Collections.ArrayList]::new(2048) + + try { + Get-ChildItem "$PathIn/include/RE" -Recurse -File -ErrorAction SilentlyContinue + | Where-Object { ($_.Extension -eq '.h') } | Resolve-Path -Relative | ForEach-Object { + if (!$_.EndsWith('Starfield.h')) { + Write-Host "`t<$_>" + $_generated.Add("`n#include `"$(Normalize-Path $_.Substring(2))`"") | Out-Null + } + } + } + finally { + Pop-Location + } + + return $_generated + } +} + +# @@SOURCEGEN +Write-Host "`tGenerating Starfield.h..." +Remove-Item "$PathIn/Starfield.h" -Force -Confirm:$false -ErrorAction Ignore + +$generated = '#pragma once + +#include "SFSE/Impl/PCH.h" +' +$generated += Resolve-Files +$generated += "`n" +[IO.File]::WriteAllText("$PathIn/include/RE/Starfield.h", $generated) \ No newline at end of file diff --git a/CommonLibSF/cmake/make-sourcelist.ps1 b/CommonLibSF/cmake/make-sourcelist.ps1 new file mode 100644 index 00000000..bc653937 --- /dev/null +++ b/CommonLibSF/cmake/make-sourcelist.ps1 @@ -0,0 +1,64 @@ +#Requires -Version 5 + +# args +param ( + [string]$PathIn, + [string]$PathOut +) + +$ErrorActionPreference = "Stop" + +$SourceExt = @('.asm', '.c', '.cc', '.cpp', '.cxx', '.h', '.hpp', '.hxx', 'inc', '.inl', '.ixx') +$ConfigExt = @('.ini', '.json', '.toml', '.xml') +$DocsExt = @('.md') + +function Normalize-Path { + param ( + [string]$in + ) + + $out = $in -replace '\\', '/' + while ($out.Contains('//')) { + $out = $out -replace '//', '/' + } + return $out +} + +function Resolve-Files { + param ( + [string[]]$range = @('include', 'src', 'test') + ) + + process { + Push-Location $PathIn + $_generated = [System.Collections.ArrayList]::new(2048) + + try { + foreach ($directory in $range) { + Write-Host "[$PathIn/$directory]" + + Get-ChildItem "$PathIn/$directory" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { + ($_.Extension -in ($SourceExt + $DocsExt)) -and + ($_.Name -notmatch 'Plugin.h|Version.h') + } | Resolve-Path -Relative | ForEach-Object { + Write-Host "`t<$_>" + $_generated.Add("`n`t`"$(Normalize-Path $_.Substring(2))`"") | Out-Null + } + } + } + finally { + Pop-Location + } + + return $_generated + } +} + +# @@SOURCEGEN +Write-Host "`tGenerating CMake sourcelist..." +Remove-Item "$PathOut/sourcelist.cmake" -Force -Confirm:$false -ErrorAction Ignore + +$generated = 'set(SOURCES' +$generated += Resolve-Files +$generated += "`n)" +[IO.File]::WriteAllText("$PathOut/sourcelist.cmake", $generated) diff --git a/CommonLibSF/include/RE/Offsets.h b/CommonLibSF/include/RE/Offsets.h new file mode 100644 index 00000000..41c7e864 --- /dev/null +++ b/CommonLibSF/include/RE/Offsets.h @@ -0,0 +1,7 @@ +#pragma once + +#include "REL/Relocation.h" + +namespace RE::Offset +{ +} diff --git a/CommonLibSF/include/RE/Offsets_NiRTTI.h b/CommonLibSF/include/RE/Offsets_NiRTTI.h new file mode 100644 index 00000000..fad7d07d --- /dev/null +++ b/CommonLibSF/include/RE/Offsets_NiRTTI.h @@ -0,0 +1,7 @@ +#pragma once + +#include "REL/Relocation.h" + +namespace RE +{ +} diff --git a/CommonLibSF/include/RE/Offsets_RTTI.h b/CommonLibSF/include/RE/Offsets_RTTI.h new file mode 100644 index 00000000..fad7d07d --- /dev/null +++ b/CommonLibSF/include/RE/Offsets_RTTI.h @@ -0,0 +1,7 @@ +#pragma once + +#include "REL/Relocation.h" + +namespace RE +{ +} diff --git a/CommonLibSF/include/RE/Offsets_VTABLE.h b/CommonLibSF/include/RE/Offsets_VTABLE.h new file mode 100644 index 00000000..fad7d07d --- /dev/null +++ b/CommonLibSF/include/RE/Offsets_VTABLE.h @@ -0,0 +1,7 @@ +#pragma once + +#include "REL/Relocation.h" + +namespace RE +{ +} diff --git a/CommonLibSF/include/RE/RTTI.h b/CommonLibSF/include/RE/RTTI.h new file mode 100644 index 00000000..25c9dec4 --- /dev/null +++ b/CommonLibSF/include/RE/RTTI.h @@ -0,0 +1,246 @@ +#pragma once + +// NOTE: this has not been updated + +struct __type_info_node; + +namespace RE +{ + namespace msvc + { + class __declspec(novtable) type_info + { + public: + virtual ~type_info(); // 00 + + [[nodiscard]] const char* mangled_name() const noexcept { return _name; } + + private: + // members + void* _data; // 08 + char _name[1]; // 10 + }; + static_assert(sizeof(type_info) == 0x18); + } + + namespace RTTI + { + template + class RVA + { + public: + using value_type = T; + using pointer = value_type*; + using reference = value_type&; + + constexpr RVA() noexcept = default; + + constexpr RVA(std::uint32_t a_rva) noexcept : + _rva(a_rva) + {} + + [[nodiscard]] pointer get() const { return is_good() ? REL::Relocation{ REL::Offset(_rva) }.get() : nullptr; } + [[nodiscard]] std::uint32_t offset() const noexcept { return _rva; } + [[nodiscard]] reference operator*() const { return *get(); } + [[nodiscard]] pointer operator->() const { return get(); } + [[nodiscard]] explicit constexpr operator bool() const noexcept { return is_good(); } + + protected: + [[nodiscard]] constexpr bool is_good() const noexcept { return _rva != 0; } + + // members + std::uint32_t _rva{ 0 }; // 00 + }; + static_assert(sizeof(RVA) == 0x4); + + using TypeDescriptor = msvc::type_info; + + struct PMD + { + public: + // members + std::int32_t mDisp; // 0 + std::int32_t pDisp; // 4 + std::int32_t vDisp; // 8 + }; + static_assert(sizeof(PMD) == 0xC); + + struct BaseClassDescriptor + { + public: + enum class Attribute : std::uint32_t + { + kNone = 0, + kNotVisible = 1 << 0, + kAmbiguous = 1 << 1, + kPrivate = 1 << 2, + kPrivateOrProtectedBase = 1 << 3, + kVirtual = 1 << 4, + kNonPolymorphic = 1 << 5, + kHasHierarchyDescriptor = 1 << 6 + }; + + // members + RVA typeDescriptor; // 00 + std::uint32_t numContainedBases; // 04 + PMD pmd; // 08 + stl::enumeration attributes; // 14 + }; + static_assert(sizeof(BaseClassDescriptor) == 0x18); + + struct ClassHierarchyDescriptor + { + public: + enum class Attribute + { + kNoInheritance = 0, + kMultipleInheritance = 1 << 0, + kVirtualInheritance = 1 << 1, + kAmbiguousInheritance = 1 << 2 + }; + + // members + std::uint32_t signature; // 00 + stl::enumeration attributes; // 04 + std::uint32_t numBaseClasses; // 08 + RVA baseClassArray; // 0C + }; + static_assert(sizeof(ClassHierarchyDescriptor) == 0x10); + + struct CompleteObjectLocator + { + public: + enum class Signature + { + x86 = 0, + x64 = 1 + }; + + // members + stl::enumeration signature; // 00 + std::uint32_t offset; // 04 + std::uint32_t ctorDispOffset; // 08 + RVA typeDescriptor; // 0C + RVA classDescriptor; // 10 + }; + static_assert(sizeof(CompleteObjectLocator) == 0x14); + } + + inline void* RTDynamicCast(void* a_inptr, std::int32_t a_vfDelta, void* a_srcType, void* a_targetType, std::int32_t a_isReference) + { + + using func_t = decltype(&RTDynamicCast); + REL::Relocation func{ 0x0 }; + return func(a_inptr, a_vfDelta, a_srcType, a_targetType, a_isReference); + } + + namespace detail + { + template + using remove_cvpr_t = + std::remove_cv_t< + std::remove_pointer_t< + std::remove_reference_t>>; + + template + struct target_is_valid : + std::disjunction< + std::is_polymorphic< + remove_cvpr_t>, + std::is_same< + void*, + std::remove_cv_t>> + {}; + + template + struct types_are_compat : + std::false_type + {}; + + template + struct types_are_compat : + std::is_pointer + {}; + + template + struct types_are_compat : + std::conjunction< + std::is_pointer, + std::is_const< + std::remove_pointer_t>> + {}; + + template + struct types_are_compat : + std::conjunction< + std::is_pointer, + std::is_volatile< + std::remove_pointer_t>> + {}; + + template + struct types_are_compat : + std::conjunction< + std::is_pointer, + std::is_const< + std::remove_pointer_t>, + std::is_volatile< + std::remove_pointer_t>> + {}; + + template + struct implements_rtti : + std::false_type + {}; + + template + struct implements_rtti< + T, + std::void_t< + decltype(remove_cvpr_t::RTTI)>> : + std::true_type + {}; + + template + struct cast_is_valid : + std::conjunction< + types_are_compat< + To, + From>, + target_is_valid< + To>, + implements_rtti, + implements_rtti> + {}; + + template + inline constexpr bool cast_is_valid_v = cast_is_valid::value; + } +} + +template < + class To, + class From, + std::enable_if_t< + RE::detail::cast_is_valid_v< + To, + From*>, + int> = 0> +To starfield_cast(From* a_from) +{ + REL::Relocation from{ RE::detail::remove_cvpr_t::RTTI }; + REL::Relocation to{ RE::detail::remove_cvpr_t::RTTI }; + + if (!from.get() || !to.get()) { + return nullptr; + } + + return static_cast( + RE::RTDynamicCast( + const_cast( + static_cast(a_from)), + 0, + from.get(), + to.get(), + false)); +} diff --git a/CommonLibSF/include/RE/Starfield.h b/CommonLibSF/include/RE/Starfield.h new file mode 100644 index 00000000..69a49387 --- /dev/null +++ b/CommonLibSF/include/RE/Starfield.h @@ -0,0 +1,9 @@ +#pragma once + +#include "SFSE/Impl/PCH.h" + +#include "RE/Offsets_NiRTTI.h" +#include "RE/Offsets_RTTI.h" +#include "RE/Offsets_VTABLE.h" +#include "RE/Offsets.h" +#include "RE/RTTI.h" diff --git a/CommonLibSF/include/REL/Relocation.h b/CommonLibSF/include/REL/Relocation.h new file mode 100644 index 00000000..220a255a --- /dev/null +++ b/CommonLibSF/include/REL/Relocation.h @@ -0,0 +1,617 @@ +#pragma once + +#define REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER_IMPL(a_nopropQual, a_propQual, ...) \ + template < \ + class R, \ + class Cls, \ + class... Args> \ + struct member_function_pod_type \ + { \ + using type = R(__VA_ARGS__ Cls*, Args...) a_propQual; \ + }; \ + \ + template < \ + class R, \ + class Cls, \ + class... Args> \ + struct member_function_pod_type \ + { \ + using type = R(__VA_ARGS__ Cls*, Args..., ...) a_propQual; \ + }; + +#define REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER(a_qualifer, ...) \ + REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER_IMPL(a_qualifer, , ##__VA_ARGS__) \ + REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER_IMPL(a_qualifer, noexcept, ##__VA_ARGS__) + +#define REL_MAKE_MEMBER_FUNCTION_POD_TYPE(...) \ + REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER(, __VA_ARGS__) \ + REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER(&, ##__VA_ARGS__) \ + REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER(&&, ##__VA_ARGS__) + +#define REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER_IMPL(a_nopropQual, a_propQual, ...) \ + template < \ + class R, \ + class Cls, \ + class... Args> \ + struct member_function_non_pod_type \ + { \ + using type = R&(__VA_ARGS__ Cls*, void*, Args...)a_propQual; \ + }; \ + \ + template < \ + class R, \ + class Cls, \ + class... Args> \ + struct member_function_non_pod_type \ + { \ + using type = R&(__VA_ARGS__ Cls*, void*, Args..., ...)a_propQual; \ + }; + +#define REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER(a_qualifer, ...) \ + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER_IMPL(a_qualifer, , ##__VA_ARGS__) \ + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER_IMPL(a_qualifer, noexcept, ##__VA_ARGS__) + +#define REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(...) \ + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER(, __VA_ARGS__) \ + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER(&, ##__VA_ARGS__) \ + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER(&&, ##__VA_ARGS__) + + +namespace REL +{ + namespace detail + { + template + struct member_function_pod_type; + + REL_MAKE_MEMBER_FUNCTION_POD_TYPE(); + REL_MAKE_MEMBER_FUNCTION_POD_TYPE(const); + REL_MAKE_MEMBER_FUNCTION_POD_TYPE(volatile); + REL_MAKE_MEMBER_FUNCTION_POD_TYPE(const volatile); + + template + using member_function_pod_type_t = typename member_function_pod_type::type; + + template + struct member_function_non_pod_type; + + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(); + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(const); + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(volatile); + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(const volatile); + + template + using member_function_non_pod_type_t = typename member_function_non_pod_type::type; + + // https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention + + template + struct meets_length_req : + std::disjunction< + std::bool_constant, + std::bool_constant, + std::bool_constant, + std::bool_constant> + { + }; + + template + struct meets_function_req : + std::conjunction< + std::is_trivially_constructible, + std::is_trivially_destructible, + std::is_trivially_copy_assignable, + std::negation< + std::is_polymorphic>> + { + }; + + template + struct meets_member_req : + std::is_standard_layout + { + }; + + template + struct is_x64_pod : + std::true_type + { + }; + + template + struct is_x64_pod< + T, + std::enable_if_t< + std::is_union_v>> : + std::false_type + { + }; + + template + struct is_x64_pod< + T, + std::enable_if_t< + std::is_class_v>> : + std::conjunction< + meets_length_req, + meets_function_req, + meets_member_req> + { + }; + + template + static constexpr bool is_x64_pod_v = is_x64_pod::value; + + template < + class F, + class First, + class... Rest> + decltype(auto) invoke_member_function_non_pod(F&& a_func, First&& a_first, Rest&&... a_rest) // + noexcept(std::is_nothrow_invocable_v) + { + using result_t = std::invoke_result_t; + alignas(result_t) std::byte result[sizeof(result_t)]{}; + + using func_t = member_function_non_pod_type_t; + auto func = std::bit_cast(std::forward(a_func)); + + return func(std::forward(a_first), std::addressof(result), std::forward(a_rest)...); + } + } + + inline constexpr std::uint8_t NOP = 0x90; + inline constexpr std::uint8_t NOP2[] = { 0x66, 0x90 }; + inline constexpr std::uint8_t NOP3[] = { 0x0F, 0x1F, 0x00 }; + inline constexpr std::uint8_t NOP4[] = { 0x0F, 0x1F, 0x40, 0x00 }; + inline constexpr std::uint8_t NOP5[] = { 0x0F, 0x1F, 0x44, 0x00, 0x00 }; + inline constexpr std::uint8_t NOP6[] = { 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00 }; + inline constexpr std::uint8_t NOP7[] = { 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00 }; + inline constexpr std::uint8_t NOP8[] = { 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }; + inline constexpr std::uint8_t NOP9[] = { 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + inline constexpr std::uint8_t JMP8 = 0xEB; + inline constexpr std::uint8_t JMP32 = 0xE9; + inline constexpr std::uint8_t RET = 0xC3; + inline constexpr std::uint8_t INT3 = 0xCC; + + template + std::invoke_result_t invoke(F&& a_func, Args&&... a_args) // + noexcept(std::is_nothrow_invocable_v) // + requires(std::invocable) + { + if constexpr (std::is_member_function_pointer_v>) { + if constexpr (detail::is_x64_pod_v>) { // member functions == free functions in x64 + using func_t = detail::member_function_pod_type_t>; + auto func = stl::unrestricted_cast(std::forward(a_func)); + return func(std::forward(a_args)...); + } else { // shift args to insert result + return detail::invoke_member_function_non_pod(std::forward(a_func), std::forward(a_args)...); + } + } else { + return std::forward(a_func)(std::forward(a_args)...); + } + } + + inline void safe_write(std::uintptr_t a_dst, const void* a_src, std::size_t a_count) + { + std::uint32_t old{ 0 }; + auto success = + WinAPI::VirtualProtect( + reinterpret_cast(a_dst), + a_count, + (PAGE_EXECUTE_READWRITE), + std::addressof(old)); + if (success != 0) { + std::memcpy(reinterpret_cast(a_dst), a_src, a_count); + success = + WinAPI::VirtualProtect( + reinterpret_cast(a_dst), + a_count, + old, + std::addressof(old)); + } + + assert(success != 0); + } + + template + void safe_write(std::uintptr_t a_dst, const T& a_data) + { + safe_write(a_dst, std::addressof(a_data), sizeof(T)); + } + + template + void safe_write(std::uintptr_t a_dst, std::span a_data) + { + safe_write(a_dst, a_data.data(), a_data.size_bytes()); + } + + inline void safe_fill(std::uintptr_t a_dst, std::uint8_t a_value, std::size_t a_count) + { + std::uint32_t old{ 0 }; + auto success = + WinAPI::VirtualProtect( + reinterpret_cast(a_dst), + a_count, + (PAGE_EXECUTE_READWRITE), + std::addressof(old)); + if (success != 0) { + std::fill_n(reinterpret_cast(a_dst), a_count, a_value); + success = + WinAPI::VirtualProtect( + reinterpret_cast(a_dst), + a_count, + old, + std::addressof(old)); + } + + assert(success != 0); + }; + + class Version + { + public: + using value_type = std::uint16_t; + using reference = value_type&; + using const_reference = const value_type&; + + constexpr Version() noexcept = default; + + explicit constexpr Version(std::array a_version) noexcept : + _impl(a_version) {} + + constexpr Version(value_type a_v1, value_type a_v2 = 0, value_type a_v3 = 0, value_type a_v4 = 0) noexcept : + _impl{ a_v1, a_v2, a_v3, a_v4 } {} + + explicit constexpr Version(std::string_view a_version) + { + std::array powers{ 1, 1, 1, 1 }; + std::size_t position = 0; + for (std::size_t i = 0; i < a_version.size(); ++i) { + if (a_version[i] == '.') { + if (++position == powers.size()) { + throw std::invalid_argument("Too many parts in version number."); + } + } else { + powers[position] *= 10; + } + } + position = 0; + for (std::size_t i = 0; i < a_version.size(); ++i) { + if (a_version[i] == '.') { + ++position; + } else if (a_version[i] < '0' || a_version[i] > '9') { + throw std::invalid_argument("Invalid character in version number."); + } else { + powers[position] /= 10; + _impl[position] += static_cast((a_version[i] - '0') * powers[position]); + } + } + } + + [[nodiscard]] constexpr reference operator[](std::size_t a_idx) noexcept { return _impl[a_idx]; } + + [[nodiscard]] constexpr const_reference operator[](std::size_t a_idx) const noexcept { return _impl[a_idx]; } + + [[nodiscard]] constexpr decltype(auto) begin() const noexcept { return _impl.begin(); } + + [[nodiscard]] constexpr decltype(auto) cbegin() const noexcept { return _impl.cbegin(); } + + [[nodiscard]] constexpr decltype(auto) end() const noexcept { return _impl.end(); } + + [[nodiscard]] constexpr decltype(auto) cend() const noexcept { return _impl.cend(); } + + [[nodiscard]] std::strong_ordering constexpr compare(const Version& a_rhs) const noexcept + { + for (std::size_t i = 0; i < _impl.size(); ++i) { + if ((*this)[i] != a_rhs[i]) { + return (*this)[i] < a_rhs[i] ? std::strong_ordering::less : std::strong_ordering::greater; + } + } + return std::strong_ordering::equal; + } + + [[nodiscard]] constexpr std::uint32_t pack() const noexcept + { + return static_cast( + (_impl[0] & 0x0FF) << 24u | + (_impl[1] & 0x0FF) << 16u | + (_impl[2] & 0xFFF) << 4u | + (_impl[3] & 0x00F) << 0u); + } + + [[nodiscard]] constexpr value_type major() const noexcept + { + return _impl[0]; + } + + [[nodiscard]] constexpr value_type minor() const noexcept + { + return _impl[1]; + } + + [[nodiscard]] constexpr value_type patch() const noexcept + { + return _impl[2]; + } + + [[nodiscard]] constexpr value_type build() const noexcept + { + return _impl[3]; + } + + [[nodiscard]] std::string string(std::string_view a_separator = "-"sv) const + { + std::string result; + for (auto&& ver : _impl) { + result += std::to_string(ver); + result.append(a_separator.data(), a_separator.size()); + } + result.erase(result.size() - a_separator.size(), a_separator.size()); + return result; + } + + [[nodiscard]] std::wstring wstring(std::wstring_view a_separator = L"-"sv) const + { + std::wstring result; + for (auto&& ver : _impl) { + result += std::to_wstring(ver); + result.append(a_separator.data(), a_separator.size()); + } + result.erase(result.size() - a_separator.size(), a_separator.size()); + return result; + } + + [[nodiscard]] static constexpr Version unpack(std::uint32_t a_packedVersion) noexcept + { + return REL::Version{ + static_cast((a_packedVersion >> 24) & 0x0FF), + static_cast((a_packedVersion >> 16) & 0x0FF), + static_cast((a_packedVersion >> 4) & 0xFFF), + static_cast(a_packedVersion & 0x0F) + }; + } + + private: + std::array _impl{ 0, 0, 0, 0 }; + }; + + [[nodiscard]] constexpr bool operator==(const Version& a_lhs, const Version& a_rhs) noexcept + { + return a_lhs.compare(a_rhs) == std::strong_ordering::equal; + } + + [[nodiscard]] constexpr std::strong_ordering + operator<=>(const Version& a_lhs, const Version& a_rhs) noexcept { return a_lhs.compare(a_rhs); } + + namespace literals + { + namespace detail + { + template + constexpr uint8_t read_version(std::array& result) + { + static_assert(C >= '0' && C <= '9', "Invalid character in semantic version literal."); + static_assert(Index < 4, "Too many components in semantic version literal."); + result[Index] += (C - '0'); + return 10; + } + + template + requires(sizeof...(Rest) > 0) + constexpr uint8_t read_version(std::array& result) + { + static_assert(C == '.' || (C >= '0' && C <= '9'), "Invalid character in semantic version literal."); + static_assert(Index < 4, "Too many components in semantic version literal."); + if constexpr (C == '.') { + read_version(result); + return 1; + } else { + auto position = read_version(result); + result[Index] += (C - '0') * position; + return position * 10; + } + } + } + + template + [[nodiscard]] constexpr REL::Version operator""_v() noexcept + { + std::array result{ 0, 0, 0, 0 }; + detail::read_version<0, C...>(result); + return REL::Version(result); + } + + [[nodiscard]] constexpr REL::Version operator""_v(const char* str, std::size_t len) + { + return Version(std::string_view(str, len)); + } + } + + [[nodiscard]] inline std::optional get_file_version(stl::zwstring a_filename) + { + std::uint32_t dummy{ 0 }; + std::vector buf(GetFileVersionInfoSize(a_filename.data(), std::addressof(dummy))); + if (buf.empty()) { + return std::nullopt; + } + + if (!GetFileVersionInfo(a_filename.data(), 0, static_cast(buf.size()), buf.data())) { + return std::nullopt; + } + + void* verBuf{ nullptr }; + std::uint32_t verLen{ 0 }; + if (!VerQueryValue(buf.data(), L"\\StringFileInfo\\040904B0\\ProductVersion", std::addressof(verBuf), + std::addressof(verLen))) { + return std::nullopt; + } + + Version version; + std::wistringstream ss( + std::wstring(static_cast(verBuf), verLen)); + std::wstring token; + for (std::size_t i = 0; i < 4 && std::getline(ss, token, L'.'); ++i) { + version[i] = static_cast(std::stoi(token)); + } + + return version; + } + + class Segment + { + public: + enum Name : std::size_t + { + textx, + idata, + rdata, + data, + pdata, + tls, + textw, + gfids, + total + }; + + Segment() noexcept = default; + + Segment(std::uintptr_t a_proxyBase, std::uintptr_t a_address, std::uintptr_t a_size) noexcept : + _proxyBase(a_proxyBase), + _address(a_address), + _size(a_size) {} + + [[nodiscard]] std::uintptr_t address() const noexcept { return _address; } + + [[nodiscard]] std::size_t offset() const noexcept { return address() - _proxyBase; } + + [[nodiscard]] std::size_t size() const noexcept { return _size; } + + [[nodiscard]] void* pointer() const noexcept { return reinterpret_cast(address()); } + + template + [[nodiscard]] T* pointer() const noexcept + { + return static_cast(pointer()); + } + + private: + friend class Module; + + std::uintptr_t _proxyBase{ 0 }; + std::uintptr_t _address{ 0 }; + std::size_t _size{ 0 }; + }; + + class Module + { + public: + constexpr Module() = delete; + explicit Module(std::uintptr_t a_base); + explicit Module(std::string_view a_filePath); + + [[nodiscard]] constexpr auto base() const noexcept { return _base; } + template + [[nodiscard]] constexpr auto* pointer() const noexcept { return std::bit_cast(base()); } + [[nodiscard]] constexpr auto segment(Segment::Name a_segment) noexcept { return _segments[a_segment]; } + [[nodiscard]] static Module& get(const std::uintptr_t a_address) noexcept + { + static std::unordered_map managed; + + const auto base = AsAddress(a_address) & ~3; + if (!managed.contains(base)) { + managed.try_emplace(base, base); + } + + return managed.at(base); + } + + [[nodiscard]] static Module& get(std::string_view a_filePath = {}) noexcept + { + const auto base = AsAddress(GetModuleHandle(a_filePath.empty() ? WinAPI::GetProcPath().data() : a_filePath.data())); + return get(base); + } + + private: + static constexpr std::array SEGMENTS{ + std::make_pair(".text"sv, IMAGE_SCN_MEM_EXECUTE), + std::make_pair(".idata"sv, static_cast(0)), + std::make_pair(".rdata"sv, static_cast(0)), + std::make_pair(".data"sv, static_cast(0)), + std::make_pair(".pdata"sv, static_cast(0)), + std::make_pair(".tls"sv, static_cast(0)), + std::make_pair(".text"sv, IMAGE_SCN_MEM_WRITE), + std::make_pair(".gfids"sv, static_cast(0)) + }; + + std::uintptr_t _base; + std::array _segments; + }; + + class Offset + { + public: + constexpr Offset() = default; + constexpr Offset(std::ptrdiff_t a_offset) : + _offset(a_offset) + {} + + [[nodiscard]] constexpr std::uintptr_t offset() const noexcept + { + return _offset; + } + + [[nodiscard]] constexpr std::uintptr_t address() const noexcept + { + return Module::get().base() + _offset; + } + + private: + std::ptrdiff_t _offset{ 0 }; + }; + + + template || std::is_function_v>, std::decay_t, T>> + class Relocation + { + public: + constexpr Relocation() noexcept = default; + constexpr Relocation(const std::uintptr_t a_addr) noexcept : + _address(a_addr) + {} + constexpr Relocation(Offset a_rva) noexcept : + _address(a_rva.address()) + {} + constexpr Relocation(Offset a_rva, std::ptrdiff_t a_offset) noexcept : + _address(a_rva.address() + a_offset) + {} + + [[nodiscard]] constexpr U get() const + noexcept(std::is_nothrow_copy_constructible_v) + { + return std::bit_cast(_address); + } + + [[nodiscard]] constexpr decltype(auto) address() const noexcept + { + return _address; + } + + [[nodiscard]] constexpr decltype(auto) operator*() const noexcept + requires(std::is_pointer_v) + { + return *get(); + } + + template + std::invoke_result_t operator()(Args&&... a_args) const // + noexcept(std::is_nothrow_invocable_v) // + requires(std::invocable) + { + return invoke(get(), std::forward(a_args)...); + } + + private: + std::uintptr_t _address{ 0 }; + }; +} diff --git a/CommonLibSF/include/SFSE/API.h b/CommonLibSF/include/SFSE/API.h new file mode 100644 index 00000000..e3fd1908 --- /dev/null +++ b/CommonLibSF/include/SFSE/API.h @@ -0,0 +1,22 @@ +#pragma once + +#include "SFSE/Impl/Stubs.h" +#include "SFSE/Interfaces.h" +#include "SFSE/Trampoline.h" +#include "SFSE/Version.h" + +#define SFSEAPI __cdecl + +namespace SFSE +{ + void Init(const LoadInterface* a_intfc) noexcept; + void RegisterForAPIInitEvent(std::function a_fn); + + PluginHandle GetPluginHandle() noexcept; + + const TrampolineInterface* GetTrampolineInterface() noexcept; + const MessagingInterface* GetMessagingInterface() noexcept; + + Trampoline& GetTrampoline(); + void AllocTrampoline(std::size_t a_size, bool a_trySFSEReserve = true); +} diff --git a/CommonLibSF/include/SFSE/IAT.h b/CommonLibSF/include/SFSE/IAT.h new file mode 100644 index 00000000..06e02545 --- /dev/null +++ b/CommonLibSF/include/SFSE/IAT.h @@ -0,0 +1,31 @@ +#pragma once + +namespace SFSE +{ + [[nodiscard]] std::uintptr_t GetIATAddr(std::string_view a_dll, std::string_view a_function); + [[nodiscard]] std::uintptr_t GetIATAddr(void* a_module, std::string_view a_dll, std::string_view a_function); + + [[nodiscard]] void* GetIATPtr(std::string_view a_dll, std::string_view a_function); + + template + [[nodiscard]] inline T* GetIATPtr(std::string_view a_dll, std::string_view a_function) + { + return static_cast(GetIATPtr(std::move(a_dll), std::move(a_function))); + } + + [[nodiscard]] void* GetIATPtr(void* a_module, std::string_view a_dll, std::string_view a_function); + + template + [[nodiscard]] inline T* GetIATPtr(void* a_module, std::string_view a_dll, std::string_view a_function) + { + return static_cast(GetIATPtr(a_module, std::move(a_dll), std::move(a_function))); + } + + std::uintptr_t PatchIAT(std::uintptr_t a_newFunc, std::string_view a_dll, std::string_view a_function); + + template + inline std::uintptr_t PatchIAT(F a_newFunc, std::string_view a_dll, std::string_view a_function) + { + return PatchIAT(stl::unrestricted_cast(a_newFunc), a_dll, a_function); + } +} diff --git a/CommonLibSF/include/SFSE/Impl/DInputAPI.h b/CommonLibSF/include/SFSE/Impl/DInputAPI.h new file mode 100644 index 00000000..59480c7e --- /dev/null +++ b/CommonLibSF/include/SFSE/Impl/DInputAPI.h @@ -0,0 +1,571 @@ +#pragma once + +#include "WinAPI.h" +#include + +// TODO: This should probably be behind some sort of pragma that allows linking with dinput and dinput8 +namespace RE::DirectInput8 +{ + using DWORD = std::uint32_t; + using LONG = std::int32_t; + using WORD = std::uint16_t; + using BYTE = std::uint8_t; + using UINT = std::uint32_t; + using CHAR = char; + + using LPSTR = char*; + using LPCSTR = const char*; + + using UINT_PTR = std::uintptr_t; + using HANDLE = void*; + using LPDWORD = DWORD*; + using LPVOID = void*; + using LPLONG = LONG*; + using LPWORD = WORD*; + + using GUID = SFSE::WinAPI::GUID; + using LPGUID = GUID*; + using REFGUID = const GUID&; + using FILETIME = SFSE::WinAPI::FILETIME; + using RECT = SFSE::WinAPI::RECT; + using POINT = SFSE::WinAPI::POINT; + + using IID = GUID; + using REFIID = const IID&; + using LPIID = IID*; + using D3DCOLOR = std::uint32_t; + + using HRESULT = SFSE::WinAPI::HRESULT; + using HWND = SFSE::WinAPI::HWND; + using HINSTANCE = SFSE::WinAPI::HINSTANCE; + + enum DIKey : uint32_t + { + DIK_ESCAPE = 0x1, + DIK_1 = 0x2, + DIK_2 = 0x3, + DIK_3 = 0x4, + DIK_4 = 0x5, + DIK_5 = 0x6, + DIK_6 = 0x7, + DIK_7 = 0x8, + DIK_8 = 0x9, + DIK_9 = 0xA, + DIK_0 = 0xB, + DIK_MINUS = 0xC, + DIK_EQUALS = 0xD, + DIK_BACK = 0xE, + DIK_TAB = 0xF, + DIK_Q = 0x10, + DIK_W = 0x11, + DIK_E = 0x12, + DIK_R = 0x13, + DIK_T = 0x14, + DIK_Y = 0x15, + DIK_U = 0x16, + DIK_I = 0x17, + DIK_O = 0x18, + DIK_P = 0x19, + DIK_LBRACKET = 0x1A, + DIK_RBRACKET = 0x1B, + DIK_RETURN = 0x1C, + DIK_LCONTROL = 0x1D, + DIK_A = 0x1E, + DIK_S = 0x1F, + DIK_D = 0x20, + DIK_F = 0x21, + DIK_G = 0x22, + DIK_H = 0x23, + DIK_J = 0x24, + DIK_K = 0x25, + DIK_L = 0x26, + DIK_SEMICOLON = 0x27, + DIK_APOSTROPHE = 0x28, + DIK_GRAVE = 0x29, + DIK_LSHIFT = 0x2A, + DIK_BACKSLASH = 0x2B, + DIK_Z = 0x2C, + DIK_X = 0x2D, + DIK_C = 0x2E, + DIK_V = 0x2F, + DIK_B = 0x30, + DIK_N = 0x31, + DIK_M = 0x32, + DIK_COMMA = 0x33, + DIK_PERIOD = 0x34, + DIK_SLASH = 0x35, + DIK_RSHIFT = 0x36, + DIK_MULTIPLY = 0x37, + DIK_LMENU = 0x38, + DIK_SPACE = 0x39, + DIK_CAPITAL = 0x3A, + DIK_F1 = 0x3B, + DIK_F2 = 0x3C, + DIK_F3 = 0x3D, + DIK_F4 = 0x3E, + DIK_F5 = 0x3F, + DIK_F6 = 0x40, + DIK_F7 = 0x41, + DIK_F8 = 0x42, + DIK_F9 = 0x43, + DIK_F10 = 0x44, + DIK_NUMLOCK = 0x45, + DIK_SCROLL = 0x46, + DIK_NUMPAD7 = 0x47, + DIK_NUMPAD8 = 0x48, + DIK_NUMPAD9 = 0x49, + DIK_SUBTRACT = 0x4A, + DIK_NUMPAD4 = 0x4B, + DIK_NUMPAD5 = 0x4C, + DIK_NUMPAD6 = 0x4D, + DIK_ADD = 0x4E, + DIK_NUMPAD1 = 0x4F, + DIK_NUMPAD2 = 0x50, + DIK_NUMPAD3 = 0x51, + DIK_NUMPAD0 = 0x52, + DIK_DECIMAL = 0x53, + DIK_OEM_102 = 0x56, + DIK_F11 = 0x57, + DIK_F12 = 0x58, + DIK_F13 = 0x64, + DIK_F14 = 0x65, + DIK_F15 = 0x66, + DIK_KANA = 0x70, + DIK_ABNT_C1 = 0x73, + DIK_CONVERT = 0x79, + DIK_NOCONVERT = 0x7B, + DIK_YEN = 0x7D, + DIK_ABNT_C2 = 0x7E, + DIK_NUMPADEQUALS = 0x8D, + DIK_PREVTRACK = 0x90, + DIK_AT = 0x91, + DIK_COLON = 0x92, + DIK_UNDERLINE = 0x93, + DIK_KANJI = 0x94, + DIK_STOP = 0x95, + DIK_AX = 0x96, + DIK_UNLABELED = 0x97, + DIK_NEXTTRACK = 0x99, + DIK_NUMPADENTER = 0x9C, + DIK_RCONTROL = 0x9D, + DIK_MUTE = 0xA0, + DIK_CALCULATOR = 0xA1, + DIK_PLAYPAUSE = 0xA2, + DIK_MEDIASTOP = 0xA4, + DIK_VOLUMEDOWN = 0xAE, + DIK_VOLUMEUP = 0xB0, + DIK_WEBHOME = 0xB2, + DIK_NUMPADCOMMA = 0xB3, + DIK_DIVIDE = 0xB5, + DIK_SYSRQ = 0xB7, + DIK_RMENU = 0xB8, + DIK_PAUSE = 0xC5, + DIK_HOME = 0xC7, + DIK_UP = 0xC8, + DIK_PRIOR = 0xC9, + DIK_LEFT = 0xCB, + DIK_RIGHT = 0xCD, + DIK_END = 0xCF, + DIK_DOWN = 0xD0, + DIK_NEXT = 0xD1, + DIK_INSERT = 0xD2, + DIK_DELETE = 0xD3, + DIK_LWIN = 0xDB, + DIK_RWIN = 0xDC, + DIK_APPS = 0xDD, + DIK_POWER = 0xDE, + DIK_SLEEP = 0xDF, + DIK_WAKE = 0xE3, + DIK_WEBSEARCH = 0xE5, + DIK_WEBFAVORITES = 0xE6, + DIK_WEBREFRESH = 0xE7, + DIK_WEBSTOP = 0xE8, + DIK_WEBFORWARD = 0xE9, + DIK_WEBBACK = 0xEA, + DIK_MYCOMPUTER = 0xEB, + DIK_MAIL = 0xEC, + DIK_MEDIASELECT = 0xED, + DIK_BACKSPACE = DIK_BACK, + DIK_NUMPADSTAR = DIK_MULTIPLY, + DIK_LALT = DIK_LMENU, + DIK_CAPSLOCK = DIK_CAPITAL, + DIK_NUMPADMINUS = DIK_SUBTRACT, + DIK_NUMPADPLUS = DIK_ADD, + DIK_NUMPADPERIOD = DIK_DECIMAL, + DIK_NUMPADSLASH = DIK_DIVIDE, + DIK_RALT = DIK_RMENU, + DIK_UPARROW = DIK_UP, + DIK_PGUP = DIK_PRIOR, + DIK_LEFTARROW = DIK_LEFT, + DIK_RIGHTARROW = DIK_RIGHT, + DIK_DOWNARROW = DIK_DOWN, + DIK_PGDN = DIK_NEXT + }; + + struct DIMOUSESTATE2__ + { + LONG lX; + LONG lY; + LONG lZ; + BYTE rgbButtons[8]; + }; + static_assert(sizeof(DIMOUSESTATE2__) == 0x14); + using DIMOUSESTATE2 = DIMOUSESTATE2__; + using LPDIMOUSESTATE2 = DIMOUSESTATE2*; + + struct DIDEVICEOBJECTDATA__ + { + DWORD dwOfs; // 00 + DWORD dwData; // 04 + DWORD dwTimeStamp; // 08 + DWORD dwSequence; // 0C + UINT_PTR uAppData; // 10 + }; + static_assert(sizeof(DIDEVICEOBJECTDATA__) == 0x18); + using DIDEVICEOBJECTDATA = DIDEVICEOBJECTDATA__; + using LPDIDEVICEOBJECTDATA = DIDEVICEOBJECTDATA*; + using LPCDIDEVICEOBJECTDATA = const DIDEVICEOBJECTDATA*; + + struct DIDEVICEINSTANCEA__ + { + DWORD dwSize; + GUID guidInstance; + GUID guidProduct; + DWORD dwDevType; + CHAR tszInstanceName[MAX_PATH]; + CHAR tszProductName[MAX_PATH]; + GUID guidFFDriver; // DIRECTINPUT_VERSION >= 0x0500 + WORD wUsagePage; + WORD wUsage; + }; + using DIDEVICEINSTANCEA = DIDEVICEINSTANCEA__; + using LPDIDEVICEINSTANCEA = DIDEVICEINSTANCEA*; + using LPCDIDEVICEINSTANCEA = const DIDEVICEINSTANCEA*; + + struct DIDEVICEOBJECTINSTANCEA__ + { + DWORD dwSize; + GUID guidType; + DWORD dwOfs; + DWORD dwType; + DWORD dwFlags; + CHAR tszName[MAX_PATH]; + //#if(DIRECTINPUT_VERSION >= 0x0500) + DWORD dwFFMaxForce; + DWORD dwFFForceResolution; + WORD wCollectionNumber; + WORD wDesignatorIndex; + WORD wUsagePage; + WORD wUsage; + DWORD dwDimension; + WORD wExponent; + WORD wReportId; + //#endif /* DIRECTINPUT_VERSION >= 0x0500 */ + }; + using DIDEVICEOBJECTINSTANCEA = DIDEVICEOBJECTINSTANCEA__; + using LPDIDEVICEOBJECTINSTANCEA = DIDEVICEOBJECTINSTANCEA*; + using LPCDIDEVICEOBJECTINSTANCEA = const DIDEVICEOBJECTINSTANCEA*; + + using LPDIENUMDEVICEOBJECTSCALLBACKA = bool(__stdcall*)(LPCDIDEVICEOBJECTINSTANCEA, LPVOID); + + struct DIPROPHEADER__ + { + DWORD dwSize; + DWORD dwHeaderSize; + DWORD dwObj; + DWORD dwHow; + }; + + using DIPROPHEADER = DIPROPHEADER__; + using LPDIPROPHEADER = DIPROPHEADER*; + using LPCDIPROPHEADER = const DIPROPHEADER*; + + struct DIDEVCAPS__ + { + DWORD dwSize; + DWORD dwFlags; + DWORD dwDevType; + DWORD dwAxes; + DWORD dwButtons; + DWORD dwPOVs; + DWORD dwFFSamplePeriod; // -- fields here are Dinput >= 0x0500 + DWORD dwFFMinTimeResolution; + DWORD dwFirmwareRevision; + DWORD dwHardwareRevision; + DWORD dwFFDriverVersion; + }; + static_assert(sizeof(DIDEVCAPS__) == 0x2C); + using DIDEVCAPS = DIDEVCAPS__; + using LPDIDEVCAPS = DIDEVCAPS*; + using LPCDIDEVCAPS = const DIDEVCAPS*; + struct _DIDEVICEIMAGEINFOA + { + CHAR tszImagePath[MAX_PATH]; + DWORD dwFlags; + DWORD dwViewID; + RECT rcOverlay; + DWORD dwObjID; + DWORD dwcValidPts; + POINT rgptCalloutLine[5]; + RECT rcCalloutRect; + DWORD dwTextAlign; + }; + using DIDEVICEIMAGEINFOA = _DIDEVICEIMAGEINFOA; + using LPDIDEVICEIMAGEINFOA = DIDEVICEIMAGEINFOA*; + using LPCDIDEVICEIMAGEINFOA = const DIDEVICEIMAGEINFOA*; + + struct _DIOBJECTDATAFORMAT + { + const GUID* pguid; + DWORD dwOfs; + DWORD dwType; + DWORD dwFlags; + }; + using DIOBJECTDATAFORMAT = _DIOBJECTDATAFORMAT; + using LPDIOBJECTDATAFORMAT = DIOBJECTDATAFORMAT*; + using LPCDIOBJECTDATAFORMAT = const DIOBJECTDATAFORMAT*; + + struct _DIDATAFORMAT + { + DWORD dwSize; + DWORD dwObjSize; + DWORD dwFlags; + DWORD dwDataSize; + DWORD dwNumObjs; + LPDIOBJECTDATAFORMAT rgodf; + }; + using DIDATAFORMAT = _DIDATAFORMAT; + using LPDIDATAFORMAT = DIDATAFORMAT*; + using LPCDIDATAFORMAT = const DIDATAFORMAT*; + + struct DIENVELOPE__ + { + DWORD dwSize; /* sizeof(DIENVELOPE) */ + DWORD dwAttackLevel; + DWORD dwAttackTime; /* Microseconds */ + DWORD dwFadeLevel; + DWORD dwFadeTime; /* Microseconds */ + }; + using DIENVELOPE = DIENVELOPE__; + using LPDIENVELOPE = DIENVELOPE*; + using LPCDIENVELOPE = const DIENVELOPE*; + + struct DIEFFECTINFOA__ + { + DWORD dwSize; + GUID guid; + DWORD dwEffType; + DWORD dwStaticParams; + DWORD dwDynamicParams; + CHAR tszName[MAX_PATH]; + }; + using DIEFFECTINFOA = DIEFFECTINFOA__; + using LPDIEFFECTINFOA = DIEFFECTINFOA*; + using LPCDIEFFECTINFOA = const DIEFFECTINFOA*; + + using LPDIENUMEFFECTSCALLBACKA = bool(__stdcall*)(LPCDIEFFECTINFOA, LPVOID); + + struct DIEFFECT__ + { + DWORD dwSize; /* sizeof(DIEFFECT) */ + DWORD dwFlags; /* DIEFF_* */ + DWORD dwDuration; /* Microseconds */ + DWORD dwSamplePeriod; /* Microseconds */ + DWORD dwGain; + DWORD dwTriggerButton; /* or DIEB_NOTRIGGER */ + DWORD dwTriggerRepeatInterval; /* Microseconds */ + DWORD cAxes; /* Number of axes */ + LPDWORD rgdwAxes; /* Array of axes */ + LPLONG rglDirection; /* Array of directions */ + LPDIENVELOPE lpEnvelope; /* Optional */ + DWORD cbTypeSpecificParams; /* Size of params */ + LPVOID lpvTypeSpecificParams; /* Pointer to params */ + DWORD dwStartDelay; /* Microseconds */ // -- fields here are Dinput >= 0x0600 + }; + using DIEFFECT = DIEFFECT__; + using LPDIEFFECT = DIEFFECT*; + using LPCDIEFFECT = const DIEFFECT*; + + struct DIEFFESCAPE__ + { + DWORD dwSize; + DWORD dwCommand; + LPVOID lpvInBuffer; + DWORD cbInBuffer; + LPVOID lpvOutBuffer; + DWORD cbOutBuffer; + }; + using DIEFFESCAPE = DIEFFESCAPE__; + using LPDIEFFESCAPE = DIEFFESCAPE*; + using LPCDIEFFESCAPE = const DIEFFESCAPE*; + struct DIFILEEFFECT__ + { + DWORD dwSize; + GUID GuidEffect; + LPCDIEFFECT lpDiEffect; + CHAR szFriendlyName[MAX_PATH]; + }; + using DIFILEEFFECT = DIFILEEFFECT__; + using LPDIFILEEFFECT = DIFILEEFFECT*; + using LPCDIFILEEFFECT = const DIFILEEFFECT*; + struct _DIACTIONA + { + UINT_PTR uAppData; + DWORD dwSemantic; + DWORD dwFlags; + union + { + LPCSTR lptszActionName; + UINT uResIdString; + }; + GUID guidInstance; + DWORD dwObjID; + DWORD dwHow; + }; + using DIACTIONA = _DIACTIONA; + using LPDIACTIONA = DIACTIONA*; + + struct _DIACTIONFORMATA + { + DWORD dwSize; + DWORD dwActionSize; + DWORD dwDataSize; + DWORD dwNumActions; + LPDIACTIONA rgoAction; + GUID guidActionMap; + DWORD dwGenre; + DWORD dwBufferSize; + LONG lAxisMin; + LONG lAxisMax; + HINSTANCE hInstString; + FILETIME ftTimeStamp; + DWORD dwCRC; + CHAR tszActionMap[MAX_PATH]; + }; + using DIACTIONFORMATA = _DIACTIONFORMATA; + using LPDIACTIONFORMATA = DIACTIONFORMATA*; + using LPCDIACTIONFORMATA = const DIACTIONFORMATA*; + struct _DIDEVICEIMAGEINFOHEADERA + { + DWORD dwSize; + DWORD dwSizeImageInfo; + DWORD dwcViews; + DWORD dwcButtons; + DWORD dwcAxes; + DWORD dwcPOVs; + DWORD dwBufferSize; + DWORD dwBufferUsed; + LPDIDEVICEIMAGEINFOA lprgImageInfoArray; + }; + using DIDEVICEIMAGEINFOHEADERA = _DIDEVICEIMAGEINFOHEADERA; + using LPDIDEVICEIMAGEINFOHEADERA = DIDEVICEIMAGEINFOHEADERA*; + using LPCDIDEVICEIMAGEINFOHEADERA = const DIDEVICEIMAGEINFOHEADERA*; + + struct _DICOLORSET + { + DWORD dwSize; + D3DCOLOR cTextFore; + D3DCOLOR cTextHighlight; + D3DCOLOR cCalloutLine; + D3DCOLOR cCalloutHighlight; + D3DCOLOR cBorder; + D3DCOLOR cControlFill; + D3DCOLOR cHighlightFill; + D3DCOLOR cAreaFill; + }; + using DICOLORSET = _DICOLORSET; + + struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) __declspec(novtable) + IUnknown + { + virtual HRESULT QueryInterface(const GUID&, void**) noexcept = 0; + virtual std::uint32_t AddRef() noexcept = 0; + virtual std::uint32_t Release() noexcept = 0; + }; + using LPUNKNOWN = IUnknown*; + + struct _DICONFIGUREDEVICESPARAMSA + { + DWORD dwSize; + DWORD dwcUsers; + LPSTR lptszUserNames; + DWORD dwcFormats; + LPDIACTIONFORMATA lprgFormats; + HWND hwnd; + DICOLORSET dics; + IUnknown* lpUnkDDSTarget; + }; + using DICONFIGUREDEVICESPARAMSA = _DICONFIGUREDEVICESPARAMSA; + + struct __declspec(novtable) IDirectInputEffect : public IUnknown + { + virtual HRESULT Initialize(HINSTANCE, DWORD, REFGUID) noexcept = 0; + virtual HRESULT GetEffectGuid(LPGUID) noexcept = 0; + virtual HRESULT GetParameters(LPDIEFFECT, DWORD) noexcept = 0; + virtual HRESULT SetParameters(LPCDIEFFECT, DWORD) noexcept = 0; + virtual HRESULT Start(std::uint32_t, std::uint32_t) noexcept = 0; + virtual HRESULT Stop() noexcept = 0; + virtual HRESULT GetEffectStatus(std::uint32_t*) noexcept = 0; + virtual HRESULT Download() noexcept = 0; + virtual HRESULT Unload() noexcept = 0; + virtual HRESULT Escape(LPDIEFFESCAPE) noexcept = 0; + }; + using LPDIRECTINPUTEFFECT = IDirectInputEffect*; + using LPCDIRECTINPUTEFFECT = const IDirectInputEffect*; + + using LPDIENUMCREATEDEFFECTOBJECTSCALLBACK = bool(__stdcall*)(LPDIRECTINPUTEFFECT, void*); + using LPDICONFIGUREDEVICESCALLBACK = bool(__stdcall*)(IUnknown*, void*); + using LPDIENUMDEVICESCALLBACKA = bool(__stdcall*)(const DIDEVICEINSTANCEA*, void*); + + using LPDIENUMEFFECTSCALLBACKA = bool(__stdcall*)(const DIEFFECTINFOA*, void*); + + using LPDIENUMEFFECTSINFILECALLBACK = bool(__stdcall*)(const DIFILEEFFECT*, void*); + + struct __declspec(novtable) IDirectInputDevice8A : public IUnknown + { + virtual HRESULT GetCapabilities(DIDEVCAPS*) noexcept = 0; + virtual HRESULT EnumObjects(LPDIENUMDEVICEOBJECTSCALLBACKA, void*, std::uint32_t) noexcept = 0; + virtual HRESULT GetProperty(std::uint32_t, DIPROPHEADER*) noexcept = 0; + virtual HRESULT SetProperty(std::uint32_t, const DIPROPHEADER*) noexcept = 0; + virtual HRESULT Acquire() noexcept = 0; + virtual HRESULT Unacquire() noexcept = 0; + virtual HRESULT GetDeviceState(std::uint32_t, void*) noexcept = 0; + virtual HRESULT GetDeviceData(std::uint32_t, DIDEVICEOBJECTDATA*, std::uint32_t*, std::uint32_t) noexcept = 0; + virtual HRESULT SetDataFormat(const DIDATAFORMAT*) noexcept = 0; + virtual HRESULT SetEventNotification(HANDLE) noexcept = 0; + virtual HRESULT SetCooperativeLevel(HWND, std::uint32_t) noexcept = 0; + virtual HRESULT GetObjectInfo(DIDEVICEOBJECTINSTANCEA*, std::uint32_t, std::uint32_t) noexcept = 0; + virtual HRESULT GetDeviceInfo(DIDEVICEINSTANCEA*) noexcept = 0; + virtual HRESULT RunControlPanel(HWND, std::uint32_t) noexcept = 0; + virtual HRESULT Initialize(HINSTANCE, std::uint32_t, const GUID&) noexcept = 0; + virtual HRESULT CreateEffect(const GUID&, const DIEFFECT*, IDirectInputEffect*, IUnknown*) noexcept = 0; + virtual HRESULT EnumEffects(LPDIENUMEFFECTSCALLBACKA, void*, std::uint32_t) noexcept = 0; + virtual HRESULT GetEffectInfo(DIEFFECTINFOA*, const GUID&) noexcept = 0; + virtual HRESULT GetForceFeedbackState(std::uint32_t*) noexcept = 0; + virtual HRESULT SendForceFeedbackCommand(std::uint32_t) noexcept = 0; + virtual HRESULT EnumCreatedEffectObjects(LPDIENUMCREATEDEFFECTOBJECTSCALLBACK, void*, std::uint32_t) noexcept = 0; + virtual HRESULT Escape(DIEFFESCAPE*) noexcept = 0; + virtual HRESULT Poll() noexcept = 0; + virtual HRESULT SendDeviceData(std::uint32_t, const DIDEVICEOBJECTDATA*, std::uint32_t*, std::uint32_t) noexcept = 0; + virtual HRESULT EnumEffectsInFile(const char*, LPDIENUMEFFECTSINFILECALLBACK, void*, std::uint32_t) noexcept = 0; + virtual HRESULT WriteEffectToFile(const char*, std::uint32_t, DIFILEEFFECT*, std::uint32_t) noexcept = 0; + virtual HRESULT BuildActionMap(DIACTIONFORMATA*, const char*, std::uint32_t) noexcept = 0; + virtual HRESULT SetActionMap(DIACTIONFORMATA*, const char*, std::uint32_t) noexcept = 0; + virtual HRESULT GetImageInfo(DIDEVICEIMAGEINFOHEADERA*) noexcept = 0; + }; + + using LPDIENUMDEVICESBYSEMANTICSCBA = bool(__stdcall*)(const DIDEVICEINSTANCEA*, IDirectInputDevice8A*, DWORD, DWORD, LPVOID); + + struct __declspec(novtable) IDirectInput8A : public IUnknown + { + virtual HRESULT CreateDevice(const GUID&, IDirectInputDevice8A**, IUnknown*) noexcept = 0; + virtual HRESULT EnumDevices(std::uint32_t, LPDIENUMDEVICESCALLBACKA, void*, std::uint32_t) noexcept = 0; + virtual HRESULT GetDeviceStatus(const GUID&) noexcept = 0; + virtual HRESULT RunControlPanel(HWND, std::uint32_t) noexcept = 0; + virtual HRESULT Initialize(HINSTANCE, std::uint32_t) noexcept = 0; + virtual HRESULT FindDevice(const GUID&, const char*, GUID*) noexcept = 0; + virtual HRESULT EnumDevicesBySemantics(const char*, LPDIACTIONFORMATA, LPDIENUMDEVICESBYSEMANTICSCBA, void*, std::uint32_t) noexcept = 0; + virtual HRESULT ConfigureDevices(LPDICONFIGUREDEVICESCALLBACK, DICONFIGUREDEVICESPARAMSA*, std::uint32_t, void*) noexcept = 0; + }; + +} diff --git a/CommonLibSF/include/SFSE/Impl/PCH.h b/CommonLibSF/include/SFSE/Impl/PCH.h new file mode 100644 index 00000000..ac023e0d --- /dev/null +++ b/CommonLibSF/include/SFSE/Impl/PCH.h @@ -0,0 +1,807 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static_assert( + std::is_integral_v && sizeof(std::time_t) == sizeof(std::size_t), + "wrap std::time_t instead"); + +#pragma warning(push) +#include +#include +#pragma warning(pop) + +#include "SFSE/Impl/DInputAPI.h" +#include "SFSE/Impl/WinAPI.h" +#include "SFSE/Impl/XInputAPI.h" + +#define AsAddress(ptr) std::bit_cast(ptr) +#define AsPointer(addr) std::bit_cast(addr) +#define stl_assert(cond, ...) \ + { \ + if (!((cond))) { \ + SFSE::stl::report_and_fail( \ + fmt::format(__VA_ARGS__)); \ + } \ + } + +namespace SFSE +{ + using namespace std::literals; + + namespace stl + { + template + using basic_zstring = std::basic_string_view; + + using zstring = basic_zstring; + using zwstring = basic_zstring; + + // owning pointer + template < + class T, + class = std::enable_if_t< + std::is_pointer_v>> + using owner = T; + + // non-owning pointer + template < + class T, + class = std::enable_if_t< + std::is_pointer_v>> + using observer = T; + + // non-null pointer + template < + class T, + class = std::enable_if_t< + std::is_pointer_v>> + using not_null = T; + + namespace nttp + { + template + struct string + { + using char_type = CharT; + using pointer = char_type*; + using const_pointer = const char_type*; + using reference = char_type&; + using const_reference = const char_type&; + using size_type = std::size_t; + + static constexpr auto npos = static_cast(-1); + + consteval string(const_pointer a_string) noexcept + { + for (size_type i = 0; i < N; ++i) { + c[i] = a_string[i]; + } + } + + [[nodiscard]] consteval const_reference operator[](size_type a_pos) const noexcept + { + assert(a_pos < N); + return c[a_pos]; + } + + [[nodiscard]] consteval char_type value_at(size_type a_pos) const noexcept + { + assert(a_pos < N); + return c[a_pos]; + } + + [[nodiscard]] consteval const_reference back() const noexcept { return (*this)[size() - 1]; } + [[nodiscard]] consteval const_pointer data() const noexcept { return c; } + [[nodiscard]] consteval bool empty() const noexcept { return this->size() == 0; } + [[nodiscard]] consteval const_reference front() const noexcept { return (*this)[0]; } + [[nodiscard]] consteval size_type length() const noexcept { return N; } + [[nodiscard]] consteval size_type size() const noexcept { return length(); } + + template + [[nodiscard]] consteval auto substr() const noexcept + { + return string < CharT, COUNT != npos ? COUNT : N - POS > (this->data() + POS); + } + + char_type c[N] = {}; + }; + + template + string(const CharT (&)[N]) -> string; + } + + template // + requires(std::invocable>) // + class scope_exit + { + public: + // 1) + template + explicit scope_exit(Fn&& a_fn) // + noexcept(std::is_nothrow_constructible_v || + std::is_nothrow_constructible_v) // + requires(!std::is_same_v, scope_exit> && + std::is_constructible_v) + { + static_assert(std::invocable); + + if constexpr (!std::is_lvalue_reference_v && + std::is_nothrow_constructible_v) { + _fn.emplace(std::forward(a_fn)); + } else { + _fn.emplace(a_fn); + } + } + + // 2) + scope_exit(scope_exit&& a_rhs) // + noexcept(std::is_nothrow_move_constructible_v || + std::is_nothrow_copy_constructible_v) // + requires(std::is_nothrow_move_constructible_v || + std::is_copy_constructible_v) + { + static_assert(!(std::is_nothrow_move_constructible_v && !std::is_move_constructible_v)); + static_assert(!(!std::is_nothrow_move_constructible_v && !std::is_copy_constructible_v)); + + if (a_rhs.active()) { + if constexpr (std::is_nothrow_move_constructible_v) { + _fn.emplace(std::forward(*a_rhs._fn)); + } else { + _fn.emplace(a_rhs._fn); + } + a_rhs.release(); + } + } + + // 3) + scope_exit(const scope_exit&) = delete; + + ~scope_exit() noexcept + { + if (_fn.has_value()) { + (*_fn)(); + } + } + + void release() noexcept { _fn.reset(); } + + private: + [[nodiscard]] bool active() const noexcept { return _fn.has_value(); } + + std::optional> _fn; + }; + + template + scope_exit(EF) -> scope_exit; + + template < + class Enum, + class Underlying = std::underlying_type_t> + class enumeration + { + public: + using enum_type = Enum; + using underlying_type = Underlying; + + static_assert(std::is_enum_v, "enum_type must be an enum"); + static_assert(std::is_integral_v, "underlying_type must be an integral"); + + constexpr enumeration() noexcept = default; + + constexpr enumeration(const enumeration&) noexcept = default; + + constexpr enumeration(enumeration&&) noexcept = default; + + template // NOLINTNEXTLINE(google-explicit-constructor) + constexpr enumeration(enumeration a_rhs) noexcept : + _impl(static_cast(a_rhs.get())) + {} + + template + constexpr enumeration(Args... a_values) noexcept // + requires(std::same_as && ...) + : + _impl((static_cast(a_values) | ...)) + {} + + ~enumeration() noexcept = default; + + constexpr enumeration& operator=(const enumeration&) noexcept = default; + constexpr enumeration& operator=(enumeration&&) noexcept = default; + + template + constexpr enumeration& operator=(enumeration a_rhs) noexcept + { + _impl = static_cast(a_rhs.get()); + } + + constexpr enumeration& operator=(enum_type a_value) noexcept + { + _impl = static_cast(a_value); + return *this; + } + + [[nodiscard]] explicit constexpr operator bool() const noexcept { return _impl != static_cast(0); } + + [[nodiscard]] constexpr enum_type operator*() const noexcept { return get(); } + [[nodiscard]] constexpr enum_type get() const noexcept { return static_cast(_impl); } + [[nodiscard]] constexpr underlying_type underlying() const noexcept { return _impl; } + + template + constexpr enumeration& set(Args... a_args) noexcept // + requires(std::same_as && ...) + { + _impl |= (static_cast(a_args) | ...); + return *this; + } + + template + constexpr enumeration& reset(Args... a_args) noexcept // + requires(std::same_as && ...) + { + _impl &= ~(static_cast(a_args) | ...); + return *this; + } + + template + [[nodiscard]] constexpr bool any(Args... a_args) const noexcept // + requires(std::same_as && ...) + { + return (_impl & (static_cast(a_args) | ...)) != static_cast(0); + } + + template + [[nodiscard]] constexpr bool all(Args... a_args) const noexcept // + requires(std::same_as && ...) + { + return (_impl & (static_cast(a_args) | ...)) == (static_cast(a_args) | ...); + } + + template + [[nodiscard]] constexpr bool none(Args... a_args) const noexcept // + requires(std::same_as && ...) + { + return (_impl & (static_cast(a_args) | ...)) == static_cast(0); + } + + private: + underlying_type _impl{ 0 }; + }; + + template + enumeration(Args...) -> enumeration< + std::common_type_t, + std::underlying_type_t< + std::common_type_t>>; + } +} + +#define SFSE_MAKE_LOGICAL_OP(a_op, a_result) \ + template \ + [[nodiscard]] constexpr a_result operator a_op(enumeration a_lhs, enumeration a_rhs) noexcept \ + { \ + return a_lhs.get() a_op a_rhs.get(); \ + } \ + \ + template \ + [[nodiscard]] constexpr a_result operator a_op(enumeration a_lhs, E a_rhs) noexcept \ + { \ + return a_lhs.get() a_op a_rhs; \ + } + +#define SFSE_MAKE_ARITHMETIC_OP(a_op) \ + template \ + [[nodiscard]] constexpr auto operator a_op(enumeration a_enum, U a_shift) noexcept \ + -> enumeration \ + { \ + return static_cast(static_cast(a_enum.get()) a_op a_shift); \ + } \ + \ + template \ + constexpr auto operator a_op##=(enumeration& a_enum, U a_shift) noexcept \ + -> enumeration& \ + { \ + return a_enum = a_enum a_op a_shift; \ + } + +#define SFSE_MAKE_ENUMERATION_OP(a_op) \ + template \ + [[nodiscard]] constexpr auto operator a_op(enumeration a_lhs, enumeration a_rhs) noexcept \ + -> enumeration> \ + { \ + return static_cast(static_cast(a_lhs.get()) a_op static_cast(a_rhs.get())); \ + } \ + \ + template \ + [[nodiscard]] constexpr auto operator a_op(enumeration a_lhs, E a_rhs) noexcept \ + -> enumeration \ + { \ + return static_cast(static_cast(a_lhs.get()) a_op static_cast(a_rhs)); \ + } \ + \ + template \ + [[nodiscard]] constexpr auto operator a_op(E a_lhs, enumeration a_rhs) noexcept \ + -> enumeration \ + { \ + return static_cast(static_cast(a_lhs) a_op static_cast(a_rhs.get())); \ + } \ + \ + template \ + constexpr auto operator a_op##=(enumeration& a_lhs, enumeration a_rhs) noexcept \ + -> enumeration& \ + { \ + return a_lhs = a_lhs a_op a_rhs; \ + } \ + \ + template \ + constexpr auto operator a_op##=(enumeration& a_lhs, E a_rhs) noexcept \ + -> enumeration& \ + { \ + return a_lhs = a_lhs a_op a_rhs; \ + } \ + \ + template \ + constexpr auto operator a_op##=(E& a_lhs, enumeration a_rhs) noexcept \ + -> E& \ + { \ + return a_lhs = *(a_lhs a_op a_rhs); \ + } + +#define SFSE_MAKE_INCREMENTER_OP(a_op) \ + template \ + constexpr auto operator a_op##a_op(enumeration& a_lhs) noexcept \ + -> enumeration& \ + { \ + return a_lhs a_op## = static_cast(1); \ + } \ + \ + template \ + [[nodiscard]] constexpr auto operator a_op##a_op(enumeration& a_lhs, int) noexcept \ + -> enumeration \ + { \ + const auto tmp = a_lhs; \ + a_op##a_op a_lhs; \ + return tmp; \ + } + +namespace SFSE +{ + namespace stl + { + template < + class E, + class U> + [[nodiscard]] constexpr auto operator~(enumeration a_enum) noexcept + -> enumeration + { + return static_cast(~static_cast(a_enum.get())); + } + + SFSE_MAKE_LOGICAL_OP(==, bool); + SFSE_MAKE_LOGICAL_OP(<=>, std::strong_ordering); + + SFSE_MAKE_ARITHMETIC_OP(<<); + SFSE_MAKE_ENUMERATION_OP(<<); + SFSE_MAKE_ARITHMETIC_OP(>>); + SFSE_MAKE_ENUMERATION_OP(>>); + + SFSE_MAKE_ENUMERATION_OP(|); + SFSE_MAKE_ENUMERATION_OP(&); + SFSE_MAKE_ENUMERATION_OP(^); + + SFSE_MAKE_ENUMERATION_OP(+); + SFSE_MAKE_ENUMERATION_OP(-); + + SFSE_MAKE_INCREMENTER_OP(+); // ++ + SFSE_MAKE_INCREMENTER_OP(-); // -- + + template + class atomic_ref : + public std::atomic_ref + { + private: + using super = std::atomic_ref; + + public: + using value_type = typename super::value_type; + + explicit atomic_ref(volatile T& a_obj) noexcept(std::is_nothrow_constructible_v) : + super(const_cast(a_obj)) + {} + + using super::super; + using super::operator=; + }; + + template + atomic_ref(volatile T&) -> atomic_ref; + + template class atomic_ref; + template class atomic_ref; + template class atomic_ref; + template class atomic_ref; + template class atomic_ref; + template class atomic_ref; + template class atomic_ref; + template class atomic_ref; + + static_assert(atomic_ref::is_always_lock_free); + static_assert(atomic_ref::is_always_lock_free); + static_assert(atomic_ref::is_always_lock_free); + static_assert(atomic_ref::is_always_lock_free); + static_assert(atomic_ref::is_always_lock_free); + static_assert(atomic_ref::is_always_lock_free); + static_assert(atomic_ref::is_always_lock_free); + static_assert(atomic_ref::is_always_lock_free); + + template + struct ssizeof + { + [[nodiscard]] constexpr operator std::ptrdiff_t() const noexcept { return value; } + + [[nodiscard]] constexpr std::ptrdiff_t operator()() const noexcept { return value; } + + static constexpr auto value = static_cast(sizeof(T)); + }; + + template + inline constexpr auto ssizeof_v = ssizeof::value; + + template + [[nodiscard]] auto adjust_pointer(U* a_ptr, std::ptrdiff_t a_adjust) noexcept + { + auto addr = a_ptr ? reinterpret_cast(a_ptr) + a_adjust : 0; + if constexpr (std::is_const_v && std::is_volatile_v) { + return reinterpret_cast*>(addr); + } else if constexpr (std::is_const_v) { + return reinterpret_cast*>(addr); + } else if constexpr (std::is_volatile_v) { + return reinterpret_cast*>(addr); + } else { + return reinterpret_cast(addr); + } + } + + template + bool emplace_vtable(T* a_ptr) + { + auto address = T::VTABLE[0].address(); + if (!address) { + return false; + } + reinterpret_cast(a_ptr)[0] = address; + return true; + } + + template + void memzero(volatile T* a_ptr, std::size_t a_size = sizeof(T)) + { + const auto begin = reinterpret_cast(a_ptr); + constexpr char val{ 0 }; + std::fill_n(begin, a_size, val); + } + + template + [[nodiscard]] inline auto pun_bits(Args... a_args) // + requires(std::same_as, bool> && ...) + { + constexpr auto ARGC = sizeof...(Args); + + std::bitset bits; + std::size_t i = 0; + ((bits[i++] = a_args), ...); + + if constexpr (ARGC <= std::numeric_limits::digits) { + return bits.to_ulong(); + } else if constexpr (ARGC <= std::numeric_limits::digits) { + return bits.to_ullong(); + } else { + static_assert(false && sizeof...(Args)); + } + } + + [[nodiscard]] inline auto utf8_to_utf16(std::string_view a_in) noexcept + -> std::optional + { + const auto cvt = [&](wchar_t* a_dst, std::size_t a_length) { + return WinAPI::MultiByteToWideChar( + CP_UTF8, + 0, + a_in.data(), + static_cast(a_in.length()), + a_dst, + static_cast(a_length)); + }; + + const auto len = cvt(nullptr, 0); + if (len == 0) { + return std::nullopt; + } + + std::wstring out(len, '\0'); + if (cvt(out.data(), out.length()) == 0) { + return std::nullopt; + } + + return out; + } + + [[nodiscard]] inline auto utf16_to_utf8(std::wstring_view a_in) noexcept + -> std::optional + { + const auto cvt = [&](char* a_dst, std::size_t a_length) { + return WinAPI::WideCharToMultiByte( + CP_UTF8, + 0, + a_in.data(), + static_cast(a_in.length()), + a_dst, + static_cast(a_length), + nullptr, + nullptr); + }; + + const auto len = cvt(nullptr, 0); + if (len == 0) { + return std::nullopt; + } + + std::string out(len, '\0'); + if (cvt(out.data(), out.length()) == 0) { + return std::nullopt; + } + + return out; + } + +#ifndef __clang__ + using source_location = std::source_location; +#else + /** + * A polyfill for source location support for Clang. + * + *

+ * Clang-CL can use source_location, but not in the context of a default argument due to + * a bug in its support for consteval. This bug does not affect constexpr so + * this class uses a constexpr version of the typical constructor. + *

+ */ + struct source_location + { + public: + static constexpr source_location current( + const uint_least32_t a_line = __builtin_LINE(), + const uint_least32_t a_column = __builtin_COLUMN(), + const char* const a_file = __builtin_FILE(), + const char* const a_function = __builtin_FUNCTION()) noexcept + { + source_location result; + result._line = a_line; + result._column = a_column; + result._file = a_file; + result._function = a_function; + return result; + } + + [[nodiscard]] constexpr const char* file_name() const noexcept + { + return _file; + } + + [[nodiscard]] constexpr const char* function_name() const noexcept + { + return _function; + } + + [[nodiscard]] constexpr uint_least32_t line() const noexcept + { + return _line; + } + + [[nodiscard]] constexpr uint_least32_t column() const noexcept + { + return _column; + } + + private: + source_location() = default; + + uint_least32_t _line{}; + uint_least32_t _column{}; + const char* _file = ""; + const char* _function = ""; + }; +#endif + + inline bool report_and_error(std::string_view a_msg, bool a_fail = true, + SFSE::stl::source_location a_loc = SFSE::stl::source_location::current()) + { + const auto body = [&]() -> std::wstring { + const std::filesystem::path p = a_loc.file_name(); + auto filename = p.lexically_normal().generic_string(); + + const std::regex r{ R"((?:^|[\\\/])(?:include|src)[\\\/](.*)$)" }; + std::smatch matches; + if (std::regex_search(filename, matches, r)) { + filename = matches[1].str(); + } + + return utf8_to_utf16( + fmt::format( + "{}({}): {}"sv, + filename, + a_loc.line(), + a_msg)) + .value_or(L""s); + }(); + + const auto caption = []() { + const auto maxPath = WinAPI::GetMaxPath(); + std::vector buf; + buf.reserve(maxPath); + buf.resize(maxPath / 2); + std::uint32_t result = 0; + do { + buf.resize(buf.size() * 2); + result = GetModuleFileName( + WinAPI::GetCurrentModule(), + buf.data(), + static_cast(buf.size())); + } while (result && result == buf.size() && buf.size() <= (std::numeric_limits::max)()); + + if (result && result != buf.size()) { + std::filesystem::path p(buf.begin(), buf.begin() + result); + return p.filename().native(); + } else { + return L""s; + } + }(); + + spdlog::log( + spdlog::source_loc{ + a_loc.file_name(), + static_cast(a_loc.line()), + a_loc.function_name() }, + spdlog::level::critical, + a_msg); + + if (a_fail) { + MessageBox(nullptr, body.c_str(), (caption.empty() ? nullptr : caption.c_str()), 0); + WinAPI::TerminateProcess(WinAPI::GetCurrentProcess(), EXIT_FAILURE); + } + return true; + } + + [[noreturn]] inline void report_and_fail(std::string_view a_msg, + SFSE::stl::source_location a_loc = SFSE::stl::source_location::current()) + { + report_and_error(a_msg, true, a_loc); + } + + template + [[nodiscard]] constexpr auto to_underlying(Enum a_val) noexcept // + requires(std::is_enum_v) + { + return static_cast>(a_val); + } + + template + [[nodiscard]] To unrestricted_cast(From a_from) noexcept + { + if constexpr (std::is_same_v< + std::remove_cv_t, + std::remove_cv_t>) { + return To{ a_from }; + + // From != To + } else if constexpr (std::is_reference_v) { + return stl::unrestricted_cast(std::addressof(a_from)); + + // From: NOT reference + } else if constexpr (std::is_reference_v) { + return *stl::unrestricted_cast< + std::add_pointer_t< + std::remove_reference_t>>(a_from); + + // To: NOT reference + } else if constexpr (std::is_pointer_v && + std::is_pointer_v) { + return static_cast( + const_cast( + static_cast(a_from))); + } else if constexpr ((std::is_pointer_v && std::is_integral_v) || + (std::is_integral_v && std::is_pointer_v)) { + return reinterpret_cast(a_from); + } else { + union + { + std::remove_cv_t> from; + std::remove_cv_t> to; + }; + + from = std::forward(a_from); + return to; + } + } + } +} + +#undef SFSE_MAKE_INCREMENTER_OP +#undef SFSE_MAKE_ENUMERATION_OP +#undef SFSE_MAKE_ARITHMETIC_OP +#undef SFSE_MAKE_LOGICAL_OP + +namespace RE +{ + using namespace std::literals; + namespace stl = SFSE::stl; + namespace WinAPI = SFSE::WinAPI; +} + +namespace REL +{ + using namespace std::literals; + namespace stl = SFSE::stl; + namespace WinAPI = SFSE::WinAPI; +} + +#include "REL/Relocation.h" + +#include "RE/Offsets.h" +#include "RE/Offsets_NiRTTI.h" +#include "RE/Offsets_RTTI.h" +#include "RE/Offsets_VTABLE.h" + +#undef cdecl // Workaround for Clang. diff --git a/CommonLibSF/include/SFSE/Impl/Stubs.h b/CommonLibSF/include/SFSE/Impl/Stubs.h new file mode 100644 index 00000000..ec55aa60 --- /dev/null +++ b/CommonLibSF/include/SFSE/Impl/Stubs.h @@ -0,0 +1,43 @@ +#pragma once + +namespace SFSE +{ + using PluginHandle = std::uint32_t; + enum : PluginHandle + { + kInvalidPluginHandle = static_cast(-1) + }; + + namespace detail + { + struct PluginInfo + { + std::uint32_t infoVersion; + const char* name; + std::uint32_t version; + }; + + struct SFSEInterface + { + std::uint32_t sfseVersion; + std::uint32_t runtimeVersion; + void* (*QueryInterface)(std::uint32_t); + PluginHandle (*GetPluginHandle)(); + const void* (*GetPluginInfo)(const char*); + }; + + struct SFSEMessagingInterface + { + std::uint32_t interfaceVersion; + bool (*RegisterListener)(PluginHandle, const char*, void*); + bool (*Dispatch)(PluginHandle, std::uint32_t, void*, std::uint32_t, const char*); + }; + + struct SFSETrampolineInterface + { + std::uint32_t interfaceVersion; + void* (*AllocateFromBranchPool)(PluginHandle, std::size_t); + void* (*AllocateFromLocalPool)(PluginHandle, std::size_t); + }; + } +} diff --git a/CommonLibSF/include/SFSE/Impl/WinAPI.h b/CommonLibSF/include/SFSE/Impl/WinAPI.h new file mode 100644 index 00000000..3d8dbacf --- /dev/null +++ b/CommonLibSF/include/SFSE/Impl/WinAPI.h @@ -0,0 +1,590 @@ +#pragma once + +#undef CP_UTF8 +#undef IMAGE_SCN_MEM_EXECUTE +#undef IMAGE_SCN_MEM_WRITE +#undef INVALID_HANDLE_VALUE +#undef MAX_PATH +#undef MEM_COMMIT +#undef MEM_RESERVE +#undef MEM_RELEASE +#undef PAGE_EXECUTE_READWRITE +#undef HKEY_LOCAL_MACHINE + +#undef GetEnvironmentVariable +#undef GetFileVersionInfoSize +#undef GetModuleFileName +#undef VerQueryValue +#undef GetFileVersionInfo +#undef GetModuleHandle +#undef LoadLibrary +#undef MessageBox +#undef OutputDebugString + +namespace SFSE::WinAPI +{ + inline constexpr auto CP_UTF8{ 65001u }; + inline constexpr auto FILE_ATTRIBUTE_READONLY{ 0x00000001u }; + inline constexpr auto FILE_ATTRIBUTE_HIDDEN{ 0x00000002u }; + inline constexpr auto FILE_ATTRIBUTE_SYSTEM{ 0x00000004u }; + inline constexpr auto FILE_ATTRIBUTE_DIRECTORY{ 0x00000010u }; + inline constexpr auto FILE_ATTRIBUTE_ARCHIVE{ 0x00000020u }; + inline constexpr auto IMAGE_SCN_MEM_EXECUTE{ 0x20000000u }; + inline constexpr auto IMAGE_SCN_MEM_WRITE{ 0x80000000u }; + inline const auto INVALID_HANDLE_VALUE{ reinterpret_cast(static_cast(-1)) }; + inline constexpr auto MAX_PATH{ 260u }; + inline constexpr auto MEM_COMMIT{ 0x00001000u }; + inline constexpr auto MEM_RESERVE{ 0x00002000u }; + inline constexpr auto MEM_RELEASE{ 0x00008000u }; + inline constexpr auto PAGE_EXECUTE_READWRITE{ 0x40u }; + + struct CRITICAL_SECTION + { + public: + // members + void* DebugInfo; // 00 + std::int32_t LockCount; // 08 + std::int32_t RecursionCount; // 0C + void* OwningThread; // 10 + void* LockSemaphore; // 18 + std::uint64_t* SpinCount; // 20 + }; + static_assert(sizeof(CRITICAL_SECTION) == 0x28); + + struct _FILETIME + { + public: + // members + std::uint32_t dwLowDateTime; // 00 + std::uint32_t dwHighDateTime; // 04 + }; + static_assert(sizeof(_FILETIME) == 0x8); + using FILETIME = _FILETIME; + + struct _GUID + { + std::uint32_t Data1; + std::uint16_t Data2; + std::uint16_t Data3; + std::uint8_t Data4[8]; + }; + static_assert(sizeof(_GUID) == 0x10); + using GUID = _GUID; + + struct HWND__; + using HWND = HWND__*; + + struct HINSTANCE__; + using HINSTANCE = HINSTANCE__*; + using HMODULE = HINSTANCE; + + struct HKEY__; + using HKEY = HKEY__*; + + inline auto HKEY_LOCAL_MACHINE = reinterpret_cast(static_cast(0x80000002)); + + struct _WIN32_FIND_DATAA + { + public: + // members + std::uint32_t dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + std::uint32_t nFileSizeHigh; + std::uint32_t nFileSizeLow; + std::uint32_t dwReserved0; + std::uint32_t dwReserved1; + char cFileName[MAX_PATH]; + char cAlternateFileName[14]; + }; + static_assert(sizeof(_WIN32_FIND_DATAA) == 0x140); + using WIN32_FIND_DATAA = _WIN32_FIND_DATAA; + + struct _WIN32_FIND_DATAW + { + public: + // members + std::uint32_t dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + std::uint32_t nFileSizeHigh; + std::uint32_t nFileSizeLow; + std::uint32_t dwReserved0; + std::uint32_t dwReserved1; + wchar_t cFileName[MAX_PATH]; + wchar_t cAlternateFileName[14]; + }; + static_assert(sizeof(_WIN32_FIND_DATAW) == 0x250); + using WIN32_FIND_DATAW = _WIN32_FIND_DATAW; + + struct tagRECT + { + std::int32_t left; + std::int32_t top; + std::int32_t right; + std::int32_t bottom; + }; + using RECT = tagRECT; + + struct tagPOINT + { + std::int32_t x; + std::int32_t y; + }; + using POINT = tagPOINT; + + using HRESULT = std::int32_t; + + enum VKEnum : std::uint32_t + { + VK_LBUTTON = 0x1, + VK_RBUTTON = 0x2, + VK_CANCEL = 0x3, + VK_MBUTTON = 0x4, + VK_XBUTTON1 = 0x5, + VK_XBUTTON2 = 0x6, + VK_BACK = 0x8, + VK_TAB = 0x9, + VK_RESERVED_0A = 0xA, + VK_RESERVED_0B = 0xB, + VK_CLEAR = 0xC, + VK_RETURN = 0xD, + VK_SHIFT = 0x10, + VK_CONTROL = 0x11, + VK_MENU = 0x12, + VK_PAUSE = 0x13, + VK_CAPITAL = 0x14, + VK_KANA = 0x15, + VK_HANGUEL = 0x15, + VK_HANGUL = 0x15, + VK_IME_ON = 0x16, + VK_JUNJA = 0x17, + VK_FINAL = 0x18, + VK_HANJA = 0x19, + VK_KANJI = VK_HANJA, + VK_IME_OFF = 0x1A, + VK_ESCAPE = 0x1B, + VK_CONVERT = 0x1C, + VK_NONCONVERT = 0x1D, + VK_ACCEPT = 0x1E, + VK_MODECHANGE = 0x1F, + VK_SPACE = 0x20, + VK_PRIOR = 0x21, + VK_NEXT = 0x22, + VK_END = 0x23, + VK_HOME = 0x24, + VK_LEFT = 0x25, + VK_UP = 0x26, + VK_RIGHT = 0x27, + VK_DOWN = 0x28, + VK_SELECT = 0x29, + VK_PRINT = 0x2A, + VK_EXECUTE = 0x2B, + VK_SNAPSHOT = 0x2C, + VK_INSERT = 0x2D, + VK_DELETE = 0x2E, + VK_HELP = 0x2F, + VK_0 = 0x30, + VK_1 = 0x31, + VK_2 = 0x32, + VK_3 = 0x33, + VK_4 = 0x34, + VK_5 = 0x35, + VK_6 = 0x36, + VK_7 = 0x37, + VK_8 = 0x38, + VK_9 = 0x39, + VK_A = 0x41, + VK_B = 0x42, + VK_C = 0x43, + VK_D = 0x44, + VK_E = 0x45, + VK_F = 0x46, + VK_G = 0x47, + VK_H = 0x48, + VK_I = 0x49, + VK_J = 0x4A, + VK_K = 0x4B, + VK_L = 0x4C, + VK_M = 0x4D, + VK_N = 0x4E, + VK_O = 0x4F, + VK_P = 0x50, + VK_Q = 0x51, + VK_R = 0x52, + VK_S = 0x53, + VK_T = 0x54, + VK_U = 0x55, + VK_V = 0x56, + VK_W = 0x57, + VK_X = 0x58, + VK_Y = 0x59, + VK_Z = 0x5A, + VK_LWIN = 0x5B, + VK_RWIN = 0x5C, + VK_APPS = 0x5D, + VK_RESERVED_5E = 0x5E, + VK_SLEEP = 0x5F, + VK_NUMPAD0 = 0x60, + VK_NUMPAD1 = 0x61, + VK_NUMPAD2 = 0x62, + VK_NUMPAD3 = 0x63, + VK_NUMPAD4 = 0x64, + VK_NUMPAD5 = 0x65, + VK_NUMPAD6 = 0x66, + VK_NUMPAD7 = 0x67, + VK_NUMPAD8 = 0x68, + VK_NUMPAD9 = 0x69, + VK_MULTIPLY = 0x6A, + VK_ADD = 0x6B, + VK_SEPARATOR = 0x6C, + VK_SUBTRACT = 0x6D, + VK_DECIMAL = 0x6E, + VK_DIVIDE = 0x6F, + VK_F1 = 0x70, + VK_F2 = 0x71, + VK_F3 = 0x72, + VK_F4 = 0x73, + VK_F5 = 0x74, + VK_F6 = 0x75, + VK_F7 = 0x76, + VK_F8 = 0x77, + VK_F9 = 0x78, + VK_F10 = 0x79, + VK_F11 = 0x7A, + VK_F12 = 0x7B, + VK_F13 = 0x7C, + VK_F14 = 0x7D, + VK_F15 = 0x7E, + VK_F16 = 0x7F, + VK_F17 = 0x80, + VK_F18 = 0x81, + VK_F19 = 0x82, + VK_F20 = 0x83, + VK_F21 = 0x84, + VK_F22 = 0x85, + VK_F23 = 0x86, + VK_F24 = 0x87, + VK_NUMLOCK = 0x90, + VK_SCROLL = 0x91, + VK_OEMSPECIFIC_92 = 0x92, + VK_OEMSPECIFIC_93 = 0x93, + VK_OEMSPECIFIC_94 = 0x94, + VK_OEMSPECIFIC_95 = 0x95, + VK_OEMSPECIFIC_96 = 0x96, + VK_LSHIFT = 0xA0, + VK_RSHIFT = 0xA1, + VK_LCONTROL = 0xA2, + VK_RCONTROL = 0xA3, + VK_LMENU = 0xA4, + VK_RMENU = 0xA5, + VK_BROWSER_BACK = 0xA6, + VK_BROWSER_FORWARD = 0xA7, + VK_BROWSER_REFRESH = 0xA8, + VK_BROWSER_STOP = 0xA9, + VK_BROWSER_SEARCH = 0xAA, + VK_BROWSER_FAVORITES = 0xAB, + VK_BROWSER_HOME = 0xAC, + VK_VOLUME_MUTE = 0xAD, + VK_VOLUME_DOWN = 0xAE, + VK_VOLUME_UP = 0xAF, + VK_MEDIA_NEXT_TRACK = 0xB0, + VK_MEDIA_PREV_TRACK = 0xB1, + VK_MEDIA_STOP = 0xB2, + VK_MEDIA_PLAY_PAUSE = 0xB3, + VK_LAUNCH_MAIL = 0xB4, + VK_LAUNCH_MEDIA_SELECT = 0xB5, + VK_LAUNCH_APP1 = 0xB6, + VK_LAUNCH_APP2 = 0xB7, + VK_RESERVED_B8 = 0xB8, + VK_RESERVED_B9 = 0xB9, + VK_OEM_1 = 0xBA, + VK_OEM_PLUS = 0xBB, + VK_OEM_COMMA = 0xBC, + VK_OEM_MINUS = 0xBD, + VK_OEM_PERIOD = 0xBE, + VK_OEM_2 = 0xBF, + VK_OEM_3 = 0xC0, + VK_RESERVED_C1 = 0xC1, + VK_RESERVED_C2 = 0xC2, + VK_RESERVED_C3 = 0xC3, + VK_RESERVED_C4 = 0xC4, + VK_RESERVED_C5 = 0xC5, + VK_RESERVED_C6 = 0xC6, + VK_RESERVED_C7 = 0xC7, + VK_RESERVED_C8 = 0xC8, + VK_RESERVED_C9 = 0xC9, + VK_RESERVED_CA = 0xCA, + VK_RESERVED_CB = 0xCB, + VK_RESERVED_CC = 0xCC, + VK_RESERVED_CD = 0xCD, + VK_RESERVED_CE = 0xCE, + VK_RESERVED_CF = 0xCF, + VK_RESERVED_D0 = 0xD0, + VK_RESERVED_D1 = 0xD1, + VK_RESERVED_D2 = 0xD2, + VK_RESERVED_D3 = 0xD3, + VK_RESERVED_D4 = 0xD4, + VK_RESERVED_D5 = 0xD5, + VK_RESERVED_D6 = 0xD6, + VK_RESERVED_D7 = 0xD7, + VK_OEM_4 = 0xDB, + VK_OEM_5 = 0xDC, + VK_OEM_6 = 0xDD, + VK_OEM_7 = 0xDE, + VK_OEM_8 = 0xDF, + VK_RESERVED_E0 = 0xE0, + VK_OEMSPECIFIC_E1 = 0xE1, + VK_OEM_102 = 0xE2, + VK_OEMSPECIFIC_E3 = 0xE3, + VK_OEMSPECIFIC_E4 = 0xE4, + VK_PROCESSKEY = 0xE5, + VK_OEMSPECIFIC_E6 = 0xE6, + VK_PACKET = 0xE7, + VK_OEMSPECIFIC_E9 = 0xE9, + VK_OEMSPECIFIC_EA = 0xEA, + VK_OEMSPECIFIC_EB = 0xEB, + VK_OEMSPECIFIC_EC = 0xEC, + VK_OEMSPECIFIC_ED = 0xED, + VK_OEMSPECIFIC_EE = 0xEE, + VK_OEMSPECIFIC_EF = 0xEF, + VK_OEMSPECIFIC_F0 = 0xF0, + VK_OEMSPECIFIC_F1 = 0xF1, + VK_OEMSPECIFIC_F2 = 0xF2, + VK_OEMSPECIFIC_F3 = 0xF3, + VK_OEMSPECIFIC_F4 = 0xF4, + VK_OEMSPECIFIC_F5 = 0xF5, + VK_ATTN = 0xF6, + VK_CRSEL = 0xF7, + VK_EXSEL = 0xF8, + VK_EREOF = 0xF9, + VK_PLAY = 0xFA, + VK_ZOOM = 0xFB, + VK_NONAME = 0xFC, + VK_PA1 = 0xFD, + VK_OEM_CLEAR = 0xFE, + VK_RESERVED_FF = 0xFF + }; + + [[nodiscard]] bool FindClose(void* a_findFile) noexcept; + + [[nodiscard]] void* FindFirstFile( + const char* a_fileName, + WIN32_FIND_DATAA* a_findFileData) noexcept; + + [[nodiscard]] void* FindFirstFile( + const wchar_t* a_fileName, + WIN32_FIND_DATAW* a_findFileData) noexcept; + + [[nodiscard]] bool FindNextFile( + void* a_findFile, + WIN32_FIND_DATAA* a_findFileData) noexcept; + + [[nodiscard]] bool FindNextFile( + void* a_findFile, + WIN32_FIND_DATAW* a_findFileData) noexcept; + + bool FreeLibrary(HMODULE a_module) noexcept; + + [[nodiscard]] void* GetCurrentModule() noexcept; + + [[nodiscard]] void* GetCurrentProcess() noexcept; + + [[nodiscard]] std::uint32_t GetCurrentThreadID() noexcept; + + [[nodiscard]] std::uint32_t GetEnvironmentVariable( + const char* a_name, + char* a_buffer, + std::uint32_t a_size) noexcept; + + [[nodiscard]] std::uint32_t GetEnvironmentVariable( + const wchar_t* a_name, + wchar_t* a_buffer, + std::uint32_t a_size) noexcept; + + [[nodiscard]] bool GetFileVersionInfo( + const char* a_filename, + std::uint32_t a_handle, + std::uint32_t a_len, + void* a_data) noexcept; + + [[nodiscard]] bool GetFileVersionInfo( + const wchar_t* a_filename, + std::uint32_t a_handle, + std::uint32_t a_len, + void* a_data) noexcept; + + [[nodiscard]] std::uint32_t GetFileVersionInfoSize( + const char* a_filename, + std::uint32_t* a_handle) noexcept; + + [[nodiscard]] std::uint32_t GetFileVersionInfoSize( + const wchar_t* a_filename, + std::uint32_t* a_handle) noexcept; + + [[nodiscard]] int GetKeyNameText( + std::int32_t a_lParam, + char* a_buffer, + int a_size) noexcept; + + [[nodiscard]] int GetKeyNameText( + std::int32_t a_lParam, + wchar_t* a_buffer, + int a_size) noexcept; + + [[nodiscard]] std::int16_t GetKeyState(int nVirtKey) noexcept; + + [[nodiscard]] std::size_t GetMaxPath() noexcept; + + [[nodiscard]] std::uint32_t GetModuleFileName( + void* a_module, + char* a_filename, + std::uint32_t a_size) noexcept; + + [[nodiscard]] std::uint32_t GetModuleFileName( + void* a_module, + wchar_t* a_filename, + std::uint32_t a_size) noexcept; + + [[nodiscard]] HMODULE GetModuleHandle(const char* a_moduleName) noexcept; + + [[nodiscard]] HMODULE GetModuleHandle(const wchar_t* a_moduleName) noexcept; + + [[nodiscard]] std::uint32_t GetPrivateProfileString( + const char* a_appName, + const char* a_keyName, + const char* a_default, + char* a_outString, + std::uint32_t a_size, + const char* a_fileName) noexcept; + + [[nodiscard]] std::uint32_t GetPrivateProfileString( + const wchar_t* a_appName, + const wchar_t* a_keyName, + const wchar_t* a_default, + wchar_t* a_outString, + std::uint32_t a_size, + const wchar_t* a_fileName) noexcept; + + [[nodiscard]] void* GetProcAddress( + void* a_module, + const char* a_procName) noexcept; + + [[nodiscard]] std::string_view GetProcPath(HMODULE a_handle = 0) noexcept; + + [[nodiscard]] bool IsDebuggerPresent() noexcept; + + [[nodiscard]] HMODULE LoadLibrary(const char* a_libFileName) noexcept; + + [[nodiscard]] HMODULE LoadLibrary(const wchar_t* a_libFileName) noexcept; + + std::int32_t MessageBox( + void* a_wnd, + const char* a_text, + const char* a_caption, + unsigned int a_type) noexcept; + + std::int32_t MessageBox( + void* a_wnd, + const wchar_t* a_text, + const wchar_t* a_caption, + unsigned int a_type) noexcept; + + [[nodiscard]] int MultiByteToWideChar( + unsigned int a_codePage, + std::uint32_t a_flags, + const char* a_multiByteStr, + int a_multiByte, + wchar_t* a_wideCharStr, + int a_wideChar); + + void OutputDebugString( + const char* a_outputString) noexcept; + + void OutputDebugString( + const wchar_t* a_outputString) noexcept; + + long RegGetValueW(HKEY hkey, const char* subKey, const char* value, unsigned long flags, unsigned long* type, + void* data, unsigned long* length); + + long RegGetValueW(HKEY hkey, const wchar_t* subKey, const wchar_t* value, unsigned long flags, unsigned long* type, + void* data, unsigned long* length); + + [[nodiscard]] int ShowCursor(bool bShow) noexcept; + + [[noreturn]] void TerminateProcess( + void* a_process, + unsigned int a_exitCode) noexcept; + + [[nodiscard]] void* TlsGetValue(std::uint32_t a_tlsIndex) noexcept; + + bool TlsSetValue( + std::uint32_t a_tlsIndex, + void* a_tlsValue) noexcept; + + bool VirtualFree( + void* a_address, + std::size_t a_size, + std::uint32_t a_freeType) noexcept; + + [[nodiscard]] bool VerQueryValue( + const void* a_block, + const char* a_subBlock, + void** a_buffer, + unsigned int* a_len) noexcept; + + [[nodiscard]] bool VerQueryValue( + const void* a_block, + const wchar_t* a_subBlock, + void** a_buffer, + unsigned int* a_len) noexcept; + + [[nodiscard]] bool VirtualProtect( + void* a_address, + std::size_t a_size, + std::uint32_t a_newProtect, + std::uint32_t* a_oldProtect) noexcept; + + [[nodiscard]] int WideCharToMultiByte( + unsigned int a_codePage, + std::uint32_t a_flags, + const wchar_t* a_wideCharStr, + int a_wideChar, + char* a_multiByteStr, + int a_multiByte, + const char* a_defaultChar, + int* a_usedDefaultChar); +} + +namespace RE::DirectX +{ + struct XMFLOAT4X4 + { + public: + // members + float m[4][4]; + }; + static_assert(sizeof(XMFLOAT4X4) == 0x40); +} + +#define CP_UTF8 ::SFSE::WinAPI::CP_UTF8 +#define IMAGE_SCN_MEM_EXECUTE ::SFSE::WinAPI::IMAGE_SCN_MEM_EXECUTE +#define IMAGE_SCN_MEM_WRITE ::SFSE::WinAPI::IMAGE_SCN_MEM_WRITE +#define INVALID_HANDLE_VALUE ::SFSE::WinAPI::INVALID_HANDLE_VALUE +#define MAX_PATH ::SFSE::WinAPI::MAX_PATH +#define MEM_COMMIT ::SFSE::WinAPI::MEM_COMMIT +#define MEM_RESERVE ::SFSE::WinAPI::MEM_RESERVE +#define MEM_RELEASE ::SFSE::WinAPI::MEM_RELEASE +#define PAGE_EXECUTE_READWRITE ::SFSE::WinAPI::PAGE_EXECUTE_READWRITE + +#define GetEnvironmentVariable ::SFSE::WinAPI::GetEnvironmentVariable +#define GetFileVersionInfoSize ::SFSE::WinAPI::GetFileVersionInfoSize +#define GetModuleFileName ::SFSE::WinAPI::GetModuleFileName +#define VerQueryValue ::SFSE::WinAPI::VerQueryValue +#define GetFileVersionInfo ::SFSE::WinAPI::GetFileVersionInfo +#define GetModuleHandle ::SFSE::WinAPI::GetModuleHandle +#define LoadLibrary ::SFSE::WinAPI::LoadLibrary +#define MessageBox ::SFSE::WinAPI::MessageBox +#define OutputDebugString ::SFSE::WinAPI::OutputDebugString diff --git a/CommonLibSF/include/SFSE/Impl/XInputAPI.h b/CommonLibSF/include/SFSE/Impl/XInputAPI.h new file mode 100644 index 00000000..42280876 --- /dev/null +++ b/CommonLibSF/include/SFSE/Impl/XInputAPI.h @@ -0,0 +1,84 @@ +#pragma once + +// TODO: This should probably be behind some sort of pragma that allows linking with xinput +namespace RE::XInput +{ + struct __XINPUT_GAMEPAD + { + std::uint16_t wButtons; + std::byte bLeftTrigger; + std::byte bRightTrigger; + std::int16_t sThumbLX; + std::int16_t sThumbLY; + std::int16_t sThumbRX; + std::int16_t sThumbRY; + }; + using XINPUT_GAMEPAD = __XINPUT_GAMEPAD; + struct __XINPUT_STATE + { + std::uint32_t dwPacketNumber; + __XINPUT_GAMEPAD Gamepad; + }; + using XINPUT_STATE = __XINPUT_STATE; + struct __XINPUT_KEYSTROKE + { + std::uint16_t VirtualKey; + std::uint16_t Unicode; + std::uint16_t Flags; + std::uint8_t UserIndex; + std::uint8_t HidCode; + }; + using XINPUT_KEYSTROKE = __XINPUT_KEYSTROKE; + struct __XINPUT_VIBRATION + { + std::uint16_t wLeftMotorSpeed; + std::uint16_t wRightMotorSpeed; + }; + using XINPUT_VIBRATION = __XINPUT_VIBRATION; + + struct __XINPUT_CAPABILITIES + { + std::uint8_t Type; + std::uint8_t SubType; + std::uint16_t Flags; + XINPUT_GAMEPAD Gamepad; + XINPUT_VIBRATION Vibration; + }; + using XINPUT_CAPABILITIES = __XINPUT_CAPABILITIES; + + enum XInputButton : std::uint16_t + { + XINPUT_GAMEPAD_DPAD_UP = 0x0001, + XINPUT_GAMEPAD_DPAD_DOWN = 0x0002, + XINPUT_GAMEPAD_DPAD_LEFT = 0x0004, + XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008, + XINPUT_GAMEPAD_START = 0x0010, + XINPUT_GAMEPAD_BACK = 0x0020, + XINPUT_GAMEPAD_LEFT_THUMB = 0x0040, + XINPUT_GAMEPAD_RIGHT_THUMB = 0x0080, + XINPUT_GAMEPAD_LEFT_SHOULDER = 0x0100, + XINPUT_GAMEPAD_RIGHT_SHOULDER = 0x0200, + XINPUT_GAMEPAD_A = 0x1000, + XINPUT_GAMEPAD_B = 0x2000, + XINPUT_GAMEPAD_X = 0x4000, + XINPUT_GAMEPAD_Y = 0x8000 + }; + static constexpr std::uint16_t XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849; + static constexpr std::uint16_t XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689; + static constexpr std::uint8_t XINPUT_GAMEPAD_TRIGGER_THRESHOLD = 30; + + static constexpr std::uint16_t XINPUT_BUTTON_MASK = XINPUT_GAMEPAD_DPAD_UP | + XINPUT_GAMEPAD_DPAD_DOWN | + XINPUT_GAMEPAD_DPAD_LEFT | + XINPUT_GAMEPAD_DPAD_RIGHT | + XINPUT_GAMEPAD_START | + XINPUT_GAMEPAD_BACK | + XINPUT_GAMEPAD_LEFT_THUMB | + XINPUT_GAMEPAD_RIGHT_THUMB | + XINPUT_GAMEPAD_LEFT_SHOULDER | + XINPUT_GAMEPAD_RIGHT_SHOULDER | + XINPUT_GAMEPAD_A | + XINPUT_GAMEPAD_B | + XINPUT_GAMEPAD_X | + XINPUT_GAMEPAD_Y; +} diff --git a/CommonLibSF/include/SFSE/InputMap.h b/CommonLibSF/include/SFSE/InputMap.h new file mode 100644 index 00000000..62973c66 --- /dev/null +++ b/CommonLibSF/include/SFSE/InputMap.h @@ -0,0 +1,53 @@ +#pragma once + +namespace SFSE +{ + namespace InputMap + { + enum + { + // first 256 for keyboard, then 8 mouse buttons, then mouse wheel up, wheel down, then 16 gamepad buttons + kMacro_KeyboardOffset = 0, // not actually used, just for self-documentation + kMacro_NumKeyboardKeys = 256, + + kMacro_MouseButtonOffset = kMacro_NumKeyboardKeys, // 256 + kMacro_NumMouseButtons = 8, + + kMacro_MouseWheelOffset = kMacro_MouseButtonOffset + kMacro_NumMouseButtons, // 264 + kMacro_MouseWheelDirections = 2, + + kMacro_GamepadOffset = kMacro_MouseWheelOffset + kMacro_MouseWheelDirections, // 266 + kMacro_NumGamepadButtons = 16, + + kMaxMacros = kMacro_GamepadOffset + kMacro_NumGamepadButtons // 282 + }; + + enum + { + kGamepadButtonOffset_DPAD_UP = kMacro_GamepadOffset, // 266 + kGamepadButtonOffset_DPAD_DOWN, + kGamepadButtonOffset_DPAD_LEFT, + kGamepadButtonOffset_DPAD_RIGHT, + kGamepadButtonOffset_START, + kGamepadButtonOffset_BACK, + kGamepadButtonOffset_LEFT_THUMB, + kGamepadButtonOffset_RIGHT_THUMB, + kGamepadButtonOffset_LEFT_SHOULDER, + kGamepadButtonOffset_RIGHT_SHOULDER, + kGamepadButtonOffset_A, + kGamepadButtonOffset_B, + kGamepadButtonOffset_X, + kGamepadButtonOffset_Y, + kGamepadButtonOffset_LT, + kGamepadButtonOffset_RT // 281 + }; + + std::uint32_t GamepadMaskToKeycode(std::uint32_t keyMask); + std::uint32_t GamepadKeycodeToMask(std::uint32_t keyCode); + + std::string GetKeyName(std::uint32_t keyCode); + std::string GetKeyboardKeyName(std::uint32_t keyCode); + std::string GetMouseButtonName(std::uint32_t keyCode); + std::string GetGamepadButtonName(std::uint32_t a_keyCode); + } +} diff --git a/CommonLibSF/include/SFSE/Interfaces.h b/CommonLibSF/include/SFSE/Interfaces.h new file mode 100644 index 00000000..bcaadaaf --- /dev/null +++ b/CommonLibSF/include/SFSE/Interfaces.h @@ -0,0 +1,161 @@ +#pragma once + +#include "SFSE/Impl/Stubs.h" +#include "SFSE/Version.h" + +namespace SFSE +{ + struct PluginInfo; + + class QueryInterface + { + public: + [[nodiscard]] REL::Version RuntimeVersion() const; + [[nodiscard]] std::uint32_t SFSEVersion() const; + + protected: + [[nodiscard]] const detail::SFSEInterface* GetProxy() const; + }; + + class LoadInterface : public QueryInterface + { + public: + enum : std::uint32_t + { + kInvalid = 0, + kScaleform, + kPapyrus, + kSerialization, + kTask, + kMessaging, + kObject, + kTrampoline, + kTotal + }; + + [[nodiscard]] PluginHandle GetPluginHandle() const; + const PluginInfo* GetPluginInfo(const char* a_name) const; + [[nodiscard]] void* QueryInterface(std::uint32_t a_id) const; + }; + + class MessagingInterface + { + public: + struct Message + { + const char* sender; + std::uint32_t type; + std::uint32_t dataLen; + void* data; + }; + + using EventCallback = std::add_pointer_t; + + enum + { + kVersion = 2 + }; + + enum : std::uint32_t + { + kPostLoad, + kPostPostLoad, + }; + + [[nodiscard]] std::uint32_t Version() const; + + bool Dispatch(std::uint32_t a_messageType, void* a_data, std::uint32_t a_dataLen, const char* a_receiver) const; + bool RegisterListener(EventCallback* a_callback) const; + bool RegisterListener(const char* a_sender, EventCallback* a_callback) const; + + protected: + [[nodiscard]] const detail::SFSEMessagingInterface* GetProxy() const; + }; + + class TrampolineInterface + { + public: + enum + { + kVersion = 1 + }; + + [[nodiscard]] std::uint32_t Version() const; + + [[nodiscard]] void* AllocateFromBranchPool(std::size_t a_size) const; + [[nodiscard]] void* AllocateFromLocalPool(std::size_t a_size) const; + + private: + [[nodiscard]] const detail::SFSETrampolineInterface* GetProxy() const; + }; + + struct PluginInfo + { + enum + { + kVersion = 1 + }; + + std::uint32_t infoVersion; + const char* name; + std::uint32_t version; + }; + + struct PluginVersionData + { + public: + enum + { + kVersion = 1, + }; + + constexpr void PluginVersion(std::uint32_t a_version) noexcept { pluginVersion = a_version; } + constexpr void PluginName(std::string_view a_plugin) noexcept { SetCharBuffer(a_plugin, std::span{ pluginName }); } + constexpr void AuthorName(std::string_view a_name) noexcept { SetCharBuffer(a_name, std::span{ author }); } + constexpr void UsesSigScanning(bool a_value) noexcept { addressIndependence = !a_value; } + constexpr void UsesAddressLibrary(bool a_value) noexcept { addressIndependence = a_value; } + constexpr void HasNoStructUse(bool a_value) noexcept { structureCompatibility = !a_value; } + constexpr void IsLayoutDependent(bool a_value) noexcept { structureCompatibility = a_value; } + constexpr void CompatibleVersions(std::initializer_list a_versions) noexcept + { + // must be zero-terminated + assert(a_versions.size() < std::size(compatibleVersions) - 1); + std::ranges::transform( + a_versions, std::begin(compatibleVersions), + [](const REL::Version& a_version) noexcept { return a_version.pack(); }); + } + constexpr void MinimumRequiredXSEVersion(std::uint32_t a_version) noexcept { xseMinimum = a_version; } + + const std::uint32_t dataVersion{ kVersion }; + std::uint32_t pluginVersion = 0; + char pluginName[256] = {}; + char author[256] = {}; + std::uint32_t addressIndependence; + std::uint32_t structureCompatibility; + std::uint32_t compatibleVersions[16] = {}; + std::uint32_t xseMinimum = SFSE_PACK_LATEST.pack(); + const std::uint32_t reservedNonBreaking = 0; + const std::uint32_t reservedBreaking = 0; + + private: + static constexpr void SetCharBuffer( + std::string_view a_src, + std::span a_dst) noexcept + { + assert(a_src.size() < a_dst.size()); + std::ranges::fill(a_dst, '\0'); + std::ranges::copy(a_src, a_dst.begin()); + } + }; + static_assert(offsetof(PluginVersionData, dataVersion) == 0x000); + static_assert(offsetof(PluginVersionData, pluginVersion) == 0x004); + static_assert(offsetof(PluginVersionData, pluginName) == 0x008); + static_assert(offsetof(PluginVersionData, author) == 0x108); + static_assert(offsetof(PluginVersionData, addressIndependence) == 0x208); + static_assert(offsetof(PluginVersionData, structureCompatibility) == 0x20C); + static_assert(offsetof(PluginVersionData, compatibleVersions) == 0x210); + static_assert(offsetof(PluginVersionData, xseMinimum) == 0x250); + static_assert(offsetof(PluginVersionData, reservedNonBreaking) == 0x254); + static_assert(offsetof(PluginVersionData, reservedBreaking) == 0x258); + static_assert(sizeof(PluginVersionData) == 0x25C); +} // namespace SFSE diff --git a/CommonLibSF/include/SFSE/Logger.h b/CommonLibSF/include/SFSE/Logger.h new file mode 100644 index 00000000..531231ef --- /dev/null +++ b/CommonLibSF/include/SFSE/Logger.h @@ -0,0 +1,41 @@ +#pragma once + +#define SFSE_MAKE_SOURCE_LOGGER(a_func, a_type) \ + \ + template \ + struct [[maybe_unused]] a_func \ + { \ + a_func() = delete; \ + \ + explicit a_func( \ + fmt::format_string a_fmt, \ + Args&&... a_args, \ + SFSE::stl::source_location a_loc = SFSE::stl::source_location::current()) \ + { \ + spdlog::log( \ + spdlog::source_loc{ \ + a_loc.file_name(), \ + static_cast(a_loc.line()), \ + a_loc.function_name() }, \ + spdlog::level::a_type, \ + a_fmt, \ + std::forward(a_args)...); \ + } \ + }; \ + \ + template \ + a_func(fmt::format_string, Args&&...) -> a_func; + +namespace SFSE::log +{ + SFSE_MAKE_SOURCE_LOGGER(trace, trace); + SFSE_MAKE_SOURCE_LOGGER(debug, debug); + SFSE_MAKE_SOURCE_LOGGER(info, info); + SFSE_MAKE_SOURCE_LOGGER(warn, warn); + SFSE_MAKE_SOURCE_LOGGER(error, err); + SFSE_MAKE_SOURCE_LOGGER(critical, critical); + + [[nodiscard]] std::optional log_directory(); +} + +#undef SFSE_MAKE_SOURCE_LOGGER diff --git a/CommonLibSF/include/SFSE/SFSE.h b/CommonLibSF/include/SFSE/SFSE.h new file mode 100644 index 00000000..94178045 --- /dev/null +++ b/CommonLibSF/include/SFSE/SFSE.h @@ -0,0 +1,11 @@ +#pragma once + +#include "SFSE/Impl/PCH.h" + +#include "SFSE/API.h" +#include "SFSE/IAT.h" +#include "SFSE/InputMap.h" +#include "SFSE/Interfaces.h" +#include "SFSE/Logger.h" +#include "SFSE/Trampoline.h" +#include "SFSE/Version.h" diff --git a/CommonLibSF/include/SFSE/Trampoline.h b/CommonLibSF/include/SFSE/Trampoline.h new file mode 100644 index 00000000..7c0b1646 --- /dev/null +++ b/CommonLibSF/include/SFSE/Trampoline.h @@ -0,0 +1,359 @@ +#pragma once + +#if defined(SFSE_SUPPORT_XBYAK) +namespace Xbyak +{ + class CodeGenerator; +} +#endif + +namespace SFSE +{ + namespace detail + { + [[nodiscard]] constexpr std::size_t roundup(std::size_t a_number, std::size_t a_multiple) noexcept + { + if (a_multiple == 0) { + return 0; + } + + const auto remainder = a_number % a_multiple; + return remainder == 0 ? + a_number : + a_number + a_multiple - remainder; + } + + [[nodiscard]] constexpr std::size_t rounddown(std::size_t a_number, std::size_t a_multiple) noexcept + { + if (a_multiple == 0) { + return 0; + } + + const auto remainder = a_number % a_multiple; + return remainder == 0 ? + a_number : + a_number - remainder; + } + } + + class Trampoline + { + public: + using deleter_type = std::function; + + Trampoline() = default; + Trampoline(const Trampoline&) = delete; + + Trampoline(Trampoline&& a_rhs) { move_from(std::move(a_rhs)); } + + explicit Trampoline(std::string_view a_name) : + _name(a_name) + {} + + ~Trampoline() { release(); } + + Trampoline& operator=(const Trampoline&) = delete; + + Trampoline& operator=(Trampoline&& a_rhs) + { + if (this != std::addressof(a_rhs)) { + move_from(std::move(a_rhs)); + } + return *this; + } + + void create(std::size_t a_size) { return create(a_size, nullptr); } + + void create(std::size_t a_size, void* a_module) + { + if (a_size == 0) { + stl::report_and_fail("cannot create a trampoline with a zero size"sv); + } + + if (!a_module) { + const auto text = REL::Module::get().segment(REL::Segment::textx); + a_module = text.pointer() + text.size(); + } + + auto mem = do_create(a_size, reinterpret_cast(a_module)); + if (!mem) { + stl::report_and_fail("failed to create trampoline"sv); + } + + set_trampoline(mem, a_size, + [](void* a_mem, std::size_t) { + WinAPI::VirtualFree(a_mem, 0, MEM_RELEASE); + }); + } + + void set_trampoline(void* a_trampoline, std::size_t a_size) { set_trampoline(a_trampoline, a_size, {}); } + + void set_trampoline(void* a_trampoline, std::size_t a_size, deleter_type a_deleter) + { + auto trampoline = static_cast(a_trampoline); + if (trampoline) { + constexpr auto INT3 = static_cast(0xCC); + std::memset(trampoline, INT3, a_size); + } + + release(); + + _deleter = std::move(a_deleter); + _data = trampoline; + _capacity = a_size; + _size = 0; + + log_stats(); + } + + [[nodiscard]] void* allocate(std::size_t a_size) + { + auto result = do_allocate(a_size); + log_stats(); + return result; + } + +#ifdef SFSE_SUPPORT_XBYAK + [[nodiscard]] void* allocate(Xbyak::CodeGenerator& a_code); +#endif + + template + [[nodiscard]] T* allocate() + { + return static_cast(allocate(sizeof(T))); + } + + [[nodiscard]] constexpr std::size_t empty() const noexcept { return _capacity == 0; } + [[nodiscard]] constexpr std::size_t capacity() const noexcept { return _capacity; } + [[nodiscard]] constexpr std::size_t allocated_size() const noexcept { return _size; } + [[nodiscard]] constexpr std::size_t free_size() const noexcept { return _capacity - _size; } + + template + std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst) + { + std::uint8_t data = 0; + if constexpr (N == 5) { + // E9 cd + // JMP rel32 + data = 0xE9; + } else if constexpr (N == 6) { + // FF /4 + // JMP r/m64 + data = 0x25; + } else { + static_assert(false && N, "invalid branch size"); + } + + return write_branch(a_src, a_dst, data); + } + + template + std::uintptr_t write_branch(std::uintptr_t a_src, F a_dst) + { + return write_branch(a_src, stl::unrestricted_cast(a_dst)); + } + + template + std::uintptr_t write_call(std::uintptr_t a_src, std::uintptr_t a_dst) + { + std::uint8_t data = 0; + if constexpr (N == 5) { + // E8 cd + // CALL rel32 + data = 0xE8; + } else if constexpr (N == 6) { + // FF /2 + // CALL r/m64 + data = 0x15; + } else { + static_assert(false && N, "invalid call size"); + } + + return write_branch(a_src, a_dst, data); + } + + template + std::uintptr_t write_call(std::uintptr_t a_src, F a_dst) + { + return write_call(a_src, stl::unrestricted_cast(a_dst)); + } + + private: + [[nodiscard]] void* do_create(std::size_t a_size, std::uintptr_t a_address); + + [[nodiscard]] void* do_allocate(std::size_t a_size) + { + if (a_size > free_size()) { + stl::report_and_fail("Failed to handle allocation request"sv); + } + + auto mem = _data + _size; + _size += a_size; + + return mem; + } + + void write_5branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_opcode) + { +#pragma pack(push, 1) + struct SrcAssembly + { + // jmp/call [rip + imm32] + std::uint8_t opcode; // 0 - 0xE9/0xE8 + std::int32_t disp; // 1 + }; + static_assert(offsetof(SrcAssembly, opcode) == 0x0); + static_assert(offsetof(SrcAssembly, disp) == 0x1); + static_assert(sizeof(SrcAssembly) == 0x5); + + // FF /4 + // JMP r/m64 + struct TrampolineAssembly + { + // jmp [rip] + std::uint8_t jmp; // 0 - 0xFF + std::uint8_t modrm; // 1 - 0x25 + std::int32_t disp; // 2 - 0x00000000 + std::uint64_t addr; // 6 - [rip] + }; + static_assert(offsetof(TrampolineAssembly, jmp) == 0x0); + static_assert(offsetof(TrampolineAssembly, modrm) == 0x1); + static_assert(offsetof(TrampolineAssembly, disp) == 0x2); + static_assert(offsetof(TrampolineAssembly, addr) == 0x6); + static_assert(sizeof(TrampolineAssembly) == 0xE); +#pragma pack(pop) + + TrampolineAssembly* mem = nullptr; + if (const auto it = _5branches.find(a_dst); it != _5branches.end()) { + mem = reinterpret_cast(it->second); + } else { + mem = allocate(); + _5branches.emplace(a_dst, reinterpret_cast(mem)); + } + + const auto disp = + reinterpret_cast(mem) - + reinterpret_cast(a_src + sizeof(SrcAssembly)); + if (!in_range(disp)) { // the trampoline should already be in range, so this should never happen + stl::report_and_fail("displacement is out of range"sv); + } + + SrcAssembly assembly; + assembly.opcode = a_opcode; + assembly.disp = static_cast(disp); + REL::safe_write(a_src, &assembly, sizeof(assembly)); + + mem->jmp = static_cast(0xFF); + mem->modrm = static_cast(0x25); + mem->disp = static_cast(0); + mem->addr = static_cast(a_dst); + } + + void write_6branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_modrm) + { +#pragma pack(push, 1) + struct Assembly + { + // jmp/call [rip + imm32] + std::uint8_t opcode; // 0 - 0xFF + std::uint8_t modrm; // 1 - 0x25/0x15 + std::int32_t disp; // 2 + }; + static_assert(offsetof(Assembly, opcode) == 0x0); + static_assert(offsetof(Assembly, modrm) == 0x1); + static_assert(offsetof(Assembly, disp) == 0x2); + static_assert(sizeof(Assembly) == 0x6); +#pragma pack(pop) + + std::uintptr_t* mem = nullptr; + if (const auto it = _6branches.find(a_dst); it != _6branches.end()) { + mem = reinterpret_cast(it->second); + } else { + mem = allocate(); + _6branches.emplace(a_dst, reinterpret_cast(mem)); + } + + const auto disp = + reinterpret_cast(mem) - + reinterpret_cast(a_src + sizeof(Assembly)); + if (!in_range(disp)) { // the trampoline should already be in range, so this should never happen + stl::report_and_fail("displacement is out of range"sv); + } + + Assembly assembly; + assembly.opcode = static_cast(0xFF); + assembly.modrm = a_modrm; + assembly.disp = static_cast(disp); + REL::safe_write(a_src, &assembly, sizeof(assembly)); + + *mem = a_dst; + } + + template + [[nodiscard]] std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_data) + { + const auto disp = reinterpret_cast(a_src + N - 4); + const auto nextOp = a_src + N; + const auto func = nextOp + *disp; + + if constexpr (N == 5) { + write_5branch(a_src, a_dst, a_data); + } else if constexpr (N == 6) { + write_6branch(a_src, a_dst, a_data); + } else { + static_assert(false && N, "invalid branch size"); + } + + return func; + } + + void move_from(Trampoline&& a_rhs) + { + _5branches = std::move(a_rhs._5branches); + _6branches = std::move(a_rhs._6branches); + _name = std::move(a_rhs._name); + + _deleter = std::move(a_rhs._deleter); + + _data = a_rhs._data; + a_rhs._data = nullptr; + + _capacity = a_rhs._capacity; + a_rhs._capacity = 0; + + _size = a_rhs._size; + a_rhs._size = 0; + } + + void log_stats() const; + + [[nodiscard]] bool in_range(std::ptrdiff_t a_disp) const + { + constexpr auto min = (std::numeric_limits::min)(); + constexpr auto max = (std::numeric_limits::max)(); + + return min <= a_disp && a_disp <= max; + } + + void release() + { + if (_data && _deleter) { + _deleter(_data, _capacity); + } + + _5branches.clear(); + _6branches.clear(); + _data = nullptr; + _capacity = 0; + _size = 0; + } + + std::map _5branches; + std::map _6branches; + std::string _name{ "Default Trampoline"sv }; + deleter_type _deleter; + std::byte* _data{ nullptr }; + std::size_t _capacity{ 0 }; + std::size_t _size{ 0 }; + }; +} diff --git a/CommonLibSF/include/SFSE/Version.h b/CommonLibSF/include/SFSE/Version.h new file mode 100644 index 00000000..a30c5e10 --- /dev/null +++ b/CommonLibSF/include/SFSE/Version.h @@ -0,0 +1,10 @@ +#pragma once + +namespace SFSE +{ + constexpr REL::Version RUNTIME_SF_1_6_35(1, 6, 35, 0); + constexpr REL::Version RUNTIME_SF_1_7_23(1, 7, 23, 0); + constexpr auto RUNTIME_LATEST = RUNTIME_SF_1_7_23; + + constexpr REL::Version SFSE_PACK_LATEST(0, 1, 0, 0); +} diff --git a/CommonLibSF/src/RE/Starfield.cpp b/CommonLibSF/src/RE/Starfield.cpp new file mode 100644 index 00000000..4d0c81b1 --- /dev/null +++ b/CommonLibSF/src/RE/Starfield.cpp @@ -0,0 +1 @@ +#include "RE/Starfield.h" diff --git a/CommonLibSF/src/REL/Relocation.cpp b/CommonLibSF/src/REL/Relocation.cpp new file mode 100644 index 00000000..3fbaabf0 --- /dev/null +++ b/CommonLibSF/src/REL/Relocation.cpp @@ -0,0 +1,86 @@ +#include "REL/Relocation.h" + +#define WIN32_LEAN_AND_MEAN + +#define NOGDICAPMASKS +#define NOVIRTUALKEYCODES +//#define NOWINMESSAGES +#define NOWINSTYLES +#define NOSYSMETRICS +#define NOMENUS +#define NOICONS +#define NOKEYSTATES +#define NOSYSCOMMANDS +#define NORASTEROPS +#define NOSHOWWINDOW +#define OEMRESOURCE +#define NOATOM +#define NOCLIPBOARD +#define NOCOLOR +//#define NOCTLMGR +#define NODRAWTEXT +#define NOGDI +#define NOKERNEL +//#define NOUSER +#define NONLS +//#define NOMB +#define NOMEMMGR +#define NOMETAFILE +#define NOMINMAX +//#define NOMSG +#define NOOPENFILE +#define NOSCROLL +#define NOSERVICE +#define NOSOUND +#define NOTEXTMETRIC +#define NOWH +#define NOWINOFFSETS +#define NOCOMM +#define NOKANJI +#define NOHELP +#define NOPROFILER +#define NODEFERWINDOWPOS +#define NOMCX + +#include +#include + +namespace REL +{ + Module::Module(std::uintptr_t a_base) + { + stl_assert(a_base, "failed to initializing module info with null module base"); + + _base = a_base; + + auto dosHeader = reinterpret_cast(_base); + auto ntHeader = stl::adjust_pointer<::IMAGE_NT_HEADERS64>(dosHeader, dosHeader->e_lfanew); + const auto* sections = IMAGE_FIRST_SECTION(ntHeader); + const auto size = std::min(ntHeader->FileHeader.NumberOfSections, _segments.size()); + + for (std::size_t i = 0; i < size; ++i) { + const auto& section = sections[i]; + const auto it = std::find_if( + SEGMENTS.begin(), + SEGMENTS.end(), + [&](auto&& a_elem) { + constexpr auto size = std::extent_v; + const auto len = (std::min)(a_elem.first.size(), size); + return std::memcmp(a_elem.first.data(), section.Name, len) == 0 && + (section.Characteristics & a_elem.second) == a_elem.second; + }); + if (it != SEGMENTS.end()) { + const auto idx = static_cast(std::distance(SEGMENTS.begin(), it)); + _segments[idx] = Segment{ _base, _base + section.VirtualAddress, section.Misc.VirtualSize }; + } + } + } + + Module::Module(std::string_view a_filePath) + { + const auto base = AsAddress(GetModuleHandle(a_filePath.data())) & ~3; + stl_assert(base, "failed to initializing module info with file {}", a_filePath); + + *this = Module(base); + } +} diff --git a/CommonLibSF/src/SFSE/API.cpp b/CommonLibSF/src/SFSE/API.cpp new file mode 100644 index 00000000..5b075b82 --- /dev/null +++ b/CommonLibSF/src/SFSE/API.cpp @@ -0,0 +1,131 @@ +#include "SFSE/API.h" +#include "SFSE/Logger.h" + +namespace SFSE +{ + namespace detail + { + struct APIStorage + { + public: + [[nodiscard]] static APIStorage& get() noexcept + { + static APIStorage singleton; + return singleton; + } + + PluginHandle pluginHandle{ static_cast(-1) }; + std::uint32_t releaseIndex{ 0 }; + + TrampolineInterface* trampolineInterface{ nullptr }; + + MessagingInterface* messagingInterface{ nullptr }; + + + std::mutex apiLock; + std::vector> apiInitRegs; + bool apiInit{ false }; + + private: + APIStorage() noexcept = default; + APIStorage(const APIStorage&) = delete; + APIStorage(APIStorage&&) = delete; + + ~APIStorage() noexcept = default; + + APIStorage& operator=(const APIStorage&) = delete; + APIStorage& operator=(APIStorage&&) = delete; + }; + + template + T* QueryInterface(const LoadInterface* a_intfc, std::uint32_t a_id) + { + auto result = static_cast(a_intfc->QueryInterface(a_id)); + if (result && result->Version() > T::kVersion) { + log::warn("interface definition is out of date"sv); + } + return result; + } + } + + void Init(const LoadInterface* a_intfc) noexcept + { + stl_assert(a_intfc, "interface is null"sv); + + (void)REL::Module::get(); + + auto& storage = detail::APIStorage::get(); + const auto& intfc = *a_intfc; + + const std::scoped_lock l(storage.apiLock); + if (!storage.apiInit) { + storage.pluginHandle = intfc.GetPluginHandle(); + + storage.trampolineInterface = detail::QueryInterface(a_intfc, LoadInterface::kTrampoline); + storage.messagingInterface = detail::QueryInterface(a_intfc, LoadInterface::kMessaging); + + storage.apiInit = true; + auto& regs = storage.apiInitRegs; + for (const auto& reg : regs) { + reg(); + } + regs.clear(); + regs.shrink_to_fit(); + } + } + + void RegisterForAPIInitEvent(std::function a_fn) + { + { + auto& storage = detail::APIStorage::get(); + const std::scoped_lock l(storage.apiLock); + if (!storage.apiInit) { + storage.apiInitRegs.push_back(a_fn); + return; + } + } + + a_fn(); + } + + PluginHandle GetPluginHandle() noexcept + { + return detail::APIStorage::get().pluginHandle; + } + + std::uint32_t GetReleaseIndex() noexcept + { + return detail::APIStorage::get().releaseIndex; + } + + const TrampolineInterface* GetTrampolineInterface() noexcept + { + return detail::APIStorage::get().trampolineInterface; + } + + const MessagingInterface* GetMessagingInterface() noexcept + { + return detail::APIStorage::get().messagingInterface; + } + + Trampoline& GetTrampoline() + { + static Trampoline trampoline; + return trampoline; + } + + void AllocTrampoline(std::size_t a_size, bool a_trySFSEReserve) + { + auto& trampoline = GetTrampoline(); + if (auto intfc = GetTrampolineInterface(); + intfc && a_trySFSEReserve) { + auto memory = intfc->AllocateFromBranchPool(a_size); + if (memory) { + trampoline.set_trampoline(memory, a_size); + return; + } + } + + trampoline.create(a_size); + } +} diff --git a/CommonLibSF/src/SFSE/IAT.cpp b/CommonLibSF/src/SFSE/IAT.cpp new file mode 100644 index 00000000..9335ef1d --- /dev/null +++ b/CommonLibSF/src/SFSE/IAT.cpp @@ -0,0 +1,119 @@ +#include "SFSE/IAT.h" + +#include "SFSE/Logger.h" + +#define WIN32_LEAN_AND_MEAN + +#define NOGDICAPMASKS +#define NOVIRTUALKEYCODES +//#define NOWINMESSAGES +#define NOWINSTYLES +#define NOSYSMETRICS +#define NOMENUS +#define NOICONS +#define NOKEYSTATES +#define NOSYSCOMMANDS +#define NORASTEROPS +#define NOSHOWWINDOW +#define OEMRESOURCE +#define NOATOM +#define NOCLIPBOARD +#define NOCOLOR +//#define NOCTLMGR +#define NODRAWTEXT +#define NOGDI +#define NOKERNEL +//#define NOUSER +#define NONLS +//#define NOMB +#define NOMEMMGR +#define NOMETAFILE +#define NOMINMAX +//#define NOMSG +#define NOOPENFILE +#define NOSCROLL +#define NOSERVICE +#define NOSOUND +#define NOTEXTMETRIC +#define NOWH +#define NOWINOFFSETS +#define NOCOMM +#define NOKANJI +#define NOHELP +#define NOPROFILER +#define NODEFERWINDOWPOS +#define NOMCX + +#include + +namespace SFSE +{ + std::uintptr_t GetIATAddr(std::string_view a_dll, std::string_view a_function) + { + return reinterpret_cast(GetIATPtr(std::move(a_dll), std::move(a_function))); + } + + std::uintptr_t GetIATAddr(void* a_module, std::string_view a_dll, std::string_view a_function) + { + return reinterpret_cast(GetIATPtr(a_module, std::move(a_dll), std::move(a_function))); + } + + void* GetIATPtr(std::string_view a_dll, std::string_view a_function) + { + return GetIATPtr(REL::Module::get().pointer(), std::move(a_dll), std::move(a_function)); + } + + // https://guidedhacking.com/attachments/pe_imptbl_headers-jpg.2241/ + void* GetIATPtr(void* a_module, std::string_view a_dll, std::string_view a_function) + { + assert(a_module); + auto dosHeader = static_cast<::IMAGE_DOS_HEADER*>(a_module); + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + log::error("Invalid DOS header"); + return nullptr; + } + + auto ntHeader = stl::adjust_pointer<::IMAGE_NT_HEADERS>(dosHeader, dosHeader->e_lfanew); + auto& dataDir = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + auto importDesc = stl::adjust_pointer<::IMAGE_IMPORT_DESCRIPTOR>(dosHeader, dataDir.VirtualAddress); + + for (auto import = importDesc; import->Characteristics != 0; ++import) { + auto name = stl::adjust_pointer(dosHeader, import->Name); + if (a_dll.size() == strlen(name) && + _strnicmp(a_dll.data(), name, a_dll.size()) != 0) { + continue; + } + + auto thunk = stl::adjust_pointer<::IMAGE_THUNK_DATA>(dosHeader, import->OriginalFirstThunk); + for (std::size_t i = 0; thunk[i].u1.Ordinal; ++i) { + if (IMAGE_SNAP_BY_ORDINAL(thunk[i].u1.Ordinal)) { + continue; + } + + auto importByName = stl::adjust_pointer(dosHeader, thunk[i].u1.AddressOfData); + if (a_function.size() == strlen(importByName->Name) && + _strnicmp(a_function.data(), importByName->Name, a_function.size()) == 0) { + return stl::adjust_pointer<::IMAGE_THUNK_DATA>(dosHeader, import->FirstThunk) + i; + } + } + } + + log::warn("Failed to find {} ({})", a_dll, a_function); + return nullptr; + } + + std::uintptr_t PatchIAT(std::uintptr_t a_newFunc, std::string_view a_dll, std::string_view a_function) + { + std::uintptr_t origAddr = 0; + + auto oldFunc = GetIATAddr(a_dll, a_function); + if (oldFunc) { + origAddr = *reinterpret_cast(oldFunc); + REL::safe_write(oldFunc, a_newFunc); + } else { + log::warn("Failed to patch {} ({})", a_dll, a_function); + } + + return origAddr; + } +} diff --git a/CommonLibSF/src/SFSE/Impl/PCH.cpp b/CommonLibSF/src/SFSE/Impl/PCH.cpp new file mode 100644 index 00000000..e376e25f --- /dev/null +++ b/CommonLibSF/src/SFSE/Impl/PCH.cpp @@ -0,0 +1 @@ +#include "SFSE/Impl/PCH.h" diff --git a/CommonLibSF/src/SFSE/Impl/WinAPI.cpp b/CommonLibSF/src/SFSE/Impl/WinAPI.cpp new file mode 100644 index 00000000..95617874 --- /dev/null +++ b/CommonLibSF/src/SFSE/Impl/WinAPI.cpp @@ -0,0 +1,480 @@ +#include "SFSE/Impl/WinAPI.h" + +#include + +#undef FindFirstFile +#undef FindNextFile +#undef GetEnvironmentVariable +#undef GetFileVersionInfo +#undef GetFileVersionInfoSize +#undef GetKeyNameText +#undef GetModuleFileName +#undef GetModuleHandle +#undef GetPrivateProfileString +#undef LoadLibrary +#undef MessageBox +#undef OutputDebugString +#undef RegQueryValueEx +#undef VerQueryValue + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +namespace SFSE::WinAPI +{ + bool FreeLibrary(HMODULE a_module) noexcept + { + return ::FreeLibrary(reinterpret_cast<::HMODULE>(a_module)); + } + + [[nodiscard]] bool FindClose(void* a_findFile) noexcept + { + return static_cast( + ::FindClose(static_cast<::HMODULE>(a_findFile))); + } + + [[nodiscard]] void* FindFirstFile( + const char* a_fileName, + WIN32_FIND_DATAA* a_findFileData) noexcept + { + return static_cast( + ::FindFirstFileA( + static_cast<::LPCSTR>(a_fileName), + reinterpret_cast<::LPWIN32_FIND_DATAA>(a_findFileData))); + } + + [[nodiscard]] void* FindFirstFile( + const wchar_t* a_fileName, + WIN32_FIND_DATAW* a_findFileData) noexcept + { + return static_cast( + ::FindFirstFileW( + static_cast<::LPCWSTR>(a_fileName), + reinterpret_cast<::LPWIN32_FIND_DATAW>(a_findFileData))); + } + + [[nodiscard]] bool FindNextFile( + void* a_findFile, + WIN32_FIND_DATAA* a_findFileData) noexcept + { + return static_cast( + ::FindNextFileA( + static_cast<::HANDLE>(a_findFile), + reinterpret_cast<::LPWIN32_FIND_DATAA>(a_findFileData))); + } + + [[nodiscard]] bool FindNextFile( + void* a_findFile, + WIN32_FIND_DATAW* a_findFileData) noexcept + { + return static_cast( + ::FindNextFileW( + static_cast<::HANDLE>(a_findFile), + reinterpret_cast<::LPWIN32_FIND_DATAW>(a_findFileData))); + } + + void* GetCurrentModule() noexcept + { + return static_cast( + std::addressof(__ImageBase)); + } + + void* GetCurrentProcess() noexcept + { + return static_cast( + ::GetCurrentProcess()); + } + + std::uint32_t GetCurrentThreadID() noexcept + { + return static_cast( + ::GetCurrentThreadId()); + } + + [[nodiscard]] std::uint32_t GetEnvironmentVariable( + const char* a_name, + char* a_buffer, + std::uint32_t a_size) noexcept + { + return static_cast( + ::GetEnvironmentVariableA( + static_cast<::LPCSTR>(a_name), + static_cast<::LPSTR>(a_buffer), + static_cast<::DWORD>(a_size))); + } + + [[nodiscard]] std::uint32_t GetEnvironmentVariable( + const wchar_t* a_name, + wchar_t* a_buffer, + std::uint32_t a_size) noexcept + { + return static_cast( + ::GetEnvironmentVariableW( + static_cast<::LPCWSTR>(a_name), + static_cast<::LPWSTR>(a_buffer), + static_cast<::DWORD>(a_size))); + } + + bool GetFileVersionInfo( + const char* a_filename, + std::uint32_t a_handle, + std::uint32_t a_len, + void* a_data) noexcept + { + return static_cast( + ::GetFileVersionInfoA( + static_cast<::LPCSTR>(a_filename), + static_cast<::DWORD>(a_handle), + static_cast<::DWORD>(a_len), + static_cast<::LPVOID>(a_data))); + } + + bool GetFileVersionInfo( + const wchar_t* a_filename, + std::uint32_t a_handle, + std::uint32_t a_len, + void* a_data) noexcept + { + return static_cast( + ::GetFileVersionInfoW( + static_cast<::LPCWSTR>(a_filename), + static_cast<::DWORD>(a_handle), + static_cast<::DWORD>(a_len), + static_cast<::LPVOID>(a_data))); + } + + std::uint32_t GetFileVersionInfoSize( + const char* a_filename, + std::uint32_t* a_handle) noexcept + { + return static_cast( + ::GetFileVersionInfoSizeA( + static_cast<::LPCSTR>(a_filename), + reinterpret_cast<::LPDWORD>(a_handle))); + } + + std::uint32_t GetFileVersionInfoSize( + const wchar_t* a_filename, + std::uint32_t* a_handle) noexcept + { + return static_cast( + ::GetFileVersionInfoSizeW( + static_cast<::LPCWSTR>(a_filename), + reinterpret_cast<::LPDWORD>(a_handle))); + } + + int GetKeyNameText(std::int32_t a_lParam, wchar_t* a_buffer, int a_size) noexcept + { + return ::GetKeyNameTextW(static_cast<::LONG>(a_lParam), static_cast<::LPWSTR>(a_buffer), a_size); + } + + int GetKeyNameText(std::int32_t a_lParam, char* a_buffer, int a_size) noexcept + { + return ::GetKeyNameTextA(static_cast<::LONG>(a_lParam), static_cast<::LPSTR>(a_buffer), a_size); + } + + std::int16_t GetKeyState(int nVirtKey) noexcept + { + return ::GetKeyState(nVirtKey); + } + + std::size_t GetMaxPath() noexcept + { + return static_cast(MAX_PATH); + } + + std::uint32_t GetModuleFileName( + void* a_module, + char* a_filename, + std::uint32_t a_size) noexcept + { + return static_cast( + ::GetModuleFileNameA( + static_cast<::HMODULE>(a_module), + static_cast<::LPSTR>(a_filename), + static_cast<::DWORD>(a_size))); + } + + std::uint32_t GetModuleFileName( + void* a_module, + wchar_t* a_filename, + std::uint32_t a_size) noexcept + { + return static_cast( + ::GetModuleFileNameW( + static_cast<::HMODULE>(a_module), + static_cast<::LPWSTR>(a_filename), + static_cast<::DWORD>(a_size))); + } + + HMODULE GetModuleHandle(const char* a_moduleName) noexcept + { + return reinterpret_cast( + ::GetModuleHandleA( + static_cast<::LPCSTR>(a_moduleName))); + } + + HMODULE GetModuleHandle(const wchar_t* a_moduleName) noexcept + { + return reinterpret_cast( + ::GetModuleHandleW( + static_cast<::LPCWSTR>(a_moduleName))); + } + + std::uint32_t GetPrivateProfileString( + const char* a_appName, + const char* a_keyName, + const char* a_default, + char* a_outString, + std::uint32_t a_size, + const char* a_fileName) noexcept + { + return static_cast( + ::GetPrivateProfileStringA( + static_cast<::LPCSTR>(a_appName), + static_cast<::LPCSTR>(a_keyName), + static_cast<::LPCSTR>(a_default), + static_cast<::LPSTR>(a_outString), + static_cast<::DWORD>(a_size), + static_cast<::LPCSTR>(a_fileName))); + } + + std::uint32_t GetPrivateProfileString( + const wchar_t* a_appName, + const wchar_t* a_keyName, + const wchar_t* a_default, + wchar_t* a_outString, + std::uint32_t a_size, + const wchar_t* a_fileName) noexcept + { + return static_cast( + ::GetPrivateProfileStringW( + static_cast<::LPCWSTR>(a_appName), + static_cast<::LPCWSTR>(a_keyName), + static_cast<::LPCWSTR>(a_default), + static_cast<::LPWSTR>(a_outString), + static_cast<::DWORD>(a_size), + static_cast<::LPCWSTR>(a_fileName))); + } + + void* GetProcAddress( + void* a_module, + const char* a_procName) noexcept + { + return reinterpret_cast( + ::GetProcAddress( + static_cast<::HMODULE>(a_module), + static_cast<::LPCSTR>(a_procName))); + } + + std::string_view GetProcPath( + HMODULE a_handle) noexcept + { + static std::string fileName(MAX_PATH + 1, ' '); + auto res = GetModuleFileName(a_handle, fileName.data(), MAX_PATH + 1); + if (res == 0) { + fileName = "[ProcessHost]"; + res = 13; + } + + return { fileName.c_str(), res }; + } + + bool IsDebuggerPresent() noexcept + { + return static_cast( + ::IsDebuggerPresent()); + } + + HMODULE LoadLibrary(const char* a_libFileName) noexcept + { + return reinterpret_cast(::LoadLibraryA(static_cast<::LPCSTR>(a_libFileName))); + } + + HMODULE LoadLibrary(const wchar_t* a_libFileName) noexcept + { + return reinterpret_cast(::LoadLibraryW(static_cast<::LPCWSTR>(a_libFileName))); + } + + std::int32_t MessageBox( + void* a_wnd, + const char* a_text, + const char* a_caption, + unsigned int a_type) noexcept + { + return static_cast( + ::MessageBoxA( + static_cast<::HWND>(a_wnd), + static_cast<::LPCSTR>(a_text), + static_cast<::LPCSTR>(a_caption), + static_cast<::UINT>(a_type))); + } + + std::int32_t MessageBox( + void* a_wnd, + const wchar_t* a_text, + const wchar_t* a_caption, + unsigned int a_type) noexcept + { + return static_cast( + ::MessageBoxW( + static_cast<::HWND>(a_wnd), + static_cast<::LPCWSTR>(a_text), + static_cast<::LPCWSTR>(a_caption), + static_cast<::UINT>(a_type))); + } + + int MultiByteToWideChar( + unsigned int a_codePage, + std::uint32_t a_flags, + const char* a_multiByteStr, + int a_multiByte, + wchar_t* a_wideCharStr, + int a_wideChar) + { + return ::MultiByteToWideChar( + static_cast<::UINT>(a_codePage), + static_cast<::DWORD>(a_flags), + static_cast<::LPCCH>(a_multiByteStr), + a_multiByte, + static_cast<::LPWSTR>(a_wideCharStr), + a_wideChar); + } + + void OutputDebugString( + const char* a_outputString) noexcept + { + ::OutputDebugStringA( + static_cast<::LPCSTR>(a_outputString)); + } + + void OutputDebugString( + const wchar_t* a_outputString) noexcept + { + ::OutputDebugStringW( + static_cast<::LPCWSTR>(a_outputString)); + } + + long RegGetValueW(HKEY hkey, const char* subKey, const char* value, unsigned long flags, unsigned long* type, + void* data, unsigned long* length) + { + return ::RegGetValueA(reinterpret_cast<::HKEY>(hkey), subKey, value, flags, type, data, length); + } + + long RegGetValueW(HKEY hkey, const wchar_t* subKey, const wchar_t* value, unsigned long flags, unsigned long* type, + void* data, unsigned long* length) + { + return ::RegGetValueW(reinterpret_cast<::HKEY>(hkey), subKey, value, flags, type, data, length); + } + + int ShowCursor(bool bShow) noexcept + { + return ::ShowCursor(static_cast<::BOOL>(bShow)); + } + + void TerminateProcess( + void* a_process, + unsigned int a_exitCode) noexcept + { + ::TerminateProcess( + static_cast<::HANDLE>(a_process), + static_cast<::UINT>(a_exitCode)); +#if defined(__clang__) || defined(__GNUC__) + __builtin_unreachable(); +#elif defined(_MSC_VER) + __assume(false); +#endif + } + + void* TlsGetValue(std::uint32_t a_tlsIndex) noexcept + { + return static_cast( + ::TlsGetValue( + static_cast<::DWORD>(a_tlsIndex))); + } + + bool TlsSetValue( + std::uint32_t a_tlsIndex, + void* a_tlsValue) noexcept + { + return static_cast( + ::TlsSetValue( + static_cast<::DWORD>(a_tlsIndex), + static_cast<::LPVOID>(a_tlsValue))); + } + + bool VirtualFree( + void* a_address, + std::size_t a_size, + std::uint32_t a_freeType) noexcept + { + return static_cast( + ::VirtualFree( + static_cast<::LPVOID>(a_address), + static_cast<::SIZE_T>(a_size), + static_cast<::DWORD>(a_freeType))); + } + + bool VerQueryValue( + const void* a_block, + const char* a_subBlock, + void** a_buffer, + unsigned int* a_len) noexcept + { + return static_cast( + ::VerQueryValueA( + static_cast<::LPCVOID>(a_block), + static_cast<::LPCSTR>(a_subBlock), + static_cast<::LPVOID*>(a_buffer), + static_cast<::PUINT>(a_len))); + } + + bool VerQueryValue( + const void* a_block, + const wchar_t* a_subBlock, + void** a_buffer, + unsigned int* a_len) noexcept + { + return static_cast( + ::VerQueryValueW( + static_cast<::LPCVOID>(a_block), + static_cast<::LPCWSTR>(a_subBlock), + static_cast<::LPVOID*>(a_buffer), + static_cast<::PUINT>(a_len))); + } + + bool VirtualProtect( + void* a_address, + std::size_t a_size, + std::uint32_t a_newProtect, + std::uint32_t* a_oldProtect) noexcept + { + return static_cast( + ::VirtualProtect( + static_cast<::LPVOID>(a_address), + static_cast<::SIZE_T>(a_size), + static_cast<::DWORD>(a_newProtect), + reinterpret_cast<::PDWORD>(a_oldProtect))); + } + + int WideCharToMultiByte( + unsigned int a_codePage, + std::uint32_t a_flags, + const wchar_t* a_wideCharStr, + int a_wideChar, + char* a_multiByteStr, + int a_multiByte, + const char* a_defaultChar, + int* a_usedDefaultChar) + { + return ::WideCharToMultiByte( + static_cast<::UINT>(a_codePage), + static_cast<::DWORD>(a_flags), + static_cast<::LPCWCH>(a_wideCharStr), + a_wideChar, + static_cast<::LPSTR>(a_multiByteStr), + a_multiByte, + static_cast<::LPCCH>(a_defaultChar), + static_cast<::LPBOOL>(a_usedDefaultChar)); + } + +} diff --git a/CommonLibSF/src/SFSE/InputMap.cpp b/CommonLibSF/src/SFSE/InputMap.cpp new file mode 100644 index 00000000..703a5346 --- /dev/null +++ b/CommonLibSF/src/SFSE/InputMap.cpp @@ -0,0 +1,232 @@ +#include "SFSE/InputMap.h" + +#include "SFSE/Impl/XInputAPI.h" + +namespace SFSE +{ + std::uint32_t InputMap::GamepadMaskToKeycode(std::uint32_t keyMask) + { + using XInputButton = RE::XInput::XInputButton; + switch (keyMask) { + case XInputButton::XINPUT_GAMEPAD_DPAD_UP: + return kGamepadButtonOffset_DPAD_UP; + case XInputButton::XINPUT_GAMEPAD_DPAD_DOWN: + return kGamepadButtonOffset_DPAD_DOWN; + case XInputButton::XINPUT_GAMEPAD_DPAD_LEFT: + return kGamepadButtonOffset_DPAD_LEFT; + case XInputButton::XINPUT_GAMEPAD_DPAD_RIGHT: + return kGamepadButtonOffset_DPAD_RIGHT; + case XInputButton::XINPUT_GAMEPAD_START: + return kGamepadButtonOffset_START; + case XInputButton::XINPUT_GAMEPAD_BACK: + return kGamepadButtonOffset_BACK; + case XInputButton::XINPUT_GAMEPAD_LEFT_THUMB: + return kGamepadButtonOffset_LEFT_THUMB; + case XInputButton::XINPUT_GAMEPAD_RIGHT_THUMB: + return kGamepadButtonOffset_RIGHT_THUMB; + case XInputButton::XINPUT_GAMEPAD_LEFT_SHOULDER: + return kGamepadButtonOffset_LEFT_SHOULDER; + case XInputButton::XINPUT_GAMEPAD_RIGHT_SHOULDER: + return kGamepadButtonOffset_RIGHT_SHOULDER; + case XInputButton::XINPUT_GAMEPAD_A: + return kGamepadButtonOffset_A; + case XInputButton::XINPUT_GAMEPAD_B: + return kGamepadButtonOffset_B; + case XInputButton::XINPUT_GAMEPAD_X: + return kGamepadButtonOffset_X; + case XInputButton::XINPUT_GAMEPAD_Y: + return kGamepadButtonOffset_Y; + case 0x9: // Left Trigger game-defined ID + return kGamepadButtonOffset_LT; + case 0xA: // Right Trigger game-defined ID + return kGamepadButtonOffset_RT; + default: + return kMaxMacros; // Invalid + } + } + + std::uint32_t InputMap::GamepadKeycodeToMask(std::uint32_t keyCode) + { + using XInputButton = RE::XInput::XInputButton; + + switch (keyCode) { + case kGamepadButtonOffset_DPAD_UP: + return XInputButton::XINPUT_GAMEPAD_DPAD_UP; + case kGamepadButtonOffset_DPAD_DOWN: + return XInputButton::XINPUT_GAMEPAD_DPAD_DOWN; + case kGamepadButtonOffset_DPAD_LEFT: + return XInputButton::XINPUT_GAMEPAD_DPAD_LEFT; + case kGamepadButtonOffset_DPAD_RIGHT: + return XInputButton::XINPUT_GAMEPAD_DPAD_RIGHT; + case kGamepadButtonOffset_START: + return XInputButton::XINPUT_GAMEPAD_START; + case kGamepadButtonOffset_BACK: + return XInputButton::XINPUT_GAMEPAD_BACK; + case kGamepadButtonOffset_LEFT_THUMB: + return XInputButton::XINPUT_GAMEPAD_LEFT_THUMB; + case kGamepadButtonOffset_RIGHT_THUMB: + return XInputButton::XINPUT_GAMEPAD_RIGHT_THUMB; + case kGamepadButtonOffset_LEFT_SHOULDER: + return XInputButton::XINPUT_GAMEPAD_LEFT_SHOULDER; + case kGamepadButtonOffset_RIGHT_SHOULDER: + return XInputButton::XINPUT_GAMEPAD_RIGHT_SHOULDER; + case kGamepadButtonOffset_A: + return XInputButton::XINPUT_GAMEPAD_A; + case kGamepadButtonOffset_B: + return XInputButton::XINPUT_GAMEPAD_B; + case kGamepadButtonOffset_X: + return XInputButton::XINPUT_GAMEPAD_X; + case kGamepadButtonOffset_Y: + return XInputButton::XINPUT_GAMEPAD_Y; + case kGamepadButtonOffset_LT: + return 0x9; // Left Trigger game-defined ID + case kGamepadButtonOffset_RT: + return 0xA; // Right Trigger game-defined ID + default: + return 0xFF; // Invalid + } + } + + std::string InputMap::GetKeyName(std::uint32_t a_keyCode) + { + if (a_keyCode >= kMacro_MouseButtonOffset && a_keyCode < kMacro_GamepadOffset) { + return GetMouseButtonName(a_keyCode); + } else if (a_keyCode >= kMacro_GamepadOffset && a_keyCode < kMaxMacros) { + return GetGamepadButtonName(a_keyCode); + } else { + return GetKeyboardKeyName(a_keyCode); + } + } + + std::string InputMap::GetKeyboardKeyName(std::uint32_t a_keyCode) + { + std::int32_t scancode = static_cast(a_keyCode & 0xFF); + + using DIKey = RE::DirectInput8::DIKey; + + switch (scancode) { + case DIKey::DIK_NUMPADENTER: // Numpad Enter + scancode = 0x11C; + break; + case DIKey::DIK_RCONTROL: // Right Control + scancode = 0x11D; + break; + case DIKey::DIK_DIVIDE: // Numpad / + scancode = 0x135; + break; + case DIKey::DIK_RALT: // Right Alt + scancode = 0x138; + break; + case DIKey::DIK_HOME: // Home + scancode = 0x147; + break; + case DIKey::DIK_UPARROW: // Up Arrow + scancode = 0x148; + break; + case DIKey::DIK_PGUP: // Page Up + scancode = 0x149; + break; + case DIKey::DIK_LEFTARROW: // Left Arrow + scancode = 0x14B; + break; + case DIKey::DIK_RIGHTARROW: // Right Arrow + scancode = 0x14D; + break; + case DIKey::DIK_END: // End + scancode = 0x14F; + break; + case DIKey::DIK_DOWNARROW: // Down Arrow + scancode = 0x150; + break; + case DIKey::DIK_PGDN: // Page Down + scancode = 0x151; + break; + case DIKey::DIK_INSERT: // Insert + scancode = 0x152; + break; + case DIKey::DIK_DELETE: // Delete + scancode = 0x153; + break; + } + + std::int32_t lParam = scancode << 16; + + if (scancode == 0x45) { + lParam |= (0x1 << 24); + } + + wchar_t buffer[MAX_PATH]; + auto length = WinAPI::GetKeyNameText(lParam, buffer, MAX_PATH); + std::wstring keyNameW{ buffer, static_cast(length) }; + + return stl::utf16_to_utf8(keyNameW).value_or(""s); + } + + std::string InputMap::GetMouseButtonName(std::uint32_t a_keyCode) + { + switch (a_keyCode) { + case 256: + return "Left Mouse Button"s; + case 257: + return "Right Mouse Button"s; + case 258: + return "Middle Mouse Button"s; + case 259: + return "Mouse Button 3"s; + case 260: + return "Mouse Button 4"s; + case 261: + return "Mouse Button 5"s; + case 262: + return "Mouse Button 6"s; + case 263: + return "Mouse Button 7"s; + case 264: + return "Mouse Wheel Up"s; + case 265: + return "Mouse Wheel Down"s; + default: + return ""s; + } + } + + std::string InputMap::GetGamepadButtonName(std::uint32_t a_keyCode) + { + switch (a_keyCode) { + case kGamepadButtonOffset_DPAD_UP: + return "Gamepad DPad Up"s; + case kGamepadButtonOffset_DPAD_DOWN: + return "Gamepad DPad Down"s; + case kGamepadButtonOffset_DPAD_LEFT: + return "Gamepad DPad Left"s; + case kGamepadButtonOffset_DPAD_RIGHT: + return "Gamepad DPad Right"s; + case kGamepadButtonOffset_START: + return "Gamepad Start"s; + case kGamepadButtonOffset_BACK: + return "Gamepad Back"s; + case kGamepadButtonOffset_LEFT_THUMB: + return "Gamepad Left Thumb"s; + case kGamepadButtonOffset_RIGHT_THUMB: + return "Gamepad Right Thumb"s; + case kGamepadButtonOffset_LEFT_SHOULDER: + return "Gamepad Left Shoulder"s; + case kGamepadButtonOffset_RIGHT_SHOULDER: + return "Gamepad Right Shoulder"s; + case kGamepadButtonOffset_A: + return "Gamepad A"s; + case kGamepadButtonOffset_B: + return "Gamepad B"s; + case kGamepadButtonOffset_X: + return "Gamepad X"s; + case kGamepadButtonOffset_Y: + return "Gamepad Y"s; + case kGamepadButtonOffset_LT: + return "Gamepad LT"s; + case kGamepadButtonOffset_RT: + return "Gamepad RT"s; + default: + return ""s; + } + } +} diff --git a/CommonLibSF/src/SFSE/Interfaces.cpp b/CommonLibSF/src/SFSE/Interfaces.cpp new file mode 100644 index 00000000..036e13cb --- /dev/null +++ b/CommonLibSF/src/SFSE/Interfaces.cpp @@ -0,0 +1,101 @@ +#include "SFSE/Interfaces.h" +#include "SFSE/API.h" +#include "SFSE/Logger.h" + +#include + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +namespace SFSE +{ + REL::Version QueryInterface::RuntimeVersion() const + { + const auto packed = GetProxy()->runtimeVersion; + const auto major = static_cast((packed & 0xFF000000) >> 24); + const auto minor = static_cast((packed & 0x00FF0000) >> 16); + const auto revision = static_cast((packed & 0x0000FFF0) >> 4); + const auto build = static_cast((packed & 0x0000000F) >> 0); + return { major, minor, revision, build }; + } + + std::uint32_t QueryInterface::SFSEVersion() const + { + return GetProxy()->sfseVersion; + } + + const detail::SFSEInterface* QueryInterface::GetProxy() const + { + assert(this); + return reinterpret_cast(this); + } + + PluginHandle LoadInterface::GetPluginHandle() const + { + return GetProxy()->GetPluginHandle(); + } + + const PluginInfo* LoadInterface::GetPluginInfo(const char* a_name) const + { + return static_cast(GetProxy()->GetPluginInfo(a_name)); + } + + void* LoadInterface::QueryInterface(std::uint32_t a_id) const + { + return GetProxy()->QueryInterface(a_id); + } + + std::uint32_t MessagingInterface::Version() const + { + return GetProxy()->interfaceVersion; + } + + bool MessagingInterface::Dispatch(std::uint32_t a_messageType, void* a_data, std::uint32_t a_dataLen, const char* a_receiver) const + { + auto result = GetProxy()->Dispatch(GetPluginHandle(), a_messageType, a_data, a_dataLen, a_receiver); + if (!result) { + log::error("Failed to dispatch message to {}", (a_receiver ? a_receiver : "all listeners")); + } + return result; + } + + bool MessagingInterface::RegisterListener(EventCallback* a_callback) const + { + return RegisterListener("SFSE", a_callback); + } + + bool MessagingInterface::RegisterListener(const char* a_sender, EventCallback* a_callback) const + { + auto result = GetProxy()->RegisterListener(GetPluginHandle(), a_sender, reinterpret_cast(a_callback)); + if (!result) { + log::error("Failed to register messaging listener for {}", a_sender); + } + return result; + } + + const detail::SFSEMessagingInterface* MessagingInterface::GetProxy() const + { + assert(this); + return reinterpret_cast(this); + } + + std::uint32_t TrampolineInterface::Version() const + { + return GetProxy()->interfaceVersion; + } + + void* TrampolineInterface::AllocateFromBranchPool(std::size_t a_size) const + { + return GetProxy()->AllocateFromBranchPool(GetPluginHandle(), a_size); + } + + void* TrampolineInterface::AllocateFromLocalPool(std::size_t a_size) const + { + return GetProxy()->AllocateFromLocalPool(GetPluginHandle(), a_size); + } + + const detail::SFSETrampolineInterface* TrampolineInterface::GetProxy() const + { + assert(this); + return reinterpret_cast(this); + } +} diff --git a/CommonLibSF/src/SFSE/Logger.cpp b/CommonLibSF/src/SFSE/Logger.cpp new file mode 100644 index 00000000..14ace39a --- /dev/null +++ b/CommonLibSF/src/SFSE/Logger.cpp @@ -0,0 +1,25 @@ +#include "SFSE/Logger.h" +#include "SFSE/API.h" + +#include + +namespace SFSE +{ + namespace log + { + std::optional log_directory() + { + wchar_t* buffer{ nullptr }; + const auto result = ::SHGetKnownFolderPath(::FOLDERID_Documents, ::KNOWN_FOLDER_FLAG::KF_FLAG_DEFAULT, nullptr, std::addressof(buffer)); + std::unique_ptr knownPath(buffer, ::CoTaskMemFree); + if (!knownPath || result != S_OK) { + error("failed to get known folder path"sv); + return std::nullopt; + } + + std::filesystem::path path = knownPath.get(); + path /= "My Games\\Starfield\\SFSE\\Logs"; + return path; + } + } +} diff --git a/CommonLibSF/src/SFSE/Trampoline.cpp b/CommonLibSF/src/SFSE/Trampoline.cpp new file mode 100644 index 00000000..8adca51c --- /dev/null +++ b/CommonLibSF/src/SFSE/Trampoline.cpp @@ -0,0 +1,116 @@ +#include "SFSE/Trampoline.h" + +#include "SFSE/Logger.h" + +#define WIN32_LEAN_AND_MEAN + +#define NOGDICAPMASKS +#define NOVIRTUALKEYCODES +//#define NOWINMESSAGES +#define NOWINSTYLES +#define NOSYSMETRICS +#define NOMENUS +#define NOICONS +#define NOKEYSTATES +#define NOSYSCOMMANDS +#define NORASTEROPS +#define NOSHOWWINDOW +#define OEMRESOURCE +#define NOATOM +#define NOCLIPBOARD +#define NOCOLOR +//#define NOCTLMGR +#define NODRAWTEXT +#define NOGDI +#define NOKERNEL +//#define NOUSER +#define NONLS +//#define NOMB +#define NOMEMMGR +#define NOMETAFILE +#define NOMINMAX +//#define NOMSG +#define NOOPENFILE +#define NOSCROLL +#define NOSERVICE +#define NOSOUND +#define NOTEXTMETRIC +#define NOWH +#define NOWINOFFSETS +#define NOCOMM +#define NOKANJI +#define NOHELP +#define NOPROFILER +#define NODEFERWINDOWPOS +#define NOMCX + +#include + +#ifdef SFSE_SUPPORT_XBYAK +# include +#endif + +namespace SFSE +{ +#ifdef SFSE_SUPPORT_XBYAK + void* Trampoline::allocate(Xbyak::CodeGenerator& a_code) + { + auto result = do_allocate(a_code.getSize()); + log_stats(); + std::memcpy(result, a_code.getCode(), a_code.getSize()); + return result; + } +#endif + + // https://stackoverflow.com/a/54732489 + void* Trampoline::do_create(std::size_t a_size, std::uintptr_t a_address) + { + constexpr std::size_t gigabyte = static_cast(1) << 30; + constexpr std::size_t minRange = gigabyte * 2; + constexpr std::uintptr_t maxAddr = (std::numeric_limits::max)(); + + ::DWORD granularity; + ::SYSTEM_INFO si; + ::GetSystemInfo(&si); + granularity = si.dwAllocationGranularity; + + std::uintptr_t min = a_address >= minRange ? detail::roundup(a_address - minRange, granularity) : 0; + const std::uintptr_t max = a_address < (maxAddr - minRange) ? detail::rounddown(a_address + minRange, granularity) : maxAddr; + std::uintptr_t addr; + + ::MEMORY_BASIC_INFORMATION mbi; + do { + if (!::VirtualQuery(reinterpret_cast(min), std::addressof(mbi), sizeof(mbi))) { + log::error("VirtualQuery failed with code: 0x{:08X}"sv, ::GetLastError()); + return nullptr; + } + + auto baseAddr = reinterpret_cast(mbi.BaseAddress); + min = baseAddr + mbi.RegionSize; + + if (mbi.State == MEM_FREE) { + addr = detail::roundup(baseAddr, granularity); + + // if rounding didn't advance us into the next region and the region is the required size + if (addr < min && (min - addr) >= a_size) { + auto mem = ::VirtualAlloc(reinterpret_cast(addr), a_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (mem) { + return mem; + } else { + log::warn("VirtualAlloc failed with code: 0x{:08X}"sv, ::GetLastError()); + } + } + } + } while (min < max); + + return nullptr; + } + + void Trampoline::log_stats() const + { + auto pct = (static_cast(_size) / + static_cast(_capacity)) * + 100.0; + log::debug("{} => {}B / {}B ({:05.2f}%)"sv, _name, _size, _capacity, pct); + } +} diff --git a/CommonLibSF/vcpkg.json b/CommonLibSF/vcpkg.json new file mode 100644 index 00000000..25458884 --- /dev/null +++ b/CommonLibSF/vcpkg.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", + "name": "commonlibsf", + "version-semver": "1.0.0", + "port-version": 0, + "description": "CommonLibSF, the collaborative effort with advanced features for modern SFSE development.", + "homepage": "https://github.com/Starfield-Reverse-Engineering/CommonLibSF", + "license": "MIT", + "supports": "windows & x64", + "dependencies": [ + { + "name": "vcpkg-cmake-config", + "host": true + }, + "fmt", + "spdlog", + "xbyak" + ] +} \ No newline at end of file diff --git a/generate-sln.ps1 b/generate-sln.ps1 new file mode 100644 index 00000000..b78262b1 --- /dev/null +++ b/generate-sln.ps1 @@ -0,0 +1,2 @@ +Remove-Item $PSScriptRoot/build -Recurse -Force -ErrorAction:SilentlyContinue -Confirm:$False | Out-Null +& cmake -B $PSScriptRoot/build -S $PSScriptRoot/CommonLibSF --preset=REL \ No newline at end of file From 0856b6caa0751c9ffdbf72c2202f4b18c9ec0bc6 Mon Sep 17 00:00:00 2001 From: clang-format Date: Wed, 6 Sep 2023 01:59:56 +0000 Subject: [PATCH 02/15] fix: update sourcegen workflow --- .github/workflows/sourcegen.yml | 10 +- CommonLibSF/include/RE/RTTI.h | 9 +- CommonLibSF/include/RE/Starfield.h | 8 +- CommonLibSF/include/REL/Relocation.h | 207 +++++++++++++------------ CommonLibSF/include/SFSE/API.h | 6 +- CommonLibSF/include/SFSE/Impl/PCH.h | 4 +- CommonLibSF/include/SFSE/Impl/WinAPI.h | 14 +- CommonLibSF/include/SFSE/Interfaces.h | 16 +- CommonLibSF/include/SFSE/Trampoline.h | 8 +- CommonLibSF/src/REL/Relocation.cpp | 6 +- CommonLibSF/src/SFSE/API.cpp | 5 +- CommonLibSF/src/SFSE/Impl/WinAPI.cpp | 22 +-- 12 files changed, 161 insertions(+), 154 deletions(-) diff --git a/.github/workflows/sourcegen.yml b/.github/workflows/sourcegen.yml index 5e58f1ea..a5c7e5a8 100644 --- a/.github/workflows/sourcegen.yml +++ b/.github/workflows/sourcegen.yml @@ -9,4 +9,12 @@ jobs: - name: make Starfield.h shell: pwsh - run: "& ${{ GITHUB_WORKSPACE }}/CommonLibSF/cmake/make-directives.ps1 ${{ GITHUB_WORKSPACE }}/CommonLibSF" + run: "& ${{ github.workspace }}/CommonLibSF/cmake/make-directives.ps1 ${{ github.workspace }}/CommonLibSF" + + - name: commit + uses: EndBug/add-and-commit@v9 + with: + author_name: sourcegen + message: 'chore: update `Starfield.h`' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/CommonLibSF/include/RE/RTTI.h b/CommonLibSF/include/RE/RTTI.h index 25c9dec4..1fa7f098 100644 --- a/CommonLibSF/include/RE/RTTI.h +++ b/CommonLibSF/include/RE/RTTI.h @@ -39,10 +39,10 @@ namespace RE _rva(a_rva) {} - [[nodiscard]] pointer get() const { return is_good() ? REL::Relocation{ REL::Offset(_rva) }.get() : nullptr; } - [[nodiscard]] std::uint32_t offset() const noexcept { return _rva; } - [[nodiscard]] reference operator*() const { return *get(); } - [[nodiscard]] pointer operator->() const { return get(); } + [[nodiscard]] pointer get() const { return is_good() ? REL::Relocation{ REL::Offset(_rva) }.get() : nullptr; } + [[nodiscard]] std::uint32_t offset() const noexcept { return _rva; } + [[nodiscard]] reference operator*() const { return *get(); } + [[nodiscard]] pointer operator->() const { return get(); } [[nodiscard]] explicit constexpr operator bool() const noexcept { return is_good(); } protected: @@ -128,7 +128,6 @@ namespace RE inline void* RTDynamicCast(void* a_inptr, std::int32_t a_vfDelta, void* a_srcType, void* a_targetType, std::int32_t a_isReference) { - using func_t = decltype(&RTDynamicCast); REL::Relocation func{ 0x0 }; return func(a_inptr, a_vfDelta, a_srcType, a_targetType, a_isReference); diff --git a/CommonLibSF/include/RE/Starfield.h b/CommonLibSF/include/RE/Starfield.h index 69a49387..78a2192e 100644 --- a/CommonLibSF/include/RE/Starfield.h +++ b/CommonLibSF/include/RE/Starfield.h @@ -2,8 +2,8 @@ #include "SFSE/Impl/PCH.h" -#include "RE/Offsets_NiRTTI.h" -#include "RE/Offsets_RTTI.h" -#include "RE/Offsets_VTABLE.h" -#include "RE/Offsets.h" +#include "RE/Offsets.h" +#include "RE/Offsets_NiRTTI.h" +#include "RE/Offsets_RTTI.h" +#include "RE/Offsets_VTABLE.h" #include "RE/RTTI.h" diff --git a/CommonLibSF/include/REL/Relocation.h b/CommonLibSF/include/REL/Relocation.h index 220a255a..f11b7ae0 100644 --- a/CommonLibSF/include/REL/Relocation.h +++ b/CommonLibSF/include/REL/Relocation.h @@ -56,108 +56,107 @@ REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER(&, ##__VA_ARGS__) \ REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER(&&, ##__VA_ARGS__) - namespace REL { - namespace detail - { - template - struct member_function_pod_type; - - REL_MAKE_MEMBER_FUNCTION_POD_TYPE(); - REL_MAKE_MEMBER_FUNCTION_POD_TYPE(const); - REL_MAKE_MEMBER_FUNCTION_POD_TYPE(volatile); - REL_MAKE_MEMBER_FUNCTION_POD_TYPE(const volatile); - - template - using member_function_pod_type_t = typename member_function_pod_type::type; - - template - struct member_function_non_pod_type; - - REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(); - REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(const); - REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(volatile); - REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(const volatile); - - template - using member_function_non_pod_type_t = typename member_function_non_pod_type::type; - - // https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention - - template - struct meets_length_req : - std::disjunction< - std::bool_constant, - std::bool_constant, - std::bool_constant, - std::bool_constant> - { - }; - - template - struct meets_function_req : - std::conjunction< - std::is_trivially_constructible, - std::is_trivially_destructible, - std::is_trivially_copy_assignable, - std::negation< - std::is_polymorphic>> - { - }; - - template - struct meets_member_req : - std::is_standard_layout - { - }; - - template - struct is_x64_pod : - std::true_type - { - }; - - template - struct is_x64_pod< - T, - std::enable_if_t< - std::is_union_v>> : - std::false_type - { - }; - - template - struct is_x64_pod< - T, - std::enable_if_t< - std::is_class_v>> : - std::conjunction< - meets_length_req, - meets_function_req, - meets_member_req> - { - }; - - template - static constexpr bool is_x64_pod_v = is_x64_pod::value; - - template < - class F, - class First, - class... Rest> - decltype(auto) invoke_member_function_non_pod(F&& a_func, First&& a_first, Rest&&... a_rest) // - noexcept(std::is_nothrow_invocable_v) - { - using result_t = std::invoke_result_t; - alignas(result_t) std::byte result[sizeof(result_t)]{}; - - using func_t = member_function_non_pod_type_t; - auto func = std::bit_cast(std::forward(a_func)); - - return func(std::forward(a_first), std::addressof(result), std::forward(a_rest)...); - } - } + namespace detail + { + template + struct member_function_pod_type; + + REL_MAKE_MEMBER_FUNCTION_POD_TYPE(); + REL_MAKE_MEMBER_FUNCTION_POD_TYPE(const); + REL_MAKE_MEMBER_FUNCTION_POD_TYPE(volatile); + REL_MAKE_MEMBER_FUNCTION_POD_TYPE(const volatile); + + template + using member_function_pod_type_t = typename member_function_pod_type::type; + + template + struct member_function_non_pod_type; + + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(); + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(const); + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(volatile); + REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE(const volatile); + + template + using member_function_non_pod_type_t = typename member_function_non_pod_type::type; + + // https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention + + template + struct meets_length_req : + std::disjunction< + std::bool_constant, + std::bool_constant, + std::bool_constant, + std::bool_constant> + { + }; + + template + struct meets_function_req : + std::conjunction< + std::is_trivially_constructible, + std::is_trivially_destructible, + std::is_trivially_copy_assignable, + std::negation< + std::is_polymorphic>> + { + }; + + template + struct meets_member_req : + std::is_standard_layout + { + }; + + template + struct is_x64_pod : + std::true_type + { + }; + + template + struct is_x64_pod< + T, + std::enable_if_t< + std::is_union_v>> : + std::false_type + { + }; + + template + struct is_x64_pod< + T, + std::enable_if_t< + std::is_class_v>> : + std::conjunction< + meets_length_req, + meets_function_req, + meets_member_req> + { + }; + + template + static constexpr bool is_x64_pod_v = is_x64_pod::value; + + template < + class F, + class First, + class... Rest> + decltype(auto) invoke_member_function_non_pod(F&& a_func, First&& a_first, Rest&&... a_rest) // + noexcept(std::is_nothrow_invocable_v) + { + using result_t = std::invoke_result_t; + alignas(result_t) std::byte result[sizeof(result_t)]{}; + + using func_t = member_function_non_pod_type_t; + auto func = std::bit_cast(std::forward(a_func)); + + return func(std::forward(a_first), std::addressof(result), std::forward(a_rest)...); + } + } inline constexpr std::uint8_t NOP = 0x90; inline constexpr std::uint8_t NOP2[] = { 0x66, 0x90 }; @@ -248,7 +247,7 @@ namespace REL assert(success != 0); }; - class Version + class Version { public: using value_type = std::uint16_t; @@ -512,7 +511,10 @@ namespace REL [[nodiscard]] constexpr auto base() const noexcept { return _base; } template - [[nodiscard]] constexpr auto* pointer() const noexcept { return std::bit_cast(base()); } + [[nodiscard]] constexpr auto* pointer() const noexcept + { + return std::bit_cast(base()); + } [[nodiscard]] constexpr auto segment(Segment::Name a_segment) noexcept { return _segments[a_segment]; } [[nodiscard]] static Module& get(const std::uintptr_t a_address) noexcept { @@ -570,7 +572,6 @@ namespace REL std::ptrdiff_t _offset{ 0 }; }; - template || std::is_function_v>, std::decay_t, T>> class Relocation { diff --git a/CommonLibSF/include/SFSE/API.h b/CommonLibSF/include/SFSE/API.h index e3fd1908..c1f0a40b 100644 --- a/CommonLibSF/include/SFSE/API.h +++ b/CommonLibSF/include/SFSE/API.h @@ -12,10 +12,10 @@ namespace SFSE void Init(const LoadInterface* a_intfc) noexcept; void RegisterForAPIInitEvent(std::function a_fn); - PluginHandle GetPluginHandle() noexcept; + PluginHandle GetPluginHandle() noexcept; - const TrampolineInterface* GetTrampolineInterface() noexcept; - const MessagingInterface* GetMessagingInterface() noexcept; + const TrampolineInterface* GetTrampolineInterface() noexcept; + const MessagingInterface* GetMessagingInterface() noexcept; Trampoline& GetTrampoline(); void AllocTrampoline(std::size_t a_size, bool a_trySFSEReserve = true); diff --git a/CommonLibSF/include/SFSE/Impl/PCH.h b/CommonLibSF/include/SFSE/Impl/PCH.h index ac023e0d..98f18c0b 100644 --- a/CommonLibSF/include/SFSE/Impl/PCH.h +++ b/CommonLibSF/include/SFSE/Impl/PCH.h @@ -252,7 +252,7 @@ namespace SFSE template constexpr enumeration(Args... a_values) noexcept // requires(std::same_as && ...) - : + : _impl((static_cast(a_values) | ...)) {} @@ -725,7 +725,7 @@ namespace SFSE } [[noreturn]] inline void report_and_fail(std::string_view a_msg, - SFSE::stl::source_location a_loc = SFSE::stl::source_location::current()) + SFSE::stl::source_location a_loc = SFSE::stl::source_location::current()) { report_and_error(a_msg, true, a_loc); } diff --git a/CommonLibSF/include/SFSE/Impl/WinAPI.h b/CommonLibSF/include/SFSE/Impl/WinAPI.h index 3d8dbacf..51c5d716 100644 --- a/CommonLibSF/include/SFSE/Impl/WinAPI.h +++ b/CommonLibSF/include/SFSE/Impl/WinAPI.h @@ -78,10 +78,10 @@ namespace SFSE::WinAPI using HINSTANCE = HINSTANCE__*; using HMODULE = HINSTANCE; - struct HKEY__; - using HKEY = HKEY__*; + struct HKEY__; + using HKEY = HKEY__*; - inline auto HKEY_LOCAL_MACHINE = reinterpret_cast(static_cast(0x80000002)); + inline auto HKEY_LOCAL_MACHINE = reinterpret_cast(static_cast(0x80000002)); struct _WIN32_FIND_DATAA { @@ -506,11 +506,11 @@ namespace SFSE::WinAPI void OutputDebugString( const wchar_t* a_outputString) noexcept; - long RegGetValueW(HKEY hkey, const char* subKey, const char* value, unsigned long flags, unsigned long* type, - void* data, unsigned long* length); + long RegGetValueW(HKEY hkey, const char* subKey, const char* value, unsigned long flags, unsigned long* type, + void* data, unsigned long* length); - long RegGetValueW(HKEY hkey, const wchar_t* subKey, const wchar_t* value, unsigned long flags, unsigned long* type, - void* data, unsigned long* length); + long RegGetValueW(HKEY hkey, const wchar_t* subKey, const wchar_t* value, unsigned long flags, unsigned long* type, + void* data, unsigned long* length); [[nodiscard]] int ShowCursor(bool bShow) noexcept; diff --git a/CommonLibSF/include/SFSE/Interfaces.h b/CommonLibSF/include/SFSE/Interfaces.h index bcaadaaf..cddd10e7 100644 --- a/CommonLibSF/include/SFSE/Interfaces.h +++ b/CommonLibSF/include/SFSE/Interfaces.h @@ -33,9 +33,9 @@ namespace SFSE kTotal }; - [[nodiscard]] PluginHandle GetPluginHandle() const; - const PluginInfo* GetPluginInfo(const char* a_name) const; - [[nodiscard]] void* QueryInterface(std::uint32_t a_id) const; + [[nodiscard]] PluginHandle GetPluginHandle() const; + const PluginInfo* GetPluginInfo(const char* a_name) const; + [[nodiscard]] void* QueryInterface(std::uint32_t a_id) const; }; class MessagingInterface @@ -64,9 +64,9 @@ namespace SFSE [[nodiscard]] std::uint32_t Version() const; - bool Dispatch(std::uint32_t a_messageType, void* a_data, std::uint32_t a_dataLen, const char* a_receiver) const; - bool RegisterListener(EventCallback* a_callback) const; - bool RegisterListener(const char* a_sender, EventCallback* a_callback) const; + bool Dispatch(std::uint32_t a_messageType, void* a_data, std::uint32_t a_dataLen, const char* a_receiver) const; + bool RegisterListener(EventCallback* a_callback) const; + bool RegisterListener(const char* a_sender, EventCallback* a_callback) const; protected: [[nodiscard]] const detail::SFSEMessagingInterface* GetProxy() const; @@ -133,7 +133,7 @@ namespace SFSE std::uint32_t addressIndependence; std::uint32_t structureCompatibility; std::uint32_t compatibleVersions[16] = {}; - std::uint32_t xseMinimum = SFSE_PACK_LATEST.pack(); + std::uint32_t xseMinimum = SFSE_PACK_LATEST.pack(); const std::uint32_t reservedNonBreaking = 0; const std::uint32_t reservedBreaking = 0; @@ -158,4 +158,4 @@ namespace SFSE static_assert(offsetof(PluginVersionData, reservedNonBreaking) == 0x254); static_assert(offsetof(PluginVersionData, reservedBreaking) == 0x258); static_assert(sizeof(PluginVersionData) == 0x25C); -} // namespace SFSE +} // namespace SFSE diff --git a/CommonLibSF/include/SFSE/Trampoline.h b/CommonLibSF/include/SFSE/Trampoline.h index 7c0b1646..af10e3a7 100644 --- a/CommonLibSF/include/SFSE/Trampoline.h +++ b/CommonLibSF/include/SFSE/Trampoline.h @@ -19,8 +19,8 @@ namespace SFSE const auto remainder = a_number % a_multiple; return remainder == 0 ? - a_number : - a_number + a_multiple - remainder; + a_number : + a_number + a_multiple - remainder; } [[nodiscard]] constexpr std::size_t rounddown(std::size_t a_number, std::size_t a_multiple) noexcept @@ -31,8 +31,8 @@ namespace SFSE const auto remainder = a_number % a_multiple; return remainder == 0 ? - a_number : - a_number - remainder; + a_number : + a_number - remainder; } } diff --git a/CommonLibSF/src/REL/Relocation.cpp b/CommonLibSF/src/REL/Relocation.cpp index 3fbaabf0..c451776c 100644 --- a/CommonLibSF/src/REL/Relocation.cpp +++ b/CommonLibSF/src/REL/Relocation.cpp @@ -61,9 +61,9 @@ namespace REL for (std::size_t i = 0; i < size; ++i) { const auto& section = sections[i]; const auto it = std::find_if( - SEGMENTS.begin(), - SEGMENTS.end(), - [&](auto&& a_elem) { + SEGMENTS.begin(), + SEGMENTS.end(), + [&](auto&& a_elem) { constexpr auto size = std::extent_v; const auto len = (std::min)(a_elem.first.size(), size); return std::memcmp(a_elem.first.data(), section.Name, len) == 0 && diff --git a/CommonLibSF/src/SFSE/API.cpp b/CommonLibSF/src/SFSE/API.cpp index 5b075b82..85e17d7d 100644 --- a/CommonLibSF/src/SFSE/API.cpp +++ b/CommonLibSF/src/SFSE/API.cpp @@ -17,10 +17,9 @@ namespace SFSE PluginHandle pluginHandle{ static_cast(-1) }; std::uint32_t releaseIndex{ 0 }; - TrampolineInterface* trampolineInterface{ nullptr }; - - MessagingInterface* messagingInterface{ nullptr }; + TrampolineInterface* trampolineInterface{ nullptr }; + MessagingInterface* messagingInterface{ nullptr }; std::mutex apiLock; std::vector> apiInitRegs; diff --git a/CommonLibSF/src/SFSE/Impl/WinAPI.cpp b/CommonLibSF/src/SFSE/Impl/WinAPI.cpp index 95617874..2c6ed294 100644 --- a/CommonLibSF/src/SFSE/Impl/WinAPI.cpp +++ b/CommonLibSF/src/SFSE/Impl/WinAPI.cpp @@ -354,17 +354,17 @@ namespace SFSE::WinAPI static_cast<::LPCWSTR>(a_outputString)); } - long RegGetValueW(HKEY hkey, const char* subKey, const char* value, unsigned long flags, unsigned long* type, - void* data, unsigned long* length) - { - return ::RegGetValueA(reinterpret_cast<::HKEY>(hkey), subKey, value, flags, type, data, length); - } - - long RegGetValueW(HKEY hkey, const wchar_t* subKey, const wchar_t* value, unsigned long flags, unsigned long* type, - void* data, unsigned long* length) - { - return ::RegGetValueW(reinterpret_cast<::HKEY>(hkey), subKey, value, flags, type, data, length); - } + long RegGetValueW(HKEY hkey, const char* subKey, const char* value, unsigned long flags, unsigned long* type, + void* data, unsigned long* length) + { + return ::RegGetValueA(reinterpret_cast<::HKEY>(hkey), subKey, value, flags, type, data, length); + } + + long RegGetValueW(HKEY hkey, const wchar_t* subKey, const wchar_t* value, unsigned long flags, unsigned long* type, + void* data, unsigned long* length) + { + return ::RegGetValueW(reinterpret_cast<::HKEY>(hkey), subKey, value, flags, type, data, length); + } int ShowCursor(bool bShow) noexcept { From b21ff97dedd958035109a3c7776a2760aa6c9162 Mon Sep 17 00:00:00 2001 From: sourcegen Date: Wed, 6 Sep 2023 02:04:10 +0000 Subject: [PATCH 03/15] chore: update `Starfield.h` --- CommonLibSF/include/RE/Starfield.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CommonLibSF/include/RE/Starfield.h b/CommonLibSF/include/RE/Starfield.h index 78a2192e..69a49387 100644 --- a/CommonLibSF/include/RE/Starfield.h +++ b/CommonLibSF/include/RE/Starfield.h @@ -2,8 +2,8 @@ #include "SFSE/Impl/PCH.h" -#include "RE/Offsets.h" -#include "RE/Offsets_NiRTTI.h" -#include "RE/Offsets_RTTI.h" -#include "RE/Offsets_VTABLE.h" +#include "RE/Offsets_NiRTTI.h" +#include "RE/Offsets_RTTI.h" +#include "RE/Offsets_VTABLE.h" +#include "RE/Offsets.h" #include "RE/RTTI.h" From 4883472f45896dca654aab6cdf0a38b659ff59bf Mon Sep 17 00:00:00 2001 From: DK Date: Wed, 6 Sep 2023 11:18:53 +0800 Subject: [PATCH 04/15] feat: fix leftover code --- CMakeLists.txt | 19 +++++++++++ CommonLibSF/CMakeLists.txt | 2 +- CommonLibSF/include/REL/Relocation.h | 48 +++++++++++++++++++++++++-- CommonLibSF/include/SFSE/Interfaces.h | 8 ++--- CommonLibSF/include/SFSE/Trampoline.h | 7 ++++ CommonLibSF/src/SFSE/Interfaces.cpp | 6 ++-- 6 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..758afd5c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.21) + +# singleton target across multiple projects +if(TARGET CommonLib) + return() +endif() + +# info +project( + CommonLib + LANGUAGES CXX +) + +# out-of-source builds only +if(${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR}) + message(FATAL_ERROR "In-source builds are not allowed.") +endif() + +add_subdirectory(CommonLibSF) \ No newline at end of file diff --git a/CommonLibSF/CMakeLists.txt b/CommonLibSF/CMakeLists.txt index 76f964c4..bab2969e 100644 --- a/CommonLibSF/CMakeLists.txt +++ b/CommonLibSF/CMakeLists.txt @@ -101,7 +101,7 @@ if (MSVC) /wd5053 # support for 'explicit()' in C++17 and earlier is a vendor extension /wd5204 # 'type-name': class has virtual functions, but its trivial destructor is not virtual; instances of objects derived from this class may not be destructed correctly /wd5220 # 'member': a non-static data member with a volatile qualified type no longer implies that compiler generated copy / move constructors and copy / move assignment operators are not trivial - /FI${CMAKE_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}.dir/$/cmake_pch.hxx + /FI${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${PROJECT_NAME}/CMakeFiles/${PROJECT_NAME}.dir/$/cmake_pch.hxx ) endif() diff --git a/CommonLibSF/include/REL/Relocation.h b/CommonLibSF/include/REL/Relocation.h index f11b7ae0..b8f86bfa 100644 --- a/CommonLibSF/include/REL/Relocation.h +++ b/CommonLibSF/include/REL/Relocation.h @@ -576,6 +576,11 @@ namespace REL class Relocation { public: + using value_type = + std::conditional_t< + std::is_member_pointer_v || std::is_function_v>, + std::decay_t, T>; + constexpr Relocation() noexcept = default; constexpr Relocation(const std::uintptr_t a_addr) noexcept : _address(a_addr) @@ -597,22 +602,59 @@ namespace REL { return _address; } + [[nodiscard]] std::size_t offset() const noexcept { return _address - base(); } + template [[nodiscard]] constexpr decltype(auto) operator*() const noexcept requires(std::is_pointer_v) { return *get(); } + template + [[nodiscard]] constexpr auto operator->() const noexcept // + requires(std::is_pointer_v) + { + return get(); + } + template - std::invoke_result_t operator()(Args&&... a_args) const // - noexcept(std::is_nothrow_invocable_v) // - requires(std::invocable) + std::invoke_result_t operator()(Args&&... a_args) const // + noexcept(std::is_nothrow_invocable_v) // + requires(std::invocable) { return invoke(get(), std::forward(a_args)...); } + template + std::uintptr_t write_vfunc(std::size_t a_idx, std::uintptr_t a_newFunc) // + requires(std::same_as) + { + const auto addr = address() + (sizeof(void*) * a_idx); + const auto result = *std::bit_cast(addr); + safe_write(addr, a_newFunc); + return result; + } + + template + std::uintptr_t write_vfunc(std::size_t a_idx, F a_newFunc) // + requires(std::same_as) + { + return write_vfunc(a_idx, stl::unrestricted_cast(a_newFunc)); + } + + /** + template + void write_vfunc() + { + REL::Relocation vtbl{ F::VTABLE[0] }; + T::func = vtbl.write_vfunc(idx, T::thunk); + } + /**/ + private: + [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); } + std::uintptr_t _address{ 0 }; }; } diff --git a/CommonLibSF/include/SFSE/Interfaces.h b/CommonLibSF/include/SFSE/Interfaces.h index cddd10e7..fe0f86aa 100644 --- a/CommonLibSF/include/SFSE/Interfaces.h +++ b/CommonLibSF/include/SFSE/Interfaces.h @@ -65,8 +65,8 @@ namespace SFSE [[nodiscard]] std::uint32_t Version() const; bool Dispatch(std::uint32_t a_messageType, void* a_data, std::uint32_t a_dataLen, const char* a_receiver) const; - bool RegisterListener(EventCallback* a_callback) const; - bool RegisterListener(const char* a_sender, EventCallback* a_callback) const; + bool RegisterListener(EventCallback a_callback) const; + bool RegisterListener(const char* a_sender, EventCallback a_callback) const; protected: [[nodiscard]] const detail::SFSEMessagingInterface* GetProxy() const; @@ -124,7 +124,7 @@ namespace SFSE a_versions, std::begin(compatibleVersions), [](const REL::Version& a_version) noexcept { return a_version.pack(); }); } - constexpr void MinimumRequiredXSEVersion(std::uint32_t a_version) noexcept { xseMinimum = a_version; } + constexpr void MinimumRequiredXSEVersion(REL::Version a_version) noexcept { xseMinimum = a_version.pack(); } const std::uint32_t dataVersion{ kVersion }; std::uint32_t pluginVersion = 0; @@ -133,7 +133,7 @@ namespace SFSE std::uint32_t addressIndependence; std::uint32_t structureCompatibility; std::uint32_t compatibleVersions[16] = {}; - std::uint32_t xseMinimum = SFSE_PACK_LATEST.pack(); + std::uint32_t xseMinimum = 0; const std::uint32_t reservedNonBreaking = 0; const std::uint32_t reservedBreaking = 0; diff --git a/CommonLibSF/include/SFSE/Trampoline.h b/CommonLibSF/include/SFSE/Trampoline.h index af10e3a7..ec2ff181 100644 --- a/CommonLibSF/include/SFSE/Trampoline.h +++ b/CommonLibSF/include/SFSE/Trampoline.h @@ -178,6 +178,13 @@ namespace SFSE return write_call(a_src, stl::unrestricted_cast(a_dst)); } + template + void write_thunk_call(std::uintptr_t a_src) + { + SFSE::AllocTrampoline(14); + T::func = write_call<5>(a_src, T::thunk); + } + private: [[nodiscard]] void* do_create(std::size_t a_size, std::uintptr_t a_address); diff --git a/CommonLibSF/src/SFSE/Interfaces.cpp b/CommonLibSF/src/SFSE/Interfaces.cpp index 036e13cb..dee41d65 100644 --- a/CommonLibSF/src/SFSE/Interfaces.cpp +++ b/CommonLibSF/src/SFSE/Interfaces.cpp @@ -58,14 +58,14 @@ namespace SFSE return result; } - bool MessagingInterface::RegisterListener(EventCallback* a_callback) const + bool MessagingInterface::RegisterListener(EventCallback a_callback) const { return RegisterListener("SFSE", a_callback); } - bool MessagingInterface::RegisterListener(const char* a_sender, EventCallback* a_callback) const + bool MessagingInterface::RegisterListener(const char* a_sender, EventCallback a_callback) const { - auto result = GetProxy()->RegisterListener(GetPluginHandle(), a_sender, reinterpret_cast(a_callback)); + auto result = GetProxy()->RegisterListener(GetPluginHandle(), a_sender, std::bit_cast(a_callback)); if (!result) { log::error("Failed to register messaging listener for {}", a_sender); } From c0bb1fb785d3abcf11e97c4319ff102c94085a35 Mon Sep 17 00:00:00 2001 From: clang-format Date: Wed, 6 Sep 2023 03:19:14 +0000 Subject: [PATCH 05/15] fix: redo workflow --- .../{clang-format.yml => maintenance.yml} | 11 +++++++--- .github/workflows/sourcegen.yml | 20 ------------------- CommonLibSF/include/RE/Starfield.h | 8 ++++---- 3 files changed, 12 insertions(+), 27 deletions(-) rename .github/workflows/{clang-format.yml => maintenance.yml} (65%) delete mode 100644 .github/workflows/sourcegen.yml diff --git a/.github/workflows/clang-format.yml b/.github/workflows/maintenance.yml similarity index 65% rename from .github/workflows/clang-format.yml rename to .github/workflows/maintenance.yml index 0c556649..d5e73e6f 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/maintenance.yml @@ -1,11 +1,15 @@ on: [push] jobs: - formatting: + maintainance: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + + - shell: pwsh + run: "& ${{ github.workspace }}/CommonLibSF/cmake/make-directives.ps1 ${{ github.workspace }}/CommonLibSF" + - uses: DoozyX/clang-format-lint-action@v0.16.2 with: source: '.' @@ -13,9 +17,10 @@ jobs: extensions: 'c,cc,cpp,cxx,h,hpp,hxx,inl,inc,ixx' clangFormatVersion: 16 inplace: True + - uses: EndBug/add-and-commit@v9 with: - author_name: clang-format - message: 'chore: style formatting' + author_name: maintenance + message: 'chore: maintenance' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sourcegen.yml b/.github/workflows/sourcegen.yml deleted file mode 100644 index a5c7e5a8..00000000 --- a/.github/workflows/sourcegen.yml +++ /dev/null @@ -1,20 +0,0 @@ -on: [push] - -jobs: - sourcegen: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v3 - - - name: make Starfield.h - shell: pwsh - run: "& ${{ github.workspace }}/CommonLibSF/cmake/make-directives.ps1 ${{ github.workspace }}/CommonLibSF" - - - name: commit - uses: EndBug/add-and-commit@v9 - with: - author_name: sourcegen - message: 'chore: update `Starfield.h`' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/CommonLibSF/include/RE/Starfield.h b/CommonLibSF/include/RE/Starfield.h index 69a49387..78a2192e 100644 --- a/CommonLibSF/include/RE/Starfield.h +++ b/CommonLibSF/include/RE/Starfield.h @@ -2,8 +2,8 @@ #include "SFSE/Impl/PCH.h" -#include "RE/Offsets_NiRTTI.h" -#include "RE/Offsets_RTTI.h" -#include "RE/Offsets_VTABLE.h" -#include "RE/Offsets.h" +#include "RE/Offsets.h" +#include "RE/Offsets_NiRTTI.h" +#include "RE/Offsets_RTTI.h" +#include "RE/Offsets_VTABLE.h" #include "RE/RTTI.h" From b10e0c0e2351bdc2be0c838f40a4fa4408ba98ae Mon Sep 17 00:00:00 2001 From: DK Date: Wed, 6 Sep 2023 12:15:57 +0800 Subject: [PATCH 06/15] docs: update readme --- README.md | 53 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a435d982..991bd3e1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,43 @@ # CommonLibSF -TODO: Decide on a license for this repository +A collaborative reverse-engineered library for Starfield. -All of the following are placeholders/tentative: +[![C++23](https://img.shields.io/static/v1?label=standard&message=c%2B%2B23&color=blue&logo=c%2B%2B&&logoColor=red&style=flat)]( +https://en.cppreference.com/w/cpp/compiler_support) +![Platform](https://img.shields.io/static/v1?label=platform&message=windows&color=dimgray&style=flat&logo=windows) +[![Game version](https://img.shields.io/badge/game%20version-1.7.23-orange)](#use) ## Build Dependencies - -- MSVC -- xbyak -- spdlog - -## Development Dependencies - -- Visual Studio - -## End-User Dependencies - -- Latest MSVC++ Redistributables -- Address Library for Starfield ++ [CMake](https://cmake.org/) + + Add this to your PATH ++ [PowerShell](https://github.com/PowerShell/PowerShell/releases/tag/v7.3.6) ++ [Vcpkg](https://github.com/microsoft/vcpkg) + + Add the environment variable VCPKG_ROOT with the value as the path to the folder containing vcpkg ++ [Visual Studio Community 2022](https://visualstudio.microsoft.com/) + + Desktop development with C++ + +## Get started +### use example plugin template +(TODO) + +### git submodule & linking in CMake +```ps +git submodule add https://github.com/Starfield-Reverse-Engineering/CommonLibSF.git extern/CommonLibSF +git submodule update -f --init +``` +You should have the following in CMakeLists.txt to compile and link successfully: + +```cmake +add_subdirectory(extern/CommonLibSF) +target_link_libraries( + ${PROJECT_NAME} + PRIVATE + CommonLibSF::CommonLibSF +) +``` + +## End User Dependencies +[Starfield Script Extender](https://www.nexusmods.com/starfield/mods/106) + +## Notes +CommonLib is incompatible with SFSE and is intended to replace it as a static dependency. However, you will still need the runtime component. \ No newline at end of file From f78ac20e21c28d5010124f1da87bedfe47f1bfba Mon Sep 17 00:00:00 2001 From: DK Date: Wed, 6 Sep 2023 12:29:07 +0800 Subject: [PATCH 07/15] fix: remove func --- CommonLibSF/include/SFSE/Trampoline.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CommonLibSF/include/SFSE/Trampoline.h b/CommonLibSF/include/SFSE/Trampoline.h index ec2ff181..af10e3a7 100644 --- a/CommonLibSF/include/SFSE/Trampoline.h +++ b/CommonLibSF/include/SFSE/Trampoline.h @@ -178,13 +178,6 @@ namespace SFSE return write_call(a_src, stl::unrestricted_cast(a_dst)); } - template - void write_thunk_call(std::uintptr_t a_src) - { - SFSE::AllocTrampoline(14); - T::func = write_call<5>(a_src, T::thunk); - } - private: [[nodiscard]] void* do_create(std::size_t a_size, std::uintptr_t a_address); From 42033ea9129d548520affd261e767fa7e48aed64 Mon Sep 17 00:00:00 2001 From: DK Date: Wed, 6 Sep 2023 13:07:17 +0800 Subject: [PATCH 08/15] fix: typo in `CMakeLists` --- CommonLibSF/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommonLibSF/CMakeLists.txt b/CommonLibSF/CMakeLists.txt index bab2969e..df26c9bf 100644 --- a/CommonLibSF/CMakeLists.txt +++ b/CommonLibSF/CMakeLists.txt @@ -12,7 +12,7 @@ option(SFSE_SUPPORT_XBYAK "Enables trampoline support for Xbyak." ON) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUF OFF) +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUG OFF) set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE FILEPATH "") From 217d6444b17edddad276c69f09374783dd56d980 Mon Sep 17 00:00:00 2001 From: DK Date: Thu, 7 Sep 2023 08:39:24 +0800 Subject: [PATCH 09/15] fix: bump `WINVER` to `0x0A00` for Windows 10 --- CommonLibSF/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CommonLibSF/CMakeLists.txt b/CommonLibSF/CMakeLists.txt index df26c9bf..32f3e233 100644 --- a/CommonLibSF/CMakeLists.txt +++ b/CommonLibSF/CMakeLists.txt @@ -53,8 +53,8 @@ add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_compile_definitions( ${PROJECT_NAME} PUBLIC - WINVER=0x0601 # windows 7, minimum supported version by skyrim special edition - _WIN32_WINNT=0x0601 + WINVER=0x0A00 # windows 10, minimum supported version by starfield + _WIN32_WINNT=0x0A00 "$<$:SFSE_SUPPORT_XBYAK=1>" ) From 4207f06ec05292797a1d2b33624311eaef356291 Mon Sep 17 00:00:00 2001 From: DK Date: Thu, 7 Sep 2023 09:03:47 +0800 Subject: [PATCH 10/15] fix: update `vcpkg.json` schema --- CommonLibSF/vcpkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommonLibSF/vcpkg.json b/CommonLibSF/vcpkg.json index 25458884..2cdb2aff 100644 --- a/CommonLibSF/vcpkg.json +++ b/CommonLibSF/vcpkg.json @@ -1,5 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "name": "commonlibsf", "version-semver": "1.0.0", "port-version": 0, From c7eee664ab9252bf59d2a4f12069dc90200a92e2 Mon Sep 17 00:00:00 2001 From: DK Date: Thu, 7 Sep 2023 09:07:11 +0800 Subject: [PATCH 11/15] ci: add module extensions also removed leftover evaluation --- CommonLibSF/cmake/make-sourcelist.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CommonLibSF/cmake/make-sourcelist.ps1 b/CommonLibSF/cmake/make-sourcelist.ps1 index bc653937..500ada6b 100644 --- a/CommonLibSF/cmake/make-sourcelist.ps1 +++ b/CommonLibSF/cmake/make-sourcelist.ps1 @@ -8,7 +8,7 @@ param ( $ErrorActionPreference = "Stop" -$SourceExt = @('.asm', '.c', '.cc', '.cpp', '.cxx', '.h', '.hpp', '.hxx', 'inc', '.inl', '.ixx') +$SourceExt = @('.asm', '.c', '.cc', '.cpp', 'cppm', '.cxx', '.h', '.hpp', '.hxx', 'inc', '.inl', '.ixx', '.mxx') $ConfigExt = @('.ini', '.json', '.toml', '.xml') $DocsExt = @('.md') @@ -38,8 +38,7 @@ function Resolve-Files { Write-Host "[$PathIn/$directory]" Get-ChildItem "$PathIn/$directory" -Recurse -File -ErrorAction SilentlyContinue | Where-Object { - ($_.Extension -in ($SourceExt + $DocsExt)) -and - ($_.Name -notmatch 'Plugin.h|Version.h') + ($_.Extension -in ($SourceExt + $DocsExt)) } | Resolve-Path -Relative | ForEach-Object { Write-Host "`t<$_>" $_generated.Add("`n`t`"$(Normalize-Path $_.Substring(2))`"") | Out-Null From 6ea9957c2391a8491179e4cd59feca357eb009e6 Mon Sep 17 00:00:00 2001 From: DK Date: Thu, 7 Sep 2023 09:56:11 +0800 Subject: [PATCH 12/15] chore: move LICENSE --- CommonLibSF/{LICENSE => LICENSES/COMMONLIB} | 2 ++ 1 file changed, 2 insertions(+) rename CommonLibSF/{LICENSE => LICENSES/COMMONLIB} (91%) diff --git a/CommonLibSF/LICENSE b/CommonLibSF/LICENSES/COMMONLIB similarity index 91% rename from CommonLibSF/LICENSE rename to CommonLibSF/LICENSES/COMMONLIB index 325e2cbb..2271813c 100644 --- a/CommonLibSF/LICENSE +++ b/CommonLibSF/LICENSES/COMMONLIB @@ -1,3 +1,5 @@ +This applies to the files taken with modifications from CommonLibSSE and its variant CommonLibSSE-NG + MIT License Copyright (c) 2018 Ryan-rsm-McKenzie From 3b17d98294b263814f84bb4623ea243fe297e141 Mon Sep 17 00:00:00 2001 From: DK Date: Thu, 7 Sep 2023 12:23:58 +0800 Subject: [PATCH 13/15] fix: clang-cl compatible --- CommonLibSF/include/REL/Relocation.h | 14 +++--- CommonLibSF/include/SFSE/Impl/PCH.h | 64 ++-------------------------- CommonLibSF/include/SFSE/Logger.h | 2 +- 3 files changed, 10 insertions(+), 70 deletions(-) diff --git a/CommonLibSF/include/REL/Relocation.h b/CommonLibSF/include/REL/Relocation.h index b8f86bfa..16650a3b 100644 --- a/CommonLibSF/include/REL/Relocation.h +++ b/CommonLibSF/include/REL/Relocation.h @@ -572,7 +572,7 @@ namespace REL std::ptrdiff_t _offset{ 0 }; }; - template || std::is_function_v>, std::decay_t, T>> + template class Relocation { public: @@ -592,6 +592,7 @@ namespace REL _address(a_rva.address() + a_offset) {} + template [[nodiscard]] constexpr U get() const noexcept(std::is_nothrow_copy_constructible_v) { @@ -604,16 +605,14 @@ namespace REL } [[nodiscard]] std::size_t offset() const noexcept { return _address - base(); } - template [[nodiscard]] constexpr decltype(auto) operator*() const noexcept - requires(std::is_pointer_v) + requires(std::is_pointer_v) { return *get(); } - template [[nodiscard]] constexpr auto operator->() const noexcept // - requires(std::is_pointer_v) + requires(std::is_pointer_v) { return get(); } @@ -626,9 +625,8 @@ namespace REL return invoke(get(), std::forward(a_args)...); } - template std::uintptr_t write_vfunc(std::size_t a_idx, std::uintptr_t a_newFunc) // - requires(std::same_as) + requires(std::same_as) { const auto addr = address() + (sizeof(void*) * a_idx); const auto result = *std::bit_cast(addr); @@ -650,7 +648,7 @@ namespace REL REL::Relocation vtbl{ F::VTABLE[0] }; T::func = vtbl.write_vfunc(idx, T::thunk); } - /**/ + **/ private: [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); } diff --git a/CommonLibSF/include/SFSE/Impl/PCH.h b/CommonLibSF/include/SFSE/Impl/PCH.h index 98f18c0b..fcbd0918 100644 --- a/CommonLibSF/include/SFSE/Impl/PCH.h +++ b/CommonLibSF/include/SFSE/Impl/PCH.h @@ -606,67 +606,8 @@ namespace SFSE return out; } -#ifndef __clang__ - using source_location = std::source_location; -#else - /** - * A polyfill for source location support for Clang. - * - *

- * Clang-CL can use source_location, but not in the context of a default argument due to - * a bug in its support for consteval. This bug does not affect constexpr so - * this class uses a constexpr version of the typical constructor. - *

- */ - struct source_location - { - public: - static constexpr source_location current( - const uint_least32_t a_line = __builtin_LINE(), - const uint_least32_t a_column = __builtin_COLUMN(), - const char* const a_file = __builtin_FILE(), - const char* const a_function = __builtin_FUNCTION()) noexcept - { - source_location result; - result._line = a_line; - result._column = a_column; - result._file = a_file; - result._function = a_function; - return result; - } - - [[nodiscard]] constexpr const char* file_name() const noexcept - { - return _file; - } - - [[nodiscard]] constexpr const char* function_name() const noexcept - { - return _function; - } - - [[nodiscard]] constexpr uint_least32_t line() const noexcept - { - return _line; - } - - [[nodiscard]] constexpr uint_least32_t column() const noexcept - { - return _column; - } - - private: - source_location() = default; - - uint_least32_t _line{}; - uint_least32_t _column{}; - const char* _file = ""; - const char* _function = ""; - }; -#endif - inline bool report_and_error(std::string_view a_msg, bool a_fail = true, - SFSE::stl::source_location a_loc = SFSE::stl::source_location::current()) + std::source_location a_loc = std::source_location::current()) { const auto body = [&]() -> std::wstring { const std::filesystem::path p = a_loc.file_name(); @@ -725,9 +666,10 @@ namespace SFSE } [[noreturn]] inline void report_and_fail(std::string_view a_msg, - SFSE::stl::source_location a_loc = SFSE::stl::source_location::current()) + std::source_location a_loc = std::source_location::current()) { report_and_error(a_msg, true, a_loc); + std::unreachable(); } template diff --git a/CommonLibSF/include/SFSE/Logger.h b/CommonLibSF/include/SFSE/Logger.h index 531231ef..c8259d91 100644 --- a/CommonLibSF/include/SFSE/Logger.h +++ b/CommonLibSF/include/SFSE/Logger.h @@ -10,7 +10,7 @@ explicit a_func( \ fmt::format_string a_fmt, \ Args&&... a_args, \ - SFSE::stl::source_location a_loc = SFSE::stl::source_location::current()) \ + std::source_location a_loc = std::source_location::current()) \ { \ spdlog::log( \ spdlog::source_loc{ \ From 677e902add7463f8de6017263ee41819c98aad26 Mon Sep 17 00:00:00 2001 From: DK Date: Thu, 7 Sep 2023 12:39:33 +0800 Subject: [PATCH 14/15] feat!: support `clang-cl` build --- .gitignore | 2 +- CMakeLists.txt | 2 +- CommonLibSF/CMakeLists.txt | 4 +- CommonLibSF/CMakePresets.json | 194 +++++++++++++++++++++++++++++++--- README.md | 2 +- build-clang-cl.bat | 4 + build-msvc.bat | 4 + generate-sln.ps1 | 2 - make-sln-clang-cl.bat | 3 + make-sln-msvc.bat | 3 + 10 files changed, 201 insertions(+), 19 deletions(-) create mode 100644 build-clang-cl.bat create mode 100644 build-msvc.bat delete mode 100644 generate-sln.ps1 create mode 100644 make-sln-clang-cl.bat create mode 100644 make-sln-msvc.bat diff --git a/.gitignore b/.gitignore index bddce39e..de9bb095 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/[Bb]uild* +/[Bb]uild CMakeUserPresets.json CMakeFiles CMakeCache.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 758afd5c..34686469 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.26) # singleton target across multiple projects if(TARGET CommonLib) diff --git a/CommonLibSF/CMakeLists.txt b/CommonLibSF/CMakeLists.txt index 32f3e233..824b75ee 100644 --- a/CommonLibSF/CMakeLists.txt +++ b/CommonLibSF/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.26) # singleton target across multiple projects if(TARGET CommonLibSF) @@ -101,7 +101,7 @@ if (MSVC) /wd5053 # support for 'explicit()' in C++17 and earlier is a vendor extension /wd5204 # 'type-name': class has virtual functions, but its trivial destructor is not virtual; instances of objects derived from this class may not be destructed correctly /wd5220 # 'member': a non-static data member with a volatile qualified type no longer implies that compiler generated copy / move constructors and copy / move assignment operators are not trivial - /FI${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${PROJECT_NAME}/CMakeFiles/${PROJECT_NAME}.dir/$/cmake_pch.hxx + /FI${CMAKE_CURRENT_SOURCE_DIR}/include/SFSE/Impl/PCH.h ) endif() diff --git a/CommonLibSF/CMakePresets.json b/CommonLibSF/CMakePresets.json index e551398f..8a73393c 100644 --- a/CommonLibSF/CMakePresets.json +++ b/CommonLibSF/CMakePresets.json @@ -7,7 +7,21 @@ }, "configurePresets": [ { - "name": "vcpkg", + "name": "common", + "hidden": true, + "cacheVariables": { + "CMAKE_CXX_FLAGS": "$env{PROJECT_PLATFORM_FLAGS} $env{PROJECT_TEXT_FLAGS} $env{PROJECT_COMPILER_FLAGS} $penv{CXXFLAGS}" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "intelliSenseMode": "windows-msvc-x64", + "enableMicrosoftCodeAnalysis": true, + "enableClangTidyCodeAnalysis": true + } + } + }, + { + "name": "packaging-vcpkg", "hidden": true, "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": { @@ -18,7 +32,35 @@ } }, { - "name": "win64", + "name": "buildtype-debug", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "buildtype-release", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "buildtype-relwithdebinfo", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "buildtype-minsizerel", + "hidden": true, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "MinSizeRel" + } + }, + { + "name": "x64", "hidden": true, "architecture": "x64", "cacheVariables": { @@ -26,26 +68,154 @@ } }, { - "name": "msvc", + "name": "generator-msvc", + "hidden": true, + "inherits": "x64", + "generator": "Visual Studio 17 2022" + }, + { + "name": "generator-ninja", + "hidden": true, + "generator": "Ninja" + }, + { + "name": "compiler-msvc", + "hidden": true, + "environment": { + "PROJECT_COMPILER_FLAGS": "/permissive- /Zc:preprocessor /EHsc /MP /W4 /WX /external:anglebrackets /external:W0 /bigobj", + "PROJECT_COMPILER": "msvc" + } + }, + { + "name": "compiler-clang", "hidden": true, "cacheVariables": { - "CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX $penv{CXXFLAGS}" + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + }, + "environment": { + "PROJECT_COMPILER": "clang", + "PROJECT_COMPILER_FLAGS": "-Wno-overloaded-virtual -Wno-delete-non-abstract-non-virtual-dtor -Wno-inconsistent-missing-override -Wno-reinterpret-base-class" }, - "generator": "Visual Studio 17 2022", "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { - "intelliSenseMode": "windows-msvc-x64", - "enableMicrosoftCodeAnalysis": true, - "enableClangTidyCodeAnalysis": true + "intelliSenseMode": "windows-clang-x64" } } }, { - "name": "REL", + "name": "compiler-clang-cl", + "hidden": true, + "inherits": "compiler-clang", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl" + }, + "environment": { + "CC": "clang-cl", + "CXX": "clang-cl", + "PROJECT_COMPILER_FLAGS": "/permissive- /EHsc /W4 /WX -Wno-overloaded-virtual -Wno-delete-non-abstract-non-virtual-dtor -Wno-inconsistent-missing-override -Wno-reinterpret-base-class" + } + }, + { + "name": "build-debug-msvc", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-debug", + "generator-msvc", + "compiler-msvc" + ] + }, + { + "name": "build-debug-clang-cl", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-debug", + "generator-ninja", + "compiler-clang-cl" + ] + }, + { + "name": "build-release-msvc", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-release", + "generator-msvc", + "compiler-msvc" + ] + }, + { + "name": "build-release-clang-cl", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-release", + "generator-ninja", + "compiler-clang-cl" + ] + }, + { + "name": "build-relwithdebinfo-msvc", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-relwithdebinfo", + "generator-msvc", + "compiler-msvc" + ] + }, + { + "name": "build-relwithdebinfo-clang-cl", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-relwithdebinfo", + "generator-ninja", + "compiler-clang-cl" + ] + }, + { + "name": "build-minsizerel-msvc", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-minsizerel", + "generator-msvc", + "compiler-msvc" + ] + }, + { + "name": "build-minsizerel-clang-cl", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-minsizerel", + "generator-ninja", + "compiler-clang-cl" + ] + }, + { + "name": "solution-msvc", + "inherits": [ + "common", + "packaging-vcpkg", + "buildtype-minsizerel", + "generator-msvc", + "compiler-msvc" + ] + }, + { + "name": "solution-clang-cl", + "toolset": "ClangCL", "inherits": [ - "vcpkg", - "win64", - "msvc" + "common", + "packaging-vcpkg", + "buildtype-minsizerel", + "generator-msvc", + "compiler-clang-cl" ] } ] diff --git a/README.md b/README.md index f0f9f012..7de92dff 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ https://en.cppreference.com/w/cpp/compiler_support) [![Game version](https://img.shields.io/badge/game%20version-1.7.23-orange)](#use) ## Build Dependencies -+ [CMake](https://cmake.org/) ++ [CMake 3.26+](https://cmake.org/) + Add this to your PATH + [PowerShell](https://github.com/PowerShell/PowerShell/releases/tag/v7.3.6) + [Vcpkg](https://github.com/microsoft/vcpkg) diff --git a/build-clang-cl.bat b/build-clang-cl.bat new file mode 100644 index 00000000..2849e93f --- /dev/null +++ b/build-clang-cl.bat @@ -0,0 +1,4 @@ +echo off +rd /s /q "build" +cmake -B "%cd%/build" -S "%cd%/CommonLibSF" --preset=build-release-clang-cl +cmake --build "%cd%/build" --config Release \ No newline at end of file diff --git a/build-msvc.bat b/build-msvc.bat new file mode 100644 index 00000000..7ad761ae --- /dev/null +++ b/build-msvc.bat @@ -0,0 +1,4 @@ +echo off +rd /s /q "build" +cmake -B "%cd%/build" -S "%cd%/CommonLibSF" --preset=build-release-msvc +cmake --build "%cd%/build" --config Release \ No newline at end of file diff --git a/generate-sln.ps1 b/generate-sln.ps1 deleted file mode 100644 index b78262b1..00000000 --- a/generate-sln.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -Remove-Item $PSScriptRoot/build -Recurse -Force -ErrorAction:SilentlyContinue -Confirm:$False | Out-Null -& cmake -B $PSScriptRoot/build -S $PSScriptRoot/CommonLibSF --preset=REL \ No newline at end of file diff --git a/make-sln-clang-cl.bat b/make-sln-clang-cl.bat new file mode 100644 index 00000000..8a710e77 --- /dev/null +++ b/make-sln-clang-cl.bat @@ -0,0 +1,3 @@ +echo off +rd /s /q "build" +cmake -B "%cd%/build" -S "%cd%/CommonLibSF" --preset=solution-clang-cl \ No newline at end of file diff --git a/make-sln-msvc.bat b/make-sln-msvc.bat new file mode 100644 index 00000000..deeaf508 --- /dev/null +++ b/make-sln-msvc.bat @@ -0,0 +1,3 @@ +echo off +rd /s /q "build" +cmake -B "%cd%/build" -S "%cd%/CommonLibSF" --preset=solution-msvc \ No newline at end of file From 476551588a89811e8f062a0a9869cf15eece8514 Mon Sep 17 00:00:00 2001 From: maintenance Date: Thu, 7 Sep 2023 04:40:27 +0000 Subject: [PATCH 15/15] chore: maintenance --- CommonLibSF/include/SFSE/Impl/PCH.h | 2 +- CommonLibSF/include/SFSE/Logger.h | 46 ++++++++++++++--------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CommonLibSF/include/SFSE/Impl/PCH.h b/CommonLibSF/include/SFSE/Impl/PCH.h index fcbd0918..f8f6faa4 100644 --- a/CommonLibSF/include/SFSE/Impl/PCH.h +++ b/CommonLibSF/include/SFSE/Impl/PCH.h @@ -666,7 +666,7 @@ namespace SFSE } [[noreturn]] inline void report_and_fail(std::string_view a_msg, - std::source_location a_loc = std::source_location::current()) + std::source_location a_loc = std::source_location::current()) { report_and_error(a_msg, true, a_loc); std::unreachable(); diff --git a/CommonLibSF/include/SFSE/Logger.h b/CommonLibSF/include/SFSE/Logger.h index c8259d91..e1914ada 100644 --- a/CommonLibSF/include/SFSE/Logger.h +++ b/CommonLibSF/include/SFSE/Logger.h @@ -1,29 +1,29 @@ #pragma once -#define SFSE_MAKE_SOURCE_LOGGER(a_func, a_type) \ - \ - template \ - struct [[maybe_unused]] a_func \ - { \ - a_func() = delete; \ - \ - explicit a_func( \ - fmt::format_string a_fmt, \ - Args&&... a_args, \ +#define SFSE_MAKE_SOURCE_LOGGER(a_func, a_type) \ + \ + template \ + struct [[maybe_unused]] a_func \ + { \ + a_func() = delete; \ + \ + explicit a_func( \ + fmt::format_string a_fmt, \ + Args&&... a_args, \ std::source_location a_loc = std::source_location::current()) \ - { \ - spdlog::log( \ - spdlog::source_loc{ \ - a_loc.file_name(), \ - static_cast(a_loc.line()), \ - a_loc.function_name() }, \ - spdlog::level::a_type, \ - a_fmt, \ - std::forward(a_args)...); \ - } \ - }; \ - \ - template \ + { \ + spdlog::log( \ + spdlog::source_loc{ \ + a_loc.file_name(), \ + static_cast(a_loc.line()), \ + a_loc.function_name() }, \ + spdlog::level::a_type, \ + a_fmt, \ + std::forward(a_args)...); \ + } \ + }; \ + \ + template \ a_func(fmt::format_string, Args&&...) -> a_func; namespace SFSE::log