├── .clang-format ├── .editorconfig ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── THIRD_PARTY_LICENSES ├── config ├── Project.hpp.in └── Version.rc.in ├── data ├── ExtraFlats.dat └── InheritanceMap.dat ├── lib ├── Core │ ├── Container │ │ ├── Container.hpp │ │ └── Registry.hpp │ ├── Facades │ │ ├── Container.hpp │ │ ├── Hook.hpp │ │ ├── Log.hpp │ │ ├── Runtime.cpp │ │ └── Runtime.hpp │ ├── Foundation │ │ ├── Application.cpp │ │ ├── Application.hpp │ │ ├── Feature.hpp │ │ ├── LocaleProvider.hpp │ │ ├── RuntimeProvider.cpp │ │ └── RuntimeProvider.hpp │ ├── Hooking │ │ ├── Detail.hpp │ │ ├── HookingAgent.cpp │ │ ├── HookingAgent.hpp │ │ ├── HookingDriver.cpp │ │ └── HookingDriver.hpp │ ├── Logging │ │ ├── LoggingAgent.cpp │ │ ├── LoggingAgent.hpp │ │ ├── LoggingDriver.cpp │ │ └── LoggingDriver.hpp │ ├── Memory │ │ ├── AddressResolver.cpp │ │ └── AddressResolver.hpp │ ├── Raw.hpp │ ├── Runtime │ │ ├── HostImage.cpp │ │ ├── HostImage.hpp │ │ ├── ModuleImage.cpp │ │ ├── ModuleImage.hpp │ │ ├── OwnerMutex.cpp │ │ └── OwnerMutex.hpp │ ├── Stl.hpp │ └── Win.hpp ├── Red │ ├── Alias.hpp │ ├── Engine.hpp │ ├── Engine │ │ ├── Framework.hpp │ │ ├── LogChannel.hpp │ │ ├── Macros │ │ │ └── Framework.hpp │ │ └── Mappings.hpp │ ├── Specializations.hpp │ ├── TypeInfo.hpp │ ├── TypeInfo │ │ ├── Common.hpp │ │ ├── Construction.hpp │ │ ├── Definition.hpp │ │ ├── Invocation.hpp │ │ ├── Macros │ │ │ ├── Definition.hpp │ │ │ └── Resolving.hpp │ │ ├── Mappings.hpp │ │ ├── Parameters.hpp │ │ ├── Properties.hpp │ │ ├── Registrar.hpp │ │ └── Resolving.hpp │ ├── Utils.hpp │ └── Utils │ │ ├── Handles.hpp │ │ ├── JobQueues.hpp │ │ └── Resources.hpp └── Support │ ├── MinHook │ ├── MinHookProvider.cpp │ └── MinHookProvider.hpp │ ├── RED4ext │ ├── RED4extProvider.cpp │ └── RED4extProvider.hpp │ ├── RedLib │ ├── RedLibProvider.cpp │ └── RedLibProvider.hpp │ └── Spdlog │ ├── SpdlogProvider.cpp │ └── SpdlogProvider.hpp ├── scripts ├── Module.reds ├── ScriptableTweak.reds ├── StatusEffect.reds ├── TweakDBBatch.reds ├── TweakDBInterface.reds ├── TweakDBManager.reds ├── TweakXL.reds └── VehicleScanner.reds ├── src ├── App │ ├── Application.cpp │ ├── Application.hpp │ ├── Environment.hpp │ ├── Facade.cpp │ ├── Facade.hpp │ ├── Migration.hpp │ ├── Stats │ │ ├── StatService.cpp │ │ └── StatService.hpp │ ├── Tweaks │ │ ├── Batch │ │ │ ├── TweakChangelog.cpp │ │ │ ├── TweakChangelog.hpp │ │ │ ├── TweakChangeset.cpp │ │ │ └── TweakChangeset.hpp │ │ ├── Declarative │ │ │ ├── Red │ │ │ │ ├── RedReader.Types.cpp │ │ │ │ ├── RedReader.Values.cpp │ │ │ │ ├── RedReader.cpp │ │ │ │ └── RedReader.hpp │ │ │ ├── TweakImporter.cpp │ │ │ ├── TweakImporter.hpp │ │ │ ├── TweakReader.cpp │ │ │ ├── TweakReader.hpp │ │ │ └── Yaml │ │ │ │ ├── YamlReader.Legacy.cpp │ │ │ │ ├── YamlReader.Template.cpp │ │ │ │ ├── YamlReader.Values.cpp │ │ │ │ ├── YamlReader.cpp │ │ │ │ └── YamlReader.hpp │ │ ├── Executable │ │ │ ├── Scriptable │ │ │ │ ├── ScriptBatch.cpp │ │ │ │ ├── ScriptBatch.hpp │ │ │ │ ├── ScriptInterface.cpp │ │ │ │ ├── ScriptInterface.hpp │ │ │ │ ├── ScriptManager.cpp │ │ │ │ ├── ScriptManager.hpp │ │ │ │ ├── ScriptUtils.hpp │ │ │ │ └── ScriptableTweak.hpp │ │ │ ├── TweakExecutor.cpp │ │ │ └── TweakExecutor.hpp │ │ ├── Metadata │ │ │ ├── MetadataExporter.cpp │ │ │ ├── MetadataExporter.hpp │ │ │ ├── MetadataImporter.cpp │ │ │ └── MetadataImporter.hpp │ │ ├── TweakContext.hpp │ │ ├── TweakService.cpp │ │ └── TweakService.hpp │ └── Utils │ │ └── Str.hpp ├── Red │ ├── Addresses │ │ └── Library.hpp │ ├── Localization.hpp │ ├── StatsDataSystem.hpp │ ├── TweakDB │ │ ├── Alias.hpp │ │ ├── Buffer.cpp │ │ ├── Buffer.hpp │ │ ├── Manager.cpp │ │ ├── Manager.hpp │ │ ├── Raws.hpp │ │ ├── Reflection.cpp │ │ ├── Reflection.hpp │ │ └── Source │ │ │ ├── Errors.hpp │ │ │ ├── Grammar.hpp │ │ │ ├── Parser.cpp │ │ │ ├── Parser.hpp │ │ │ └── Source.hpp │ └── Value.hpp ├── main.cpp ├── pch.cpp ├── pch.hpp └── rtti.cpp ├── support └── red4ext │ └── TweakXL.hpp ├── tools └── dist │ ├── package-mod.ps1 │ └── steps │ ├── compose-data.ps1 │ ├── compose-licenses.ps1 │ ├── compose-red4ext.ps1 │ ├── compose-redscripts.ps1 │ ├── create-zip-from-stage.ps1 │ ├── get-version.ps1 │ ├── install-from-stage.ps1 │ └── make-tweaks-dir.ps1 └── xmake.lua /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Microsoft 3 | --- 4 | Language: Cpp 5 | 6 | AccessModifierOffset: -4 7 | AlwaysBreakTemplateDeclarations: Yes 8 | BreakConstructorInitializers: BeforeComma 9 | BreakInheritanceList: BeforeComma 10 | KeepEmptyLinesAtTheStartOfBlocks: false 11 | NamespaceIndentation: Inner 12 | PointerAlignment: Left 13 | SpaceAfterTemplateKeyword: false 14 | IndentCaseLabels: false 15 | BreakBeforeBraces: Custom 16 | BraceWrapping: 17 | AfterCaseLabel: true 18 | AfterClass: true 19 | AfterControlStatement: Always 20 | AfterEnum: true 21 | AfterFunction: true 22 | AfterNamespace: true 23 | AfterStruct: true 24 | AfterUnion: true 25 | AfterExternBlock: true 26 | BeforeCatch: true 27 | BeforeElse: true 28 | IndentBraces: false 29 | SplitEmptyFunction: true 30 | SplitEmptyRecord: true 31 | SplitEmptyNamespace: true 32 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.{h,c,cpp,hpp}] 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [*.{yml,yaml}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.json] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vs 3 | /.vscode 4 | /.xmake 5 | /build 6 | /data/*.yaml 7 | /dev 8 | /src/App/Project.hpp 9 | /src/App/Version.rc 10 | CMakeLists.txt 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/RED4ext.SDK"] 2 | path = vendor/RED4ext.SDK 3 | url = https://github.com/psiberx/RED4ext.SDK.git 4 | [submodule "vendor/wil"] 5 | path = vendor/wil 6 | url = https://github.com/microsoft/wil.git 7 | [submodule "vendor/semver"] 8 | path = vendor/semver 9 | url = https://github.com/Neargye/semver.git 10 | [submodule "vendor/pegtl"] 11 | path = vendor/pegtl 12 | url = https://github.com/psiberx/PEGTL.git 13 | [submodule "vendor/nameof"] 14 | path = vendor/nameof 15 | url = https://github.com/Neargye/nameof.git 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pavel Siberx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TweakXL 2 | 3 | TweakXL is a modding tool and a framework to create mods that modify TweakDB, 4 | a proprietary database of REDengine 4, 5 | containing essential information about game entities and behavior. 6 | 7 | - [YAML](https://github.com/psiberx/cp2077-tweak-xl/wiki/YAML-Tweaks) and [RED](https://github.com/psiberx/cp2077-tweak-xl/wiki/RED-Tweaks) formats for manipulating data in a declarative style 8 | - [Script extensions](https://github.com/psiberx/cp2077-tweak-xl/wiki/Script-Extensions) to add complex logic and dynamic changes 9 | - [Hot reloading](https://github.com/psiberx/cp2077-tweak-xl/wiki/Modder-Tools) to speed up development 10 | - Focused on mods compatibility and maintainability 11 | 12 | ## Getting Started 13 | 14 | ### Compatibility 15 | 16 | - Cyberpunk 2077 2.2 17 | - [redscript](https://github.com/jac3km4/redscript) 0.5.27+ 18 | 19 | ### Installation 20 | 21 | 1. Install requirements: 22 | - [RED4ext](https://docs.red4ext.com/getting-started/installing-red4ext) 1.26.1+ 23 | 2. Extract the release archive `TweakXL-x.x.x.zip` into the Cyberpunk 2077 directory. 24 | 25 | ## Documentation 26 | 27 | - [TweakDB](https://github.com/psiberx/cp2077-tweak-xl/wiki/TweakDB) 28 | - [YAML Tweaks](https://github.com/psiberx/cp2077-tweak-xl/wiki/YAML-Tweaks) 29 | - [RED Tweaks](https://github.com/psiberx/cp2077-tweak-xl/wiki/RED-Tweaks) 30 | - [Script Extensions](https://github.com/psiberx/cp2077-tweak-xl/wiki/Script-Extensions) 31 | - [Modder Tools](https://github.com/psiberx/cp2077-tweak-xl/wiki/Modder-Tools) 32 | - [Examples](https://github.com/psiberx/cp2077-tweak-xl/wiki/Examples) 33 | -------------------------------------------------------------------------------- /config/Project.hpp.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Generated by xmake from config/Project.hpp.in 4 | 5 | #define BUILD_SUFFIX ".${MODE}" 6 | #define DEBUG_SUFFIX ".${DEBUG}" 7 | 8 | #include 9 | 10 | namespace App::Project 11 | { 12 | constexpr auto Name = "${NAME}"; 13 | constexpr auto Author = "${AUTHOR}"; 14 | 15 | constexpr auto NameW = L"${NAME}"; 16 | constexpr auto AuthorW = L"${AUTHOR}"; 17 | 18 | constexpr auto Version = semver::from_string_noexcept("${VERSION}").value(); 19 | } 20 | -------------------------------------------------------------------------------- /config/Version.rc.in: -------------------------------------------------------------------------------- 1 | #define VER_PRODUCTVERSION ${VERSION_MAJOR},${VERSION_MINOR},${VERSION_ALTER},0 2 | #define VER_FILEVERSION ${VERSION_MAJOR},${VERSION_MINOR},${VERSION_ALTER},${VERSION_BUILD} 3 | 4 | #define VER_PRODUCTNAME_STR "${NAME}\0" 5 | #define VER_PRODUCTVERSION_STR "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_ALTER}\0" 6 | #define VER_FILEVERSION_STR "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_ALTER}.${VERSION_BUILD}\0" 7 | 8 | 1 VERSIONINFO 9 | FILEVERSION VER_FILEVERSION 10 | PRODUCTVERSION VER_PRODUCTVERSION 11 | FILEFLAGSMASK 0x17L 12 | FILEFLAGS 0x0L 13 | FILEOS 0x4L 14 | FILETYPE 0x2L 15 | FILESUBTYPE 0x0L 16 | BEGIN 17 | BLOCK "StringFileInfo" 18 | BEGIN 19 | BLOCK "040904b0" 20 | BEGIN 21 | VALUE "CompanyName", "\0" 22 | VALUE "FileDescription", "\0" 23 | VALUE "FileVersion", VER_FILEVERSION_STR 24 | VALUE "InternalName", "\0" 25 | VALUE "LegalCopyright", "\0" 26 | VALUE "LegalTrademarks1", "\0" 27 | VALUE "LegalTrademarks2", "\0" 28 | VALUE "OriginalFilename", "\0" 29 | VALUE "ProductName", VER_PRODUCTNAME_STR 30 | VALUE "ProductVersion", VER_PRODUCTVERSION_STR 31 | END 32 | END 33 | BLOCK "VarFileInfo" 34 | BEGIN 35 | VALUE "Translation", 0x409, 1200 36 | END 37 | END 38 | -------------------------------------------------------------------------------- /data/ExtraFlats.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-tweak-xl/e6c99aa4cecc8666e8c11fb91f7baa33decc4e2f/data/ExtraFlats.dat -------------------------------------------------------------------------------- /data/InheritanceMap.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-tweak-xl/e6c99aa4cecc8666e8c11fb91f7baa33decc4e2f/data/InheritanceMap.dat -------------------------------------------------------------------------------- /lib/Core/Container/Container.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Stl.hpp" 4 | 5 | namespace Core 6 | { 7 | class Container 8 | { 9 | public: 10 | template 11 | inline static void Set(const Core::SharedPtr& aInstance) 12 | { 13 | Resolver::Assign(aInstance); 14 | } 15 | 16 | template 17 | inline static Core::SharedPtr Get() 18 | { 19 | return Resolver::Retrieve().lock(); 20 | } 21 | 22 | template 23 | inline static bool Has() 24 | { 25 | return !Resolver::Retrieve().expired(); 26 | } 27 | 28 | private: 29 | template 30 | struct Resolver 31 | { 32 | inline static void Assign(const Core::SharedPtr& aInstance) 33 | { 34 | s_instance = aInstance; 35 | } 36 | 37 | inline static Core::WeakPtr& Retrieve() 38 | { 39 | return s_instance; 40 | } 41 | 42 | inline static Core::WeakPtr s_instance; 43 | }; 44 | }; 45 | } -------------------------------------------------------------------------------- /lib/Core/Container/Registry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Container/Container.hpp" 4 | #include "Core/Stl.hpp" 5 | 6 | namespace Core 7 | { 8 | template 9 | class RegistryProxy; 10 | 11 | template 12 | class Registry 13 | { 14 | public: 15 | template 16 | requires std::is_base_of_v 17 | T& Register(Args&&... aArgs) 18 | { 19 | auto registrable = Core::MakeShared(std::forward(aArgs)...); 20 | m_registered.push_back(registrable); 21 | Container::Set(registrable); 22 | 23 | if constexpr (std::is_base_of_v, T>) 24 | { 25 | registrable->SetParent(*this); 26 | } 27 | 28 | OnRegistered(registrable); 29 | 30 | return *registrable.get(); 31 | } 32 | 33 | protected: 34 | using TraverseFunc = void (*)(const Core::SharedPtr&); 35 | 36 | void ForEach(TraverseFunc aTraverse) const 37 | { 38 | std::for_each(m_registered.begin(), m_registered.end(), aTraverse); 39 | } 40 | 41 | const Core::Vector>& GetRegistered() const 42 | { 43 | return m_registered; 44 | } 45 | 46 | template 47 | inline static Core::SharedPtr Resolve() 48 | { 49 | return Container::Get(); 50 | } 51 | 52 | template 53 | inline static bool Resolvable() 54 | { 55 | return Container::Get(); 56 | } 57 | 58 | virtual void OnRegistered(const Core::SharedPtr& aRegistred) {} 59 | 60 | private: 61 | Core::Vector> m_registered; 62 | }; 63 | 64 | template 65 | class RegistryProxy 66 | { 67 | protected: 68 | template 69 | requires std::is_base_of_v 70 | T& Register(Args&&... aArgs) 71 | { 72 | assert(m_parent); 73 | return m_parent->template Register(std::forward(aArgs)...); 74 | } 75 | 76 | template 77 | requires std::is_base_of_v 78 | T& Register(Core::SharedPtr aFeature) 79 | { 80 | assert(m_parent); 81 | return m_parent->Register(std::forward(aFeature)); 82 | } 83 | 84 | private: 85 | friend class Registry; 86 | 87 | void SetParent(Registry& aParent) 88 | { 89 | m_parent = &aParent; 90 | } 91 | 92 | Registry* m_parent{ nullptr }; 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /lib/Core/Facades/Container.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Container/Container.hpp" 4 | 5 | namespace Core 6 | { 7 | template 8 | inline Core::SharedPtr Resolve() 9 | { 10 | return Container::Get(); 11 | } 12 | 13 | template 14 | inline bool Resolvable() 15 | { 16 | return Container::Get(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/Core/Facades/Hook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Hooking/Detail.hpp" 4 | #include "Core/Hooking/HookingDriver.hpp" 5 | 6 | namespace Core::Hook 7 | { 8 | template 9 | requires Detail::HookFlowTraits::IsCompatible 10 | inline auto Attach(TCallback&& aCallback, typename TTarget::Callable* aOriginal = nullptr) 11 | { 12 | using Wrapper = Detail::HookWrapper; 13 | using Handler = Detail::HookHandler; 14 | return Handler::Attach(HookingDriver::GetDefault(), Wrapper(std::forward(aCallback)), aOriginal); 15 | } 16 | 17 | template 18 | requires Detail::HookFlowTraits::IsCompatible 19 | inline auto Before(TCallback&& aCallback) 20 | { 21 | using Wrapper = Detail::HookWrapper; 22 | using Handler = Detail::HookHandler; 23 | return Handler::Attach(HookingDriver::GetDefault(), Wrapper(std::forward(aCallback))); 24 | } 25 | 26 | template 27 | requires Detail::HookFlowTraits::IsCompatible 28 | inline auto OnceBefore(TCallback&& aCallback) 29 | { 30 | return Before(std::forward(aCallback)); 31 | } 32 | 33 | template 34 | requires Detail::HookFlowTraits::IsCompatible 35 | inline auto After(TCallback&& aCallback) 36 | { 37 | using Wrapper = Detail::HookWrapper; 38 | using Handler = Detail::HookHandler; 39 | return Handler::Attach(HookingDriver::GetDefault(), Wrapper(std::forward(aCallback))); 40 | } 41 | 42 | template 43 | requires Detail::HookFlowTraits::IsCompatible 44 | inline auto OnceAfter(TCallback&& aCallback) 45 | { 46 | return After(std::forward(aCallback)); 47 | } 48 | 49 | template 50 | requires Detail::HookFlowTraits::IsCompatible 51 | inline auto Wrap(TCallback&& aCallback) 52 | { 53 | using Wrapper = Detail::HookWrapper; 54 | using Handler = Detail::HookHandler; 55 | return Handler::Attach(HookingDriver::GetDefault(), Wrapper(std::forward(aCallback))); 56 | } 57 | 58 | template 59 | requires Detail::HookFlowTraits::IsCompatible 60 | inline auto WrapOnce(TCallback&& aCallback) 61 | { 62 | return Wrap(std::forward(aCallback)); 63 | } 64 | 65 | template 66 | inline auto Detach() 67 | { 68 | using Instance = Detail::HookInstance; 69 | return Instance::Detach(); 70 | } 71 | 72 | template 73 | requires Detail::HookFlowTraits::IsCompatible 74 | inline static auto Attach(TCallback&& aCallback, typename decltype(TTarget)::Callable* aOriginal = nullptr) 75 | { 76 | return Attach(std::forward(aCallback), aOriginal); 77 | } 78 | 79 | template 80 | requires Detail::HookFlowTraits::IsCompatible 81 | inline static auto Before(TCallback&& aCallback) 82 | { 83 | return Before(std::forward(aCallback)); 84 | } 85 | 86 | template 87 | requires Detail::HookFlowTraits::IsCompatible 88 | inline static auto OnceBefore(TCallback&& aCallback) 89 | { 90 | return Before(std::forward(aCallback)); 91 | } 92 | 93 | template 94 | requires Detail::HookFlowTraits::IsCompatible 95 | inline static auto After(TCallback&& aCallback) 96 | { 97 | return After(std::forward(aCallback)); 98 | } 99 | 100 | template 101 | requires Detail::HookFlowTraits::IsCompatible 102 | inline static auto OnceAfter(TCallback&& aCallback) 103 | { 104 | return After(std::forward(aCallback)); 105 | } 106 | 107 | template 108 | requires Detail::HookFlowTraits::IsCompatible 109 | inline static auto Wrap(TCallback&& aCallback) 110 | { 111 | return Wrap(std::forward(aCallback)); 112 | } 113 | 114 | template 115 | requires Detail::HookFlowTraits::IsCompatible 116 | inline static auto WrapOnce(TCallback&& aCallback) 117 | { 118 | return Wrap(std::forward(aCallback)); 119 | } 120 | 121 | template 122 | inline static auto Detach() 123 | { 124 | return Detach(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/Core/Facades/Log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Logging/LoggingDriver.hpp" 4 | 5 | namespace Core::Log 6 | { 7 | inline void Info(const std::string_view& aMessage) 8 | { 9 | LoggingDriver::GetDefault().LogInfo(aMessage); 10 | } 11 | 12 | inline void Warning(const std::string_view& aMessage) 13 | { 14 | LoggingDriver::GetDefault().LogWarning(aMessage); 15 | } 16 | 17 | inline void Error(const std::string_view& aMessage) 18 | { 19 | LoggingDriver::GetDefault().LogError(aMessage); 20 | } 21 | 22 | inline void Debug(const std::string_view& aMessage) 23 | { 24 | LoggingDriver::GetDefault().LogDebug(aMessage); 25 | } 26 | 27 | template 28 | constexpr void Info(std::format_string aFormat, Args&&... aArgs) 29 | { 30 | LoggingDriver::GetDefault().LogInfo(std::format(aFormat, std::forward(aArgs)...)); 31 | } 32 | 33 | template 34 | constexpr void Warning(std::format_string aFormat, Args&&... aArgs) 35 | { 36 | LoggingDriver::GetDefault().LogWarning(std::format(aFormat, std::forward(aArgs)...)); 37 | } 38 | 39 | template 40 | constexpr void Error(std::format_string aFormat, Args&&... aArgs) 41 | { 42 | LoggingDriver::GetDefault().LogError(std::format(aFormat, std::forward(aArgs)...)); 43 | } 44 | 45 | template 46 | constexpr void Debug(std::format_string aFormat, Args&&... aArgs) 47 | { 48 | LoggingDriver::GetDefault().LogDebug(std::format(aFormat, std::forward(aArgs)...)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/Core/Facades/Runtime.cpp: -------------------------------------------------------------------------------- 1 | #include "Runtime.hpp" 2 | #include "Core/Stl.hpp" 3 | 4 | namespace 5 | { 6 | Core::UniquePtr s_host; 7 | Core::UniquePtr s_module; 8 | } 9 | 10 | void Core::Runtime::Initialize(const Core::HostImage& aHost, const Core::ModuleImage& aModule) 11 | { 12 | s_host = Core::MakeUnique(aHost); 13 | s_module = Core::MakeUnique(aModule); 14 | } 15 | 16 | Core::HostImage* Core::Runtime::GetHost() 17 | { 18 | return s_host.get(); 19 | } 20 | 21 | Core::ModuleImage* Core::Runtime::GetModule() 22 | { 23 | return s_module.get(); 24 | } 25 | 26 | uintptr_t Core::Runtime::GetImageBase() 27 | { 28 | assert(s_host); 29 | return s_host->GetBase(); 30 | } 31 | 32 | std::filesystem::path Core::Runtime::GetImagePath() 33 | { 34 | assert(s_host); 35 | return s_host->GetPath(); 36 | } 37 | 38 | std::filesystem::path Core::Runtime::GetRootDir() 39 | { 40 | assert(s_host); 41 | return s_host->GetRootDir(); 42 | } 43 | 44 | std::filesystem::path Core::Runtime::GetModulePath() 45 | { 46 | assert(s_module); 47 | return s_module->GetPath(); 48 | } 49 | 50 | std::filesystem::path Core::Runtime::GetModuleDir() 51 | { 52 | assert(s_module); 53 | return s_module->GetDir(); 54 | } 55 | 56 | std::string Core::Runtime::GetModuleName() 57 | { 58 | assert(s_module); 59 | return s_module->GetName(); 60 | } 61 | 62 | bool Core::Runtime::IsASI() 63 | { 64 | assert(s_module); 65 | return s_module->IsASI(); 66 | } 67 | 68 | bool Core::Runtime::IsASI(HMODULE aHandle) 69 | { 70 | return Core::ModuleImage(aHandle).IsASI(); 71 | } 72 | 73 | bool Core::Runtime::IsEXE(std::wstring_view aName) 74 | { 75 | if (s_host) 76 | return s_host->GetPath().filename() == aName; 77 | 78 | return Core::HostImage().GetPath().filename() == aName; 79 | } 80 | -------------------------------------------------------------------------------- /lib/Core/Facades/Runtime.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Runtime/HostImage.hpp" 4 | #include "Core/Runtime/ModuleImage.hpp" 5 | 6 | namespace Core::Runtime 7 | { 8 | void Initialize(const Core::HostImage& aHost, const Core::ModuleImage& aModule); 9 | 10 | HostImage* GetHost(); 11 | ModuleImage* GetModule(); 12 | 13 | [[nodiscard]] uintptr_t GetImageBase(); 14 | [[nodiscard]] std::filesystem::path GetImagePath(); 15 | [[nodiscard]] std::filesystem::path GetRootDir(); 16 | [[nodiscard]] std::filesystem::path GetModulePath(); 17 | [[nodiscard]] std::filesystem::path GetModuleDir(); 18 | [[nodiscard]] std::string GetModuleName(); 19 | [[nodiscard]] bool IsASI(); 20 | [[nodiscard]] bool IsASI(HMODULE aHandle); 21 | [[nodiscard]] bool IsEXE(std::wstring_view aName); 22 | } 23 | -------------------------------------------------------------------------------- /lib/Core/Foundation/Application.cpp: -------------------------------------------------------------------------------- 1 | #include "Application.hpp" 2 | 3 | void Core::Application::Bootstrap() 4 | { 5 | if (m_booted) 6 | return; 7 | 8 | if (!s_discoveryCallbacks.empty()) 9 | { 10 | for (const auto& callback : s_discoveryCallbacks) 11 | { 12 | callback(*this); 13 | } 14 | s_discoveryCallbacks.clear(); 15 | } 16 | 17 | m_booted = true; 18 | 19 | OnStarting(); 20 | 21 | for (const auto& feature : GetRegistered()) 22 | { 23 | feature->OnBootstrap(); 24 | } 25 | 26 | OnStarted(); 27 | } 28 | 29 | void Core::Application::Shutdown() 30 | { 31 | if (!m_booted) 32 | return; 33 | 34 | OnStopping(); 35 | 36 | for (const auto& feature : GetRegistered()) 37 | { 38 | feature->OnShutdown(); 39 | } 40 | 41 | OnStopped(); 42 | 43 | m_booted = false; 44 | } 45 | 46 | void Core::Application::OnRegistered(const SharedPtr& aFeature) 47 | { 48 | aFeature->OnRegister(); 49 | 50 | if (m_booted) 51 | { 52 | aFeature->OnBootstrap(); 53 | } 54 | } 55 | 56 | bool Core::Application::Discover(AutoDiscoveryCallback aCallback) 57 | { 58 | s_discoveryCallbacks.push_back(aCallback); 59 | return true; 60 | } 61 | -------------------------------------------------------------------------------- /lib/Core/Foundation/Application.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Container/Registry.hpp" 4 | #include "Core/Foundation/Feature.hpp" 5 | #include "Core/Stl.hpp" 6 | 7 | namespace Core 8 | { 9 | using AutoDiscoveryCallback = void(*)(Application&); 10 | 11 | class Application : public Registry 12 | { 13 | public: 14 | template 15 | Feature::Defer Register(Args&&... aArgs) 16 | { 17 | return Registry::Register(std::forward(aArgs)...); 18 | } 19 | 20 | void Bootstrap(); 21 | void Shutdown(); 22 | 23 | static bool Discover(AutoDiscoveryCallback aCallback); 24 | 25 | protected: 26 | void OnRegistered(const SharedPtr& aFeature) override; 27 | 28 | virtual void OnStarting() {}; 29 | virtual void OnStarted() {}; 30 | virtual void OnStopping() {}; 31 | virtual void OnStopped() {}; 32 | 33 | private: 34 | bool m_booted = false; 35 | 36 | inline static Vector s_discoveryCallbacks; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /lib/Core/Foundation/Feature.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Container/Registry.hpp" 4 | 5 | namespace Core 6 | { 7 | class Application; 8 | 9 | class Feature : public RegistryProxy 10 | { 11 | public: 12 | Feature() = default; 13 | virtual ~Feature() = default; 14 | 15 | Feature(Feature&& aOther) = delete; 16 | Feature(const Feature& aOther) = delete; 17 | 18 | protected: 19 | friend class Application; 20 | friend class Registry; 21 | 22 | virtual void OnRegister() {}; 23 | virtual void OnInitialize() {}; 24 | virtual void OnBootstrap() {}; 25 | virtual void OnShutdown() {}; 26 | 27 | template 28 | requires std::is_base_of_v 29 | class Defer 30 | { 31 | public: 32 | inline Defer(T& aTarget) 33 | : m_instance(aTarget) 34 | { 35 | ++m_instance.m_deferChain; 36 | } 37 | 38 | inline Defer(T* aTarget) 39 | : m_instance(*aTarget) 40 | { 41 | ++m_instance.m_deferChain; 42 | } 43 | 44 | inline ~Defer() 45 | { 46 | if (--m_instance.m_deferChain == 0) 47 | { 48 | static_cast(m_instance).OnInitialize(); 49 | } 50 | } 51 | 52 | [[nodiscard]] inline T* operator->() const 53 | { 54 | return &m_instance; 55 | } 56 | 57 | private: 58 | T& m_instance; 59 | }; 60 | 61 | private: 62 | uint8_t m_deferChain = 0; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /lib/Core/Foundation/LocaleProvider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Feature.hpp" 4 | 5 | namespace Core 6 | { 7 | class LocaleProvider : public Feature 8 | { 9 | public: 10 | LocaleProvider(const char* aLocale = "en_US.UTF-8") 11 | { 12 | std::setlocale(LC_ALL, aLocale); 13 | } 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /lib/Core/Foundation/RuntimeProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "RuntimeProvider.hpp" 2 | #include "Core/Facades/Runtime.hpp" 3 | 4 | Core::RuntimeProvider::RuntimeProvider(HMODULE aHandle) noexcept 5 | : m_handle(aHandle) 6 | , m_basePathDepth(0) 7 | { 8 | } 9 | 10 | void Core::RuntimeProvider::OnInitialize() 11 | { 12 | Runtime::Initialize(HostImage(m_basePathDepth), ModuleImage(m_handle)); 13 | } 14 | -------------------------------------------------------------------------------- /lib/Core/Foundation/RuntimeProvider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Feature.hpp" 4 | #include "Core/Win.hpp" 5 | 6 | namespace Core 7 | { 8 | class RuntimeProvider : public Feature 9 | { 10 | public: 11 | explicit RuntimeProvider(HMODULE aHandle) noexcept; 12 | 13 | auto SetBaseImagePathDepth(int aDepth) noexcept 14 | { 15 | m_basePathDepth = aDepth; 16 | return Defer(this); 17 | } 18 | 19 | protected: 20 | void OnInitialize() override; 21 | 22 | HMODULE m_handle; 23 | int m_basePathDepth; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /lib/Core/Hooking/HookingAgent.cpp: -------------------------------------------------------------------------------- 1 | #include "HookingAgent.hpp" 2 | 3 | #include 4 | 5 | namespace 6 | { 7 | Core::HookingDriver* s_driver; 8 | } 9 | 10 | void Core::HookingAgent::SetHookingDriver(Core::HookingDriver& aDriver) 11 | { 12 | s_driver = &aDriver; 13 | } 14 | 15 | Core::HookingDriver& Core::HookingAgent::GetHookingDriver() 16 | { 17 | assert(s_driver); 18 | return *s_driver; 19 | } 20 | -------------------------------------------------------------------------------- /lib/Core/Hooking/HookingDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "HookingDriver.hpp" 2 | #include "HookingAgent.hpp" 3 | 4 | #include 5 | 6 | namespace 7 | { 8 | Core::HookingDriver* s_default; 9 | } 10 | 11 | void Core::HookingDriver::SetDefault(Core::HookingDriver& aDriver) 12 | { 13 | s_default = &aDriver; 14 | 15 | HookingAgent::SetHookingDriver(aDriver); 16 | } 17 | 18 | Core::HookingDriver& Core::HookingDriver::GetDefault() 19 | { 20 | assert(s_default); 21 | return *s_default; 22 | } 23 | -------------------------------------------------------------------------------- /lib/Core/Hooking/HookingDriver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Core 4 | { 5 | class HookingDriver 6 | { 7 | public: 8 | virtual bool HookAttach(uintptr_t aAddress, void* aCallback) = 0; 9 | virtual bool HookAttach(uintptr_t aAddress, void* aCallback, void** aOriginal) = 0; 10 | virtual bool HookDetach(uintptr_t aAddress) = 0; 11 | 12 | static void SetDefault(HookingDriver& aDriver); 13 | static HookingDriver& GetDefault(); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /lib/Core/Logging/LoggingAgent.cpp: -------------------------------------------------------------------------------- 1 | #include "LoggingAgent.hpp" 2 | 3 | #include 4 | 5 | namespace 6 | { 7 | Core::LoggingDriver* s_driver; 8 | } 9 | 10 | void Core::LoggingAgent::SetDriver(Core::LoggingDriver& aDriver) 11 | { 12 | s_driver = &aDriver; 13 | } 14 | 15 | Core::LoggingDriver& Core::LoggingAgent::GetLoggingDriver() 16 | { 17 | assert(s_driver); 18 | return *s_driver; 19 | } 20 | 21 | void Core::LoggingAgent::LogFlush() 22 | { 23 | s_driver->LogFlush(); 24 | } 25 | -------------------------------------------------------------------------------- /lib/Core/Logging/LoggingAgent.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "LoggingDriver.hpp" 4 | 5 | namespace Core 6 | { 7 | class LoggingAgent 8 | { 9 | protected: 10 | inline static void LogInfo(const char* aMessage) 11 | { 12 | GetLoggingDriver().LogInfo(aMessage); 13 | } 14 | 15 | inline static void LogWarning(const char* aMessage) 16 | { 17 | GetLoggingDriver().LogWarning(aMessage); 18 | } 19 | 20 | inline static void LogError(const char* aMessage) 21 | { 22 | GetLoggingDriver().LogError(aMessage); 23 | } 24 | 25 | inline static void LogDebug(const char* aMessage) 26 | { 27 | GetLoggingDriver().LogDebug(aMessage); 28 | } 29 | 30 | template 31 | inline static constexpr void LogInfo(std::format_string aFormat, Args&&... aArgs) 32 | { 33 | GetLoggingDriver().LogInfo(aFormat, std::forward(aArgs)...); 34 | } 35 | 36 | template 37 | inline static constexpr void LogWarning(std::format_string aFormat, Args&&... aArgs) 38 | { 39 | GetLoggingDriver().LogWarning(std::format(aFormat, std::forward(aArgs)...)); 40 | } 41 | 42 | template 43 | inline static constexpr void LogError(std::format_string aFormat, Args&&... aArgs) 44 | { 45 | GetLoggingDriver().LogError(std::format(aFormat, std::forward(aArgs)...)); 46 | } 47 | 48 | template 49 | inline static constexpr void LogDebug(std::format_string aFormat, Args&&... aArgs) 50 | { 51 | GetLoggingDriver().LogDebug(std::format(aFormat, std::forward(aArgs)...)); 52 | } 53 | 54 | static void LogFlush(); 55 | 56 | static LoggingDriver& GetLoggingDriver(); 57 | 58 | private: 59 | friend LoggingDriver; 60 | 61 | static void SetDriver(LoggingDriver& aDriver); 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /lib/Core/Logging/LoggingDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "LoggingDriver.hpp" 2 | #include "LoggingAgent.hpp" 3 | 4 | #include 5 | 6 | namespace 7 | { 8 | Core::LoggingDriver* s_default; 9 | } 10 | 11 | void Core::LoggingDriver::SetDefault(LoggingDriver& aDriver) 12 | { 13 | s_default = &aDriver; 14 | 15 | LoggingAgent::SetDriver(aDriver); 16 | } 17 | 18 | Core::LoggingDriver& Core::LoggingDriver::GetDefault() 19 | { 20 | assert(s_default); 21 | return *s_default; 22 | } 23 | -------------------------------------------------------------------------------- /lib/Core/Logging/LoggingDriver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Core 4 | { 5 | class LoggingDriver 6 | { 7 | public: 8 | virtual void LogInfo(const std::string_view& aMessage) = 0; 9 | virtual void LogWarning(const std::string_view& aMessage) = 0; 10 | virtual void LogError(const std::string_view& aMessage) = 0; 11 | virtual void LogDebug(const std::string_view& aMessage) = 0; 12 | virtual void LogFlush() = 0; 13 | 14 | template 15 | constexpr void LogInfo(std::format_string aFormat, Args&&... aArgs) 16 | { 17 | LogInfo(std::format(aFormat, std::forward(aArgs)...)); 18 | } 19 | 20 | template 21 | constexpr void LogWarning(std::format_string aFormat, Args&&... aArgs) 22 | { 23 | LogWarning(std::format(aFormat, std::forward(aArgs)...)); 24 | } 25 | 26 | template 27 | constexpr void LogError(std::format_string aFormat, Args&&... aArgs) 28 | { 29 | LogError(std::format(aFormat, std::forward(aArgs)...)); 30 | } 31 | 32 | template 33 | constexpr void LogDebug(std::format_string aFormat, Args&&... aArgs) 34 | { 35 | LogDebug(std::format(aFormat, std::forward(aArgs)...)); 36 | } 37 | 38 | static void SetDefault(LoggingDriver& aDriver); 39 | static LoggingDriver& GetDefault(); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /lib/Core/Memory/AddressResolver.cpp: -------------------------------------------------------------------------------- 1 | #include "AddressResolver.hpp" 2 | 3 | #include 4 | 5 | namespace 6 | { 7 | Core::AddressResolver* s_default; 8 | } 9 | 10 | void Core::AddressResolver::SetDefault(Core::AddressResolver& aResolver) 11 | { 12 | s_default = &aResolver; 13 | } 14 | 15 | Core::AddressResolver& Core::AddressResolver::GetDefault() 16 | { 17 | assert(s_default); 18 | return *s_default; 19 | } 20 | -------------------------------------------------------------------------------- /lib/Core/Memory/AddressResolver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Core 4 | { 5 | class AddressResolver 6 | { 7 | public: 8 | virtual uintptr_t ResolveAddress(uint32_t aAddressID) = 0; 9 | 10 | static void SetDefault(AddressResolver& aResolver); 11 | static AddressResolver& GetDefault(); 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /lib/Core/Runtime/HostImage.cpp: -------------------------------------------------------------------------------- 1 | #include "HostImage.hpp" 2 | #include "Core/Win.hpp" 3 | 4 | Core::HostImage::HostImage(int aExePathDepth) 5 | { 6 | const auto handle = GetModuleHandleW(nullptr); 7 | 8 | m_base = reinterpret_cast(handle); 9 | 10 | std::wstring filePath; 11 | wil::GetModuleFileNameW(handle, filePath); 12 | 13 | m_exe = filePath; 14 | m_root = m_exe.parent_path(); 15 | 16 | while (--aExePathDepth >= 0) 17 | m_root = m_root.parent_path(); 18 | 19 | TryResolveVersion(filePath); 20 | } 21 | 22 | uintptr_t Core::HostImage::GetBase() const 23 | { 24 | return m_base; 25 | } 26 | 27 | std::filesystem::path Core::HostImage::GetPath() const 28 | { 29 | return m_exe; 30 | } 31 | 32 | std::string Core::HostImage::GetName() const 33 | { 34 | return m_exe.stem().string(); 35 | } 36 | 37 | std::filesystem::path Core::HostImage::GetRootDir() const 38 | { 39 | return m_root; 40 | } 41 | 42 | const Core::FileVer& Core::HostImage::GetFileVer() const 43 | { 44 | return m_fileVer; 45 | } 46 | 47 | const Core::SemvVer& Core::HostImage::GetProductVer() const 48 | { 49 | return m_productVer; 50 | } 51 | 52 | bool Core::HostImage::TryResolveVersion(const std::wstring& filePath) 53 | { 54 | auto size = GetFileVersionInfoSizeW(filePath.c_str(), nullptr); 55 | if (!size) 56 | return false; 57 | 58 | std::unique_ptr data(new (std::nothrow) uint8_t[size]()); 59 | if (!data) 60 | return false; 61 | 62 | if (!GetFileVersionInfoW(filePath.c_str(), 0, size, data.get())) 63 | return false; 64 | 65 | VS_FIXEDFILEINFO* fileInfo = nullptr; 66 | UINT fileInfoBytes; 67 | 68 | if (!VerQueryValueW(data.get(), L"\\", reinterpret_cast(&fileInfo), &fileInfoBytes)) 69 | return false; 70 | 71 | constexpr auto signature = 0xFEEF04BD; 72 | if (fileInfo->dwSignature != signature) 73 | return false; 74 | 75 | m_fileVer.major = (fileInfo->dwFileVersionMS >> 16) & 0xFF; 76 | m_fileVer.minor = fileInfo->dwFileVersionMS & 0xFFFF; 77 | m_fileVer.build = (fileInfo->dwFileVersionLS >> 16) & 0xFFFF; 78 | m_fileVer.revision = fileInfo->dwFileVersionLS & 0xFFFF; 79 | 80 | m_productVer.major = (fileInfo->dwProductVersionMS >> 16) & 0xFF; 81 | m_productVer.minor = fileInfo->dwProductVersionMS & 0xFFFF; 82 | m_productVer.patch = (fileInfo->dwProductVersionLS >> 16) & 0xFFFF; 83 | 84 | return true; 85 | } 86 | -------------------------------------------------------------------------------- /lib/Core/Runtime/HostImage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Core 4 | { 5 | struct FileVer 6 | { 7 | uint16_t major; 8 | uint16_t minor; 9 | uint16_t build; 10 | uint16_t revision; 11 | }; 12 | 13 | struct SemvVer 14 | { 15 | uint16_t major; 16 | uint16_t minor; 17 | uint32_t patch; 18 | }; 19 | 20 | class HostImage 21 | { 22 | public: 23 | explicit HostImage(int32_t aExePathDepth = 0); 24 | ~HostImage() = default; 25 | 26 | [[nodiscard]] uintptr_t GetBase() const; 27 | [[nodiscard]] std::filesystem::path GetPath() const; 28 | [[nodiscard]] std::string GetName() const; 29 | [[nodiscard]] std::filesystem::path GetRootDir() const; 30 | 31 | [[nodiscard]] const FileVer& GetFileVer() const; 32 | [[nodiscard]] const SemvVer& GetProductVer() const; 33 | 34 | private: 35 | bool TryResolveVersion(const std::wstring& aFilePath); 36 | 37 | uintptr_t m_base; 38 | std::filesystem::path m_exe; 39 | std::filesystem::path m_root; 40 | FileVer m_fileVer{}; 41 | SemvVer m_productVer{}; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /lib/Core/Runtime/ModuleImage.cpp: -------------------------------------------------------------------------------- 1 | #include "ModuleImage.hpp" 2 | 3 | Core::ModuleImage::ModuleImage(HMODULE aHandle) 4 | { 5 | std::wstring filePath; 6 | wil::GetModuleFileNameW(aHandle, filePath); 7 | 8 | m_path = filePath; 9 | } 10 | 11 | std::filesystem::path Core::ModuleImage::GetPath() const 12 | { 13 | return m_path; 14 | } 15 | 16 | std::filesystem::path Core::ModuleImage::GetDir() const 17 | { 18 | return m_path.parent_path(); 19 | } 20 | 21 | std::string Core::ModuleImage::GetName() const 22 | { 23 | return m_path.stem().string(); 24 | } 25 | 26 | bool Core::ModuleImage::IsASI() const 27 | { 28 | return m_path.extension() == L".asi"; 29 | } 30 | -------------------------------------------------------------------------------- /lib/Core/Runtime/ModuleImage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Win.hpp" 4 | 5 | namespace Core 6 | { 7 | class ModuleImage 8 | { 9 | public: 10 | explicit ModuleImage(HMODULE aHandle); 11 | ~ModuleImage() = default; 12 | 13 | [[nodiscard]] std::filesystem::path GetPath() const; 14 | [[nodiscard]] std::filesystem::path GetDir() const; 15 | [[nodiscard]] std::string GetName() const; 16 | [[nodiscard]] bool IsASI() const; 17 | 18 | private: 19 | std::filesystem::path m_path; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /lib/Core/Runtime/OwnerMutex.cpp: -------------------------------------------------------------------------------- 1 | #include "OwnerMutex.hpp" 2 | 3 | Core::OwnerMutex::OwnerMutex(std::string_view aName) 4 | : m_aname(aName) 5 | , m_mutex(nullptr) 6 | { 7 | } 8 | 9 | Core::OwnerMutex::OwnerMutex(std::wstring_view aName) 10 | : m_wname(aName) 11 | , m_mutex(nullptr) 12 | { 13 | } 14 | 15 | Core::OwnerMutex::~OwnerMutex() 16 | { 17 | Release(); 18 | } 19 | 20 | bool Core::OwnerMutex::Obtain() 21 | { 22 | const auto mutex = !m_wname.empty() 23 | ? CreateMutexW(NULL, TRUE, m_wname.data()) 24 | : CreateMutexA(NULL, TRUE, m_aname.data()); 25 | 26 | if (!mutex) 27 | return false; 28 | 29 | if (GetLastError() == ERROR_ALREADY_EXISTS) 30 | { 31 | ReleaseMutex(mutex); 32 | return false; 33 | } 34 | 35 | m_mutex = mutex; 36 | 37 | return true; 38 | } 39 | 40 | bool Core::OwnerMutex::Release() 41 | { 42 | if (!m_mutex) 43 | return false; 44 | 45 | ReleaseMutex(m_mutex); 46 | m_mutex = nullptr; 47 | 48 | return true; 49 | } 50 | 51 | bool Core::OwnerMutex::IsOwner() 52 | { 53 | return m_mutex; 54 | } 55 | -------------------------------------------------------------------------------- /lib/Core/Runtime/OwnerMutex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Win.hpp" 4 | 5 | namespace Core 6 | { 7 | class OwnerMutex 8 | { 9 | public: 10 | explicit OwnerMutex(std::string_view aName); 11 | explicit OwnerMutex(std::wstring_view aName); 12 | ~OwnerMutex(); 13 | 14 | bool Obtain(); 15 | bool Release(); 16 | bool IsOwner(); 17 | 18 | private: 19 | std::string_view m_aname; 20 | std::wstring_view m_wname; 21 | HANDLE m_mutex; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /lib/Core/Stl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Core 8 | { 9 | // Pretty much the same as TiltedCore with StlAllocator, 10 | // but unique ptr allows implicit casting to the base. 11 | 12 | namespace Detail 13 | { 14 | template 15 | struct UniqueDeleter 16 | { 17 | constexpr UniqueDeleter() noexcept = default; 18 | 19 | template 20 | requires std::is_base_of_v 21 | UniqueDeleter(const UniqueDeleter& d) noexcept {} 22 | 23 | void operator()(std::conditional_t, T, T*> aData) 24 | { 25 | TiltedPhoques::Delete(aData); 26 | } 27 | }; 28 | } 29 | 30 | template 31 | using Vector = std::vector>; 32 | 33 | template 34 | using Set = tsl::hopscotch_set, std::equal_to, TiltedPhoques::StlAllocator>; 35 | 36 | template 37 | using Map = tsl::hopscotch_map, std::equal_to, TiltedPhoques::StlAllocator>>; 38 | 39 | template 40 | using SortedMap = std::map, TiltedPhoques::StlAllocator>>; 41 | 42 | // TODO: OrderedMap 43 | 44 | template 45 | using SharedPtr = std::shared_ptr; 46 | 47 | template 48 | using WeakPtr = std::weak_ptr; 49 | 50 | template 51 | using UniquePtr = std::unique_ptr>; 52 | 53 | template 54 | struct ShareFromThis : public std::enable_shared_from_this 55 | { 56 | SharedPtr ToShared() 57 | { 58 | return std::enable_shared_from_this::shared_from_this(); 59 | } 60 | 61 | WeakPtr ToWeak() 62 | { 63 | return std::enable_shared_from_this::weak_from_this(); 64 | } 65 | }; 66 | 67 | template 68 | auto MakeShared(Args&&... aArgs) 69 | { 70 | return std::allocate_shared(TiltedPhoques::StlAllocator(), std::forward(aArgs)...); 71 | } 72 | 73 | template 74 | auto MakeUnique(Args&&... aArgs) 75 | { 76 | return UniquePtr(TiltedPhoques::New(std::forward(aArgs)...)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/Core/Win.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef WIN32_LEAN_AND_MEAN 4 | #define WIN32_LEAN_AND_MEAN 5 | #endif 6 | #ifndef NOMINMAX 7 | #define NOMINMAX 8 | #endif 9 | 10 | #include 11 | #include 12 | -------------------------------------------------------------------------------- /lib/Red/Alias.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | using namespace RED4ext; 6 | } 7 | 8 | namespace Red::Detail 9 | { 10 | using namespace RED4ext::Detail; 11 | } 12 | -------------------------------------------------------------------------------- /lib/Red/Engine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TypeInfo/Resolving.hpp" 4 | #include "TypeInfo/Parameters.hpp" 5 | 6 | #include "Engine/Framework.hpp" 7 | #include "Engine/LogChannel.hpp" 8 | 9 | #include "Engine/Mappings.hpp" 10 | -------------------------------------------------------------------------------- /lib/Red/Engine/Framework.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Macros/Framework.hpp" 4 | 5 | namespace Red 6 | { 7 | template 8 | struct RuntimeSystemMapping : public std::false_type {}; 9 | 10 | namespace Detail 11 | { 12 | template 13 | concept HasRuntimeSystemMapping = RuntimeSystemMapping::value 14 | && RuntimeSystemMapping::offset >= 0 15 | && RuntimeSystemMapping::offset <= 64; 16 | } 17 | 18 | template 19 | requires std::is_base_of_v && std::is_base_of_v 20 | U* GetGameSystem() 21 | { 22 | static const auto s_type = GetType(); 23 | auto& gameInstance = CGameEngine::Get()->framework->gameInstance; 24 | return reinterpret_cast(gameInstance->GetSystem(s_type)); 25 | } 26 | 27 | template 28 | requires std::is_base_of_v && std::is_base_of_v && Detail::HasRuntimeSystemMapping 29 | U* GetRuntimeSystem() 30 | { 31 | constexpr auto systemOffset = RuntimeSystemMapping::offset * sizeof(Handle); 32 | const auto& runtimeSceneAddr = CGameEngine::Get()->framework->unk18; 33 | return reinterpret_cast*>(runtimeSceneAddr + systemOffset)->instance; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/Red/Engine/LogChannel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red::Log 4 | { 5 | inline void Channel(CName aChannel, const CString& aMessage) 6 | { 7 | static auto* s_rtti = CRTTISystem::Get(); 8 | static auto* s_logFunc = s_rtti->GetFunction("LogChannel"); 9 | static auto* s_stringType = s_rtti->GetType("String"); 10 | static auto* s_stringRefType = s_rtti->GetType("script_ref:String"); 11 | static auto* s_nameType = s_rtti->GetType("CName"); 12 | 13 | ScriptRef messageRef; 14 | messageRef.type = s_stringType; 15 | messageRef.name = s_stringType->GetName(); 16 | messageRef.ref = const_cast(&aMessage); 17 | 18 | StackArgs_t args; 19 | args.emplace_back(s_nameType, &aChannel); 20 | args.emplace_back(s_stringRefType, &messageRef); 21 | 22 | CStack stack(nullptr, args.data(), static_cast(args.size()), nullptr); 23 | 24 | s_logFunc->Execute(&stack); 25 | } 26 | 27 | inline void Channel(Red::CName aChannel, const std::string& aMessage) 28 | { 29 | Channel(aChannel, Red::CString(aMessage.c_str())); 30 | } 31 | 32 | template 33 | constexpr void Channel(CName aChannel, std::format_string aFormat, Args&&... aArgs) 34 | { 35 | Channel(aChannel, std::format(aFormat, std::forward(aArgs)...)); 36 | } 37 | 38 | template 39 | constexpr void Debug(std::format_string aFormat, Args&&... aArgs) 40 | { 41 | Channel("DEBUG", std::format(aFormat, std::forward(aArgs)...)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/Red/Engine/Macros/Framework.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RTTI_MAP_RUNTIME_SYSTEM(_type, _offset) \ 4 | template<> \ 5 | struct Red::RuntimeSystemMapping<_type> : public std::true_type \ 6 | { \ 7 | static constexpr auto offset = _offset; \ 8 | }; 9 | -------------------------------------------------------------------------------- /lib/Red/Specializations.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TypeInfo/Resolving.hpp" 4 | 5 | template<> 6 | struct std::hash 7 | { 8 | std::size_t operator()(RED4ext::CName aKey) const 9 | { 10 | return aKey.hash; 11 | } 12 | }; 13 | 14 | template<> 15 | struct std::hash 16 | { 17 | std::size_t operator()(RED4ext::TweakDBID aKey) const 18 | { 19 | return aKey.value; 20 | } 21 | }; 22 | 23 | template<> 24 | struct std::hash 25 | { 26 | std::size_t operator()(RED4ext::ResourcePath aKey) const 27 | { 28 | return aKey.hash; 29 | } 30 | }; 31 | 32 | template<> 33 | struct std::hash 34 | { 35 | std::size_t operator()(RED4ext::NodeRef aKey) const 36 | { 37 | return aKey.hash; 38 | } 39 | }; 40 | 41 | template 42 | requires std::is_class_v && std::is_convertible_v && Red::Detail::HasGeneratedTypeName 43 | struct std::hash 44 | { 45 | std::size_t operator()(T aKey) const 46 | { 47 | return static_cast(aKey); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /lib/Red/TypeInfo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TypeInfo/Construction.hpp" 4 | #include "TypeInfo/Definition.hpp" 5 | #include "TypeInfo/Properties.hpp" 6 | #include "TypeInfo/Parameters.hpp" 7 | #include "TypeInfo/Invocation.hpp" 8 | #include "TypeInfo/Registrar.hpp" 9 | #include "TypeInfo/Resolving.hpp" 10 | 11 | #include "TypeInfo/Mappings.hpp" 12 | -------------------------------------------------------------------------------- /lib/Red/TypeInfo/Common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | struct Scope 6 | { 7 | constexpr Scope(const std::source_location& aLocation = std::source_location::current()) noexcept 8 | : hash(FNV1a64(aLocation.file_name(), aLocation.line())) 9 | { 10 | } 11 | 12 | constexpr Scope(const char* aName) noexcept 13 | : hash(FNV1a64(aName)) 14 | { 15 | } 16 | 17 | constexpr Scope(uint64_t aHash) noexcept 18 | : hash(aHash) 19 | { 20 | } 21 | 22 | constexpr operator uint64_t() const noexcept 23 | { 24 | return hash; 25 | } 26 | 27 | constexpr static Scope Unique(const std::source_location& aLocation = std::source_location::current()) noexcept 28 | { 29 | return {aLocation}; 30 | } 31 | 32 | template 33 | constexpr static Scope For() 34 | { 35 | return FNV1a64(nameof::nameof_type().data()); 36 | } 37 | 38 | template 39 | constexpr static Scope For() 40 | { 41 | return S; 42 | } 43 | 44 | uint64_t hash; 45 | }; 46 | 47 | namespace Detail 48 | { 49 | template 50 | concept IsSerializable = std::is_base_of_v; 51 | 52 | template 53 | concept IsScriptable = std::is_base_of_v; 54 | 55 | template 56 | concept IsGameSystem = std::is_base_of_v; 57 | 58 | template 59 | concept IsScriptableSystem = std::is_base_of_v; 60 | 61 | template 62 | concept IsType = std::is_base_of_v; 63 | 64 | template 65 | concept IsTypeOrVoid = IsType or std::is_void_v; 66 | 67 | template 68 | struct PropertyPtr : public std::false_type {}; 69 | 70 | template 71 | struct PropertyPtr

: public std::true_type 72 | { 73 | using context_type = C; 74 | using value_type = P; 75 | }; 76 | 77 | template 78 | concept IsPropertyPtr = PropertyPtr::value; 79 | 80 | template 81 | struct FunctionPtr : public std::false_type {}; 82 | 83 | template 84 | struct FunctionPtr : public std::true_type 85 | { 86 | using context_type = void; 87 | using return_type = R; 88 | 89 | using arguments_type = std::tuple...>; 90 | using qualified_arguments_type = std::tuple; 91 | using extended_arguments_type = arguments_type; 92 | 93 | template 94 | using argument_type = typename std::tuple_element::type; 95 | 96 | template 97 | using qualified_argument_type = typename std::tuple_element::type; 98 | 99 | template 100 | using extended_argument_type = typename std::tuple_element::type; 101 | 102 | template typename P> 103 | using transform_arguments_type = std::tuple>...>; 104 | 105 | static constexpr size_t arity = sizeof...(Args); 106 | }; 107 | 108 | template 109 | struct FunctionPtr : FunctionPtr 110 | { 111 | using context_type = C; 112 | using extended_arguments_type = std::tuple...>; 113 | 114 | template typename P> 115 | using transform_arguments_type = std::tuple>...>; 116 | }; 117 | 118 | template 119 | struct FunctionPtr : FunctionPtr {}; 120 | 121 | template 122 | struct FunctionPtr : FunctionPtr {}; 123 | 124 | template 125 | concept IsFunctionPtr = FunctionPtr::value; 126 | 127 | template 128 | concept IsStaticFunctionPtr = FunctionPtr::value && std::is_void_v::context_type>; 129 | 130 | template 131 | concept IsMemberFunctionPtr = FunctionPtr::value && !std::is_void_v::context_type>; 132 | 133 | template 134 | struct Specialization : public std::false_type {}; 135 | 136 | template class G, typename A> 137 | struct Specialization> : public std::true_type 138 | { 139 | using argument_type = A; 140 | }; 141 | 142 | template class G, typename A, typename... Args> 143 | struct Specialization> : public std::true_type 144 | { 145 | using argument_type = A; 146 | }; 147 | 148 | template class G, typename A, auto V> 149 | struct Specialization> : public std::true_type 150 | { 151 | using argument_type = A; 152 | static constexpr auto argument_value = V; 153 | }; 154 | 155 | template 156 | concept IsSpecialization = Specialization::value; 157 | } 158 | 159 | template> 160 | concept IsArray = Detail::Specialization::value 161 | && std::is_same_v::argument_type>>; 162 | } 163 | -------------------------------------------------------------------------------- /lib/Red/TypeInfo/Construction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Resolving.hpp" 4 | 5 | namespace Red 6 | { 7 | template 8 | inline T* Construct() 9 | { 10 | auto type = GetType(); 11 | auto instance = reinterpret_cast(type->GetAllocator()->AllocAligned(type->GetSize(), 12 | type->GetAlignment()).memory); 13 | type->Construct(instance); 14 | return instance; 15 | } 16 | 17 | template 18 | inline void Destruct(T* aInstance) 19 | { 20 | auto type = GetType(); 21 | type->Destruct(aInstance); 22 | type->GetAllocator()->Free(aInstance); 23 | } 24 | } 25 | 26 | template 27 | requires std::is_abstract_v && Red::Detail::HasGeneratedTypeName 28 | struct RED4ext::Detail::AllocatorHook : std::true_type 29 | { 30 | inline static Memory::IAllocator* Get() 31 | { 32 | return Red::GetClass()->GetAllocator(); 33 | } 34 | }; 35 | 36 | template 37 | requires std::is_abstract_v && Red::Detail::HasGeneratedTypeName 38 | struct RED4ext::Detail::ConstructorHook : std::true_type 39 | { 40 | inline static void Apply(T* aInstance) 41 | { 42 | Red::GetClass()->ConstructCls(aInstance); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /lib/Red/TypeInfo/Macros/Resolving.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define RTTI_MAP_TYPE_NAME(_type, _name) \ 4 | template<> \ 5 | struct Red::TypeNameMapping<_type> : public std::true_type \ 6 | { \ 7 | static constexpr auto name = _name; \ 8 | }; 9 | 10 | #define RTTI_MAP_TYPE_PREFIX(_type, _prefix) \ 11 | template \ 12 | struct Red::TypePrefixMapping<_type> : public std::true_type \ 13 | { \ 14 | static constexpr auto prefix = _prefix; \ 15 | }; 16 | 17 | #define RTTI_MAP_TYPE_PROXY(_type) \ 18 | template \ 19 | struct Red::TypeProxyMapping<_type> : public std::true_type \ 20 | { \ 21 | using type = A; \ 22 | }; 23 | 24 | #define RTTI_TYPE_NAME_STR(_type) Red::GetTypeNameStr<_type>() 25 | 26 | #define RTTI_FUNC_NAME_STR(...) []() constexpr noexcept { \ 27 | constexpr auto _name = ::nameof::detail::pretty_name(#__VA_ARGS__); \ 28 | return ::Red::Detail::MakeConstStr<_name.size()>(_name.data()); }() 29 | 30 | #define RTTI_PROP_NAME_STR(...) []() constexpr noexcept { \ 31 | constexpr auto _name = ::nameof::detail::pretty_name(#__VA_ARGS__); \ 32 | constexpr auto _clean = ::Red::Detail::RemoveMemberPrefix(_name); \ 33 | return ::Red::Detail::MakeConstStr<_clean.size()>(_clean.data()); }() 34 | 35 | #define RTTI_ENUM_NAME_STR(...) ::nameof::nameof_enum<__VA_ARGS__>() 36 | 37 | #define RTTI_TYPE_NAME(_type) ::Red::GetTypeName<_type>() 38 | #define RTTI_FUNC_NAME(_func) ::Red::CName(RTTI_FUNC_NAME_STR(&_func).data()) 39 | #define RTTI_PROP_NAME(_prop) ::Red::CName(RTTI_PROP_NAME_STR(&_prop).data()) 40 | #define RTTI_ENUM_NAME(_enum) ::Red::CName(RTTI_ENUM_NAME_STR(_enum).data()) 41 | -------------------------------------------------------------------------------- /lib/Red/TypeInfo/Mappings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Resolving.hpp" 4 | 5 | namespace Red 6 | { 7 | RTTI_MAP_TYPE_NAME(int8_t, "Int8"); 8 | RTTI_MAP_TYPE_NAME(uint8_t, "Uint8"); 9 | RTTI_MAP_TYPE_NAME(int16_t, "Int16"); 10 | RTTI_MAP_TYPE_NAME(uint16_t, "Uint16"); 11 | RTTI_MAP_TYPE_NAME(int32_t, "Int32"); 12 | RTTI_MAP_TYPE_NAME(uint32_t, "Uint32"); 13 | RTTI_MAP_TYPE_NAME(int64_t, "Int64"); 14 | RTTI_MAP_TYPE_NAME(uint64_t, "Uint64"); 15 | RTTI_MAP_TYPE_NAME(float, "Float"); 16 | RTTI_MAP_TYPE_NAME(double, "Double"); 17 | RTTI_MAP_TYPE_NAME(bool, "Bool"); 18 | RTTI_MAP_TYPE_NAME(CString, "String"); 19 | RTTI_MAP_TYPE_NAME(CName, "CName"); 20 | RTTI_MAP_TYPE_NAME(TweakDBID, "TweakDBID"); 21 | RTTI_MAP_TYPE_NAME(ItemID, "gameItemID"); 22 | RTTI_MAP_TYPE_NAME(NodeRef, "NodeRef"); 23 | RTTI_MAP_TYPE_NAME(GlobalNodeRef, "worldGlobalNodeRef"); 24 | RTTI_MAP_TYPE_NAME(Variant, "Variant"); 25 | 26 | RTTI_MAP_TYPE_PREFIX(DynArray, "array:"); 27 | RTTI_MAP_TYPE_PREFIX(Handle, "handle:"); 28 | RTTI_MAP_TYPE_PREFIX(WeakHandle, "whandle:"); 29 | RTTI_MAP_TYPE_PREFIX(ResourceReference, "rRef:"); 30 | RTTI_MAP_TYPE_PREFIX(ResourceAsyncReference, "raRef:"); 31 | RTTI_MAP_TYPE_PREFIX(CurveData, "curveData:"); 32 | 33 | RTTI_MAP_TYPE_NAME(char, "Uint8"); 34 | RTTI_MAP_TYPE_NAME(ResourcePath, "redResourceReferenceScriptToken"); 35 | } 36 | -------------------------------------------------------------------------------- /lib/Red/TypeInfo/Parameters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "Construction.hpp" 5 | #include "Resolving.hpp" 6 | 7 | namespace Red 8 | { 9 | template 10 | struct Optional; 11 | 12 | template 13 | requires (!std::is_void_v) 14 | struct Optional 15 | { 16 | Optional() = default; 17 | 18 | Optional(T&& aValue) 19 | : value(aValue) 20 | { 21 | } 22 | 23 | template>> 24 | [[nodiscard]] inline explicit operator U() 25 | { 26 | return static_cast(value); 27 | } 28 | 29 | inline operator T&() 30 | { 31 | return value; 32 | } 33 | 34 | [[nodiscard]] inline T* operator->() 35 | { 36 | return &value; 37 | } 38 | 39 | template 40 | Optional& operator=(const U& aValue) noexcept 41 | { 42 | value = aValue; 43 | return *this; 44 | } 45 | 46 | template 47 | Optional& operator=(U&& aValue) noexcept 48 | { 49 | value = aValue; 50 | return *this; 51 | } 52 | 53 | [[nodiscard]] bool IsEmpty() 54 | { 55 | return !value; 56 | } 57 | 58 | [[nodiscard]] bool IsDefault() const 59 | { 60 | return value == ADefault; 61 | } 62 | 63 | T value{ADefault}; 64 | }; 65 | 66 | template 67 | struct Optional 68 | { 69 | template>> 70 | [[nodiscard]] inline explicit operator U() 71 | { 72 | return static_cast(value); 73 | } 74 | 75 | inline operator T&() 76 | { 77 | return value; 78 | } 79 | 80 | [[nodiscard]] inline T* operator->() 81 | { 82 | return &value; 83 | } 84 | 85 | template 86 | Optional& operator=(const U& aValue) noexcept 87 | { 88 | value = aValue; 89 | return *this; 90 | } 91 | 92 | template 93 | Optional& operator=(U&& aValue) noexcept 94 | { 95 | value = aValue; 96 | return *this; 97 | } 98 | 99 | [[nodiscard]] bool IsEmpty() 100 | { 101 | return !value; 102 | } 103 | 104 | T value{}; 105 | }; 106 | 107 | template> 108 | concept IsOptional = Detail::Specialization::value 109 | && std::is_same_v::argument_type, 110 | Detail::Specialization::argument_value>>; 111 | 112 | template 113 | struct ScriptRef 114 | { 115 | explicit ScriptRef(bool aAllocate = false) 116 | : unk00(nullptr) 117 | , managed(false) 118 | , type(nullptr) 119 | , ref(nullptr) 120 | { 121 | if (managed) 122 | { 123 | AllocateValue(); 124 | } 125 | } 126 | 127 | ~ScriptRef() 128 | { 129 | FreeValue(); 130 | } 131 | 132 | inline explicit operator bool() 133 | { 134 | return ref; 135 | } 136 | 137 | [[nodiscard]] inline T& operator*() 138 | { 139 | return *ref; 140 | } 141 | 142 | [[nodiscard]] inline T* operator->() 143 | { 144 | return ref; 145 | } 146 | 147 | inline auto operator[](size_t aIndex) 148 | { 149 | return (*ref)[aIndex]; 150 | } 151 | 152 | inline ScriptRef& operator=(const T& aValue) noexcept 153 | { 154 | if (ref) 155 | { 156 | *ref = aValue; 157 | } 158 | 159 | return *this; 160 | } 161 | 162 | inline ScriptRef& operator=(T&& aValue) noexcept 163 | { 164 | if (ref) 165 | { 166 | *ref = aValue; 167 | } 168 | 169 | return *this; 170 | } 171 | 172 | inline void AllocateValue() 173 | { 174 | if (!managed) 175 | { 176 | managed = true; 177 | type = GetType(); 178 | ref = Construct(); 179 | } 180 | } 181 | 182 | inline void FreeValue() 183 | { 184 | if (managed) 185 | { 186 | managed = false; 187 | Destruct(ref); 188 | } 189 | } 190 | 191 | void* unk00; // 00 - VFT 192 | bool managed; // 08 - Acrually a pointer, but never used 193 | CBaseRTTIType* type; // 10 194 | T* ref; // 18 195 | CName name; // 20 196 | }; 197 | 198 | template> 199 | concept IsScriptRef = Detail::Specialization::value 200 | && std::is_same_v::argument_type>>; 201 | 202 | RTTI_MAP_TYPE_PROXY(Optional); 203 | RTTI_MAP_TYPE_PREFIX(ScriptRef, "script_ref:"); 204 | } 205 | -------------------------------------------------------------------------------- /lib/Red/TypeInfo/Properties.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "Resolving.hpp" 5 | 6 | namespace Red 7 | { 8 | template 9 | requires (!std::is_base_of_v) 10 | inline T& GetPropertyPtr(C* aContext, CName aProp) 11 | { 12 | return GetPropertyPtr(aContext, GetClass(), aProp); 13 | } 14 | 15 | template 16 | inline T* GetPropertyPtr(ISerializable* aContext, CName aProp) 17 | { 18 | if (!aContext) 19 | return nullptr; 20 | 21 | auto prop = aContext->GetType()->GetProperty(aProp); 22 | if (!prop) 23 | return nullptr; 24 | 25 | return prop->GetValuePtr(aContext); 26 | } 27 | 28 | template 29 | inline T* GetPropertyPtr(void* aContext, CClass* aType, CName aProp) 30 | { 31 | if (!aContext || !aType) 32 | return nullptr; 33 | 34 | auto prop = aType->GetProperty(aProp); 35 | if (!prop) 36 | return nullptr; 37 | 38 | return prop->GetValuePtr(aContext); 39 | } 40 | 41 | template 42 | inline T* GetPropertyPtr(void* aContext, CName aType, CName aProp) 43 | { 44 | return GetPropertyPtr(aContext, GetClass(aType), aProp); 45 | } 46 | 47 | template 48 | requires (!std::is_base_of_v) 49 | inline T& GetProperty(C* aContext, CName aProp) 50 | { 51 | return *GetPropertyPtr(aContext, GetClass(), aProp); 52 | } 53 | 54 | template 55 | inline T& GetProperty(ISerializable* aContext, CName aProp) 56 | { 57 | return *GetPropertyPtr(aContext, aProp); 58 | } 59 | 60 | template 61 | inline T& GetProperty(void* aContext, CClass* aType, CName aProp) 62 | { 63 | return *GetPropertyPtr(aContext, aType, aProp); 64 | } 65 | 66 | template 67 | inline T& GetProperty(void* aContext, CName aType, CName aProp) 68 | { 69 | return *GetPropertyPtr(aContext, aType, aProp); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/Red/TypeInfo/Registrar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | class TypeInfoRegistrar 6 | { 7 | public: 8 | using Callback = void(*)(); 9 | 10 | TypeInfoRegistrar(Callback aRegister, Callback aDescribe) 11 | { 12 | AddRegisterCallback(aRegister); 13 | AddDescribeCallback(aDescribe); 14 | } 15 | 16 | static inline void RegisterDiscovered() 17 | { 18 | QueuePendingRegisterCallbacks(); 19 | QueuePendingDescribeCallbacks(); 20 | } 21 | 22 | static inline void AddRegisterCallback(Callback aRegister) 23 | { 24 | if (aRegister) 25 | { 26 | s_registerCallbacks.push_back(aRegister); 27 | } 28 | } 29 | 30 | static inline void AddDescribeCallback(Callback aDescribe) 31 | { 32 | if (aDescribe) 33 | { 34 | s_describeCallbacks.push_back(aDescribe); 35 | } 36 | } 37 | 38 | private: 39 | static inline void QueuePendingRegisterCallbacks() 40 | { 41 | if (!s_registerCallbacks.empty()) 42 | { 43 | CRTTISystem::Get()->AddRegisterCallback(&OnRegister); 44 | } 45 | } 46 | 47 | static inline void QueuePendingDescribeCallbacks() 48 | { 49 | if (!s_describeCallbacks.empty()) 50 | { 51 | CRTTISystem::Get()->AddPostRegisterCallback(&OnDescribe); 52 | } 53 | } 54 | 55 | static inline void ProcessPendingRegisterCallbacks() 56 | { 57 | auto callbacks = std::move(s_registerCallbacks); 58 | for (const auto& callback :callbacks) 59 | { 60 | callback(); 61 | } 62 | } 63 | 64 | static inline void ProcessPendingDescriberCallbacks() 65 | { 66 | auto callbacks = std::move(s_describeCallbacks); 67 | for (const auto& callback :callbacks) 68 | { 69 | callback(); 70 | } 71 | } 72 | 73 | static inline void OnRegister() 74 | { 75 | ProcessPendingRegisterCallbacks(); 76 | QueuePendingRegisterCallbacks(); 77 | } 78 | 79 | static inline void OnDescribe() 80 | { 81 | ProcessPendingDescriberCallbacks(); 82 | QueuePendingDescribeCallbacks(); 83 | } 84 | 85 | static inline std::vector s_registerCallbacks; 86 | static inline std::vector s_describeCallbacks; 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /lib/Red/Utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Utils/Handles.hpp" 4 | #include "Utils/JobQueues.hpp" 5 | #include "Utils/Resources.hpp" 6 | -------------------------------------------------------------------------------- /lib/Red/Utils/Handles.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | template 6 | inline WeakHandle& AsWeakHandle(T* aInstance) 7 | { 8 | return *reinterpret_cast*>(&aInstance->ref); 9 | } 10 | 11 | template 12 | inline Handle AsHandle(T* aInstance) 13 | { 14 | return AsWeakHandle(aInstance); 15 | } 16 | 17 | template 18 | inline WeakHandle ToWeakHandle(T* aInstance) 19 | { 20 | if (!aInstance) 21 | return {}; 22 | 23 | return AsWeakHandle(aInstance); 24 | } 25 | 26 | template 27 | inline WeakHandle ToWeakHandle(const Handle& aInstance) 28 | { 29 | return aInstance; 30 | } 31 | 32 | template 33 | inline Handle ToHandle(T* aInstance) 34 | { 35 | if (!aInstance) 36 | return {}; 37 | 38 | if (aInstance->ref.instance) 39 | { 40 | return reinterpret_cast*>(&aInstance->ref)->Lock(); 41 | } 42 | else 43 | { 44 | return Handle(aInstance); 45 | } 46 | } 47 | 48 | template 49 | inline Handle ToHandle(U* aInstance) 50 | { 51 | if (!aInstance) 52 | return {}; 53 | 54 | auto instance = reinterpret_cast(aInstance); 55 | if (instance->ref.instance) 56 | { 57 | return reinterpret_cast*>(&instance->ref)->Lock(); 58 | } 59 | else 60 | { 61 | return Handle(reinterpret_cast(instance)); 62 | } 63 | } 64 | 65 | template 66 | inline Handle MakeScriptedHandle(CClass* aType) 67 | { 68 | return Handle(reinterpret_cast(aType->CreateInstance(true))); 69 | } 70 | 71 | template 72 | inline Handle MakeScriptedHandle(CName aTypeName) 73 | { 74 | auto classType = GetClass(aTypeName); 75 | 76 | if (classType == nullptr) 77 | { 78 | return {}; 79 | } 80 | return Handle(reinterpret_cast(classType->CreateInstance(true))); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/Red/Utils/JobQueues.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | namespace Detail 6 | { 7 | struct WaitingContext 8 | { 9 | std::mutex mutex; 10 | std::condition_variable cv; 11 | bool finished{false}; 12 | }; 13 | } 14 | 15 | template 16 | inline void WaitForQueue(JobQueue& aQueue, const W& aTimeout) 17 | { 18 | auto context = std::make_shared(); 19 | 20 | aQueue.Dispatch([context]() { 21 | context->finished = true; 22 | context->cv.notify_all(); 23 | }); 24 | 25 | std::unique_lock lock(context->mutex); 26 | context->cv.wait_for(lock, aTimeout, [context]() { return context->finished; }); 27 | } 28 | 29 | template 30 | inline void WaitForJob(const JobHandle& aJob, const W& aTimeout) 31 | { 32 | JobQueue queue; 33 | queue.Wait(aJob); 34 | WaitForQueue(queue, aTimeout); 35 | } 36 | 37 | template typename V, typename R, typename W, typename... A> 38 | inline void WaitForJobs(const V& aJobs, const W& aTimeout) 39 | { 40 | JobQueue queue; 41 | for (const auto& job : aJobs) 42 | { 43 | queue.Wait(job); 44 | } 45 | WaitForQueue(queue, aTimeout); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/Red/Utils/Resources.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JobQueues.hpp" 4 | 5 | namespace Red 6 | { 7 | namespace Detail 8 | { 9 | template 10 | struct ResourceTraits; 11 | 12 | template 13 | struct ResourceTraits>> 14 | { 15 | inline static bool IsFinished(const SharedPtr>& aToken) 16 | { 17 | return aToken->IsLoaded() || aToken->IsFailed(); 18 | } 19 | 20 | template typename V, typename... A> 21 | inline static bool AllFinished(const V>, A...>& aTokens) 22 | { 23 | return std::ranges::all_of(aTokens, &IsFinished); 24 | } 25 | 26 | inline static JobHandle& GetJobHandle(const SharedPtr>& aToken) 27 | { 28 | return aToken->job; 29 | } 30 | 31 | inline static ResourcePath GetPath(const SharedPtr>& aToken) 32 | { 33 | return aToken->path; 34 | } 35 | }; 36 | 37 | template 38 | struct ResourceTraits> 39 | { 40 | inline static bool IsFinished(const ResourceReference& aReference) 41 | { 42 | return aReference.token->IsLoaded() || aReference.token->IsFailed(); 43 | } 44 | 45 | template typename V, typename... A> 46 | inline static bool AllFinished(const V, A...>& aReferences) 47 | { 48 | return std::ranges::all_of(aReferences, &IsFinished); 49 | } 50 | 51 | inline static JobHandle& GetJobHandle(const ResourceReference& aReference) 52 | { 53 | return aReference.token->job; 54 | } 55 | 56 | inline static ResourcePath GetPath(const ResourceReference& aReference) 57 | { 58 | return aReference.token->path; 59 | } 60 | }; 61 | } 62 | 63 | template 64 | inline void WaitForResource(const R& aResource, const W& aTimeout) 65 | { 66 | using Trait = Detail::ResourceTraits; 67 | 68 | if (!Trait::IsFinished(aResource)) 69 | { 70 | WaitForJob(Trait::GetJobHandle(aResource), aTimeout); 71 | } 72 | } 73 | 74 | template typename V, typename R, typename W, typename... A> 75 | inline void WaitForResources(const V& aResources, const W& aTimeout) 76 | { 77 | using Trait = Detail::ResourceTraits; 78 | 79 | if (!Trait::AllFinished(aResources)) 80 | { 81 | JobQueue queue; 82 | for (const auto& resource : aResources) 83 | { 84 | queue.Wait(Trait::GetJobHandle(resource)); 85 | } 86 | WaitForQueue(queue, aTimeout); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/Support/MinHook/MinHookProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "MinHookProvider.hpp" 2 | 3 | #include 4 | 5 | Support::MinHookProvider::MinHookProvider() 6 | { 7 | MH_Initialize(); 8 | 9 | SetDefault(*this); 10 | } 11 | 12 | Support::MinHookProvider::~MinHookProvider() 13 | { 14 | MH_DisableHook(MH_ALL_HOOKS); 15 | MH_Uninitialize(); 16 | } 17 | 18 | bool Support::MinHookProvider::HookAttach(uintptr_t aAddress, void* aCallback) 19 | { 20 | return HookAttach(aAddress, aCallback, nullptr); 21 | } 22 | 23 | bool Support::MinHookProvider::HookAttach(uintptr_t aAddress, void* aCallback, void** aOriginal) 24 | { 25 | if (MH_CreateHook(reinterpret_cast(aAddress), aCallback, aOriginal) != MH_OK) 26 | return false; 27 | 28 | if (MH_EnableHook(reinterpret_cast(aAddress)) != MH_OK) 29 | { 30 | MH_RemoveHook(reinterpret_cast(aAddress)); 31 | return false; 32 | } 33 | 34 | return true; 35 | } 36 | 37 | bool Support::MinHookProvider::HookDetach(uintptr_t aAddress) 38 | { 39 | if (MH_DisableHook(reinterpret_cast(aAddress)) != MH_OK) 40 | return false; 41 | 42 | if (MH_RemoveHook(reinterpret_cast(aAddress)) != MH_OK) 43 | return false; 44 | 45 | return true; 46 | } 47 | -------------------------------------------------------------------------------- /lib/Support/MinHook/MinHookProvider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Feature.hpp" 4 | #include "Core/Hooking/HookingDriver.hpp" 5 | 6 | namespace Support 7 | { 8 | class MinHookProvider 9 | : public Core::Feature 10 | , public Core::HookingDriver 11 | { 12 | public: 13 | MinHookProvider(); 14 | ~MinHookProvider() override; 15 | 16 | bool HookAttach(uintptr_t aAddress, void* aCallback) override; 17 | bool HookAttach(uintptr_t aAddress, void* aCallback, void** aOriginal) override; 18 | bool HookDetach(uintptr_t aAddress) override; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /lib/Support/RED4ext/RED4extProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "RED4extProvider.hpp" 2 | 3 | Support::RED4extProvider::RED4extProvider(RED4ext::PluginHandle aPlugin, const RED4ext::Sdk* aSdk) noexcept 4 | : m_plugin(aPlugin) 5 | , m_sdk(aSdk) 6 | , m_enableLogging(false) 7 | , m_enableHooking(false) 8 | , m_enableAddressLibrary(false) 9 | { 10 | } 11 | 12 | void Support::RED4extProvider::OnInitialize() 13 | { 14 | if (m_enableLogging) 15 | { 16 | LoggingDriver::SetDefault(*this); 17 | } 18 | 19 | if (m_enableHooking) 20 | { 21 | HookingDriver::SetDefault(*this); 22 | } 23 | 24 | if (m_enableAddressLibrary) 25 | { 26 | AddressResolver::SetDefault(*this); 27 | } 28 | } 29 | 30 | void Support::RED4extProvider::LogInfo(const std::string_view& aMessage) 31 | { 32 | m_sdk->logger->Info(m_plugin, aMessage.data()); 33 | } 34 | 35 | void Support::RED4extProvider::LogWarning(const std::string_view& aMessage) 36 | { 37 | m_sdk->logger->Warn(m_plugin, aMessage.data()); 38 | } 39 | 40 | void Support::RED4extProvider::LogError(const std::string_view& aMessage) 41 | { 42 | m_sdk->logger->Error(m_plugin, aMessage.data()); 43 | } 44 | 45 | void Support::RED4extProvider::LogDebug(const std::string_view& aMessage) 46 | { 47 | m_sdk->logger->Debug(m_plugin, aMessage.data()); 48 | } 49 | 50 | void Support::RED4extProvider::LogFlush() 51 | { 52 | } 53 | 54 | bool Support::RED4extProvider::HookAttach(uintptr_t aAddress, void* aCallback) 55 | { 56 | return m_sdk->hooking->Attach(m_plugin, reinterpret_cast(aAddress), aCallback, nullptr); 57 | } 58 | 59 | bool Support::RED4extProvider::HookAttach(uintptr_t aAddress, void* aCallback, void** aOriginal) 60 | { 61 | return m_sdk->hooking->Attach(m_plugin, reinterpret_cast(aAddress), aCallback, aOriginal); 62 | } 63 | 64 | bool Support::RED4extProvider::HookDetach(uintptr_t aAddress) 65 | { 66 | return m_sdk->hooking->Detach(m_plugin, reinterpret_cast(aAddress)); 67 | } 68 | 69 | uintptr_t Support::RED4extProvider::ResolveAddress(uint32_t aAddressID) 70 | { 71 | return RED4ext::UniversalRelocBase::Resolve(aAddressID); 72 | } 73 | -------------------------------------------------------------------------------- /lib/Support/RED4ext/RED4extProvider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Feature.hpp" 4 | #include "Core/Hooking/HookingDriver.hpp" 5 | #include "Core/Logging/LoggingDriver.hpp" 6 | #include "Core/Memory/AddressResolver.hpp" 7 | 8 | namespace Support 9 | { 10 | class RED4extProvider 11 | : public Core::Feature 12 | , public Core::LoggingDriver 13 | , public Core::HookingDriver 14 | , public Core::AddressResolver 15 | { 16 | public: 17 | RED4extProvider(RED4ext::PluginHandle aPlugin, const RED4ext::Sdk* aSdk) noexcept; 18 | 19 | void LogInfo(const std::string_view& aMessage) override; 20 | void LogWarning(const std::string_view& aMessage) override; 21 | void LogError(const std::string_view& aMessage) override; 22 | void LogDebug(const std::string_view& aMessage) override; 23 | void LogFlush() override; 24 | 25 | bool HookAttach(uintptr_t aAddress, void* aCallback) override; 26 | bool HookAttach(uintptr_t aAddress, void* aCallback, void** aOriginal) override; 27 | bool HookDetach(uintptr_t aAddress) override; 28 | 29 | uintptr_t ResolveAddress(uint32_t aAddressID) override; 30 | 31 | auto EnableLogging() noexcept 32 | { 33 | m_enableLogging = true; 34 | return Defer(this); 35 | } 36 | 37 | auto EnableHooking() noexcept 38 | { 39 | m_enableHooking = true; 40 | return Defer(this); 41 | } 42 | 43 | auto EnableAddressLibrary() noexcept 44 | { 45 | m_enableAddressLibrary = true; 46 | return Defer(this); 47 | } 48 | 49 | auto RegisterScripts(const std::filesystem::path& aPath) noexcept 50 | { 51 | m_sdk->scripts->Add(m_plugin, aPath.c_str()); 52 | return Defer(this); 53 | } 54 | 55 | protected: 56 | void OnInitialize() override; 57 | 58 | RED4ext::PluginHandle m_plugin; 59 | const RED4ext::Sdk* m_sdk; 60 | bool m_enableLogging; 61 | bool m_enableHooking; 62 | bool m_enableAddressLibrary; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /lib/Support/RedLib/RedLibProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "RedLibProvider.hpp" 2 | #include "Red/TypeInfo/Registrar.hpp" 3 | 4 | void Support::RedLibProvider::OnBootstrap() 5 | { 6 | Red::TypeInfoRegistrar::RegisterDiscovered(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/Support/RedLib/RedLibProvider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Feature.hpp" 4 | 5 | namespace Support 6 | { 7 | class RedLibProvider : public Core::Feature 8 | { 9 | void OnBootstrap() override; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /lib/Support/Spdlog/SpdlogProvider.cpp: -------------------------------------------------------------------------------- 1 | #include "SpdlogProvider.hpp" 2 | #include "Core/Facades/Runtime.hpp" 3 | #include "Core/Stl.hpp" 4 | 5 | #include 6 | #include 7 | 8 | void Support::SpdlogProvider::OnInitialize() 9 | { 10 | if (m_baseLogPath.empty()) 11 | { 12 | m_baseLogPath = Core::Runtime::GetModulePath().replace_extension(L".log"); 13 | } 14 | 15 | auto logPath = m_baseLogPath; 16 | 17 | if (m_appendTimestamp) 18 | { 19 | const auto logExtension = m_baseLogPath.extension(); 20 | 21 | logPath.replace_extension(); 22 | logPath += "-"; 23 | 24 | if (m_maxLogCount > 0) 25 | { 26 | std::error_code error; 27 | std::set existingLogs; 28 | 29 | for (const auto& entry : std::filesystem::directory_iterator(m_baseLogPath.parent_path(), error)) 30 | { 31 | if (entry.is_regular_file() && entry.path().extension() == logExtension && 32 | entry.path().wstring().starts_with(logPath.wstring())) 33 | { 34 | existingLogs.insert(entry.path()); 35 | } 36 | } 37 | 38 | auto excessiveLogCount = static_cast(existingLogs.size()) - m_maxLogCount + 1; 39 | if (excessiveLogCount > 0) 40 | { 41 | for (const auto& path : existingLogs) 42 | { 43 | std::filesystem::remove(path, error); 44 | 45 | if (--excessiveLogCount == 0) 46 | { 47 | break; 48 | } 49 | } 50 | } 51 | } 52 | 53 | // Append timestamp to filename 54 | auto now = std::chrono::system_clock::now(); 55 | std::time_t now_c = std::chrono::system_clock::to_time_t(now); 56 | std::tm now_tm = *std::localtime(&now_c); 57 | 58 | const auto logTimestamp = 59 | fmt::format("{:04d}-{:02d}-{:02d}-{:02d}-{:02d}-{:02d}", now_tm.tm_year + 1900, now_tm.tm_mon + 1, 60 | now_tm.tm_mday, now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec); 61 | 62 | logPath += logTimestamp; 63 | logPath.replace_extension(logExtension); 64 | } 65 | 66 | auto sink = Core::MakeShared(logPath.string(), true); 67 | auto logger = Core::MakeShared("", spdlog::sinks_init_list{sink}); 68 | logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%t] [%l] %v"); 69 | logger->flush_on(spdlog::level::trace); 70 | 71 | spdlog::set_default_logger(logger); 72 | spdlog::set_level(spdlog::level::trace); 73 | 74 | if (m_recentSymlink && logPath != m_baseLogPath) 75 | { 76 | std::error_code error; 77 | std::filesystem::remove(m_baseLogPath, error); 78 | std::filesystem::create_symlink(logPath.filename(), m_baseLogPath, error); 79 | } 80 | 81 | SetDefault(*this); 82 | } 83 | 84 | void Support::SpdlogProvider::LogInfo(const std::string_view& aMessage) 85 | { 86 | spdlog::default_logger_raw()->info(aMessage); 87 | } 88 | 89 | void Support::SpdlogProvider::LogWarning(const std::string_view& aMessage) 90 | { 91 | spdlog::default_logger_raw()->warn(aMessage); 92 | } 93 | 94 | void Support::SpdlogProvider::LogError(const std::string_view& aMessage) 95 | { 96 | spdlog::default_logger_raw()->error(aMessage); 97 | } 98 | 99 | void Support::SpdlogProvider::LogDebug(const std::string_view& aMessage) 100 | { 101 | spdlog::default_logger_raw()->debug(aMessage); 102 | } 103 | 104 | void Support::SpdlogProvider::LogFlush() 105 | { 106 | spdlog::default_logger_raw()->flush(); 107 | } 108 | -------------------------------------------------------------------------------- /lib/Support/Spdlog/SpdlogProvider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Feature.hpp" 4 | #include "Core/Logging/LoggingDriver.hpp" 5 | 6 | namespace Support 7 | { 8 | class SpdlogProvider 9 | : public Core::Feature 10 | , public Core::LoggingDriver 11 | { 12 | public: 13 | void LogInfo(const std::string_view& aMessage) override; 14 | void LogWarning(const std::string_view& aMessage) override; 15 | void LogError(const std::string_view& aMessage) override; 16 | void LogDebug(const std::string_view& aMessage) override; 17 | void LogFlush() override; 18 | 19 | auto SetLogPath(const std::filesystem::path& aPath) noexcept 20 | { 21 | m_baseLogPath = aPath; 22 | return Defer(this); 23 | } 24 | 25 | auto AppendTimestampToLogName() noexcept 26 | { 27 | m_appendTimestamp = true; 28 | return Defer(this); 29 | } 30 | 31 | auto CreateRecentLogSymlink() noexcept 32 | { 33 | m_recentSymlink = true; 34 | return Defer(this); 35 | } 36 | 37 | auto SetMaxLogFiles(int32_t aMaxFiles) noexcept 38 | { 39 | m_maxLogCount = aMaxFiles; 40 | return Defer(this); 41 | } 42 | 43 | protected: 44 | void OnInitialize() override; 45 | 46 | std::filesystem::path m_baseLogPath; 47 | bool m_appendTimestamp{ false }; 48 | bool m_recentSymlink{ false }; 49 | int32_t m_maxLogCount{ 10 }; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /scripts/Module.reds: -------------------------------------------------------------------------------- 1 | module TweakXL 2 | -------------------------------------------------------------------------------- /scripts/ScriptableTweak.reds: -------------------------------------------------------------------------------- 1 | public abstract native class ScriptableTweak { 2 | protected cb func OnApply() -> Void 3 | } 4 | -------------------------------------------------------------------------------- /scripts/StatusEffect.reds: -------------------------------------------------------------------------------- 1 | @wrapMethod(StealthMappinController) 2 | private final func UpdateStatusEffectIcon() { 3 | wrappedMethod(); 4 | if this.m_statusEffectShowing { 5 | let iconRecord = TweakDBInterface.GetUIIconRecord(TDBID.Create("UIIcon." + this.m_mappin.GetStatusEffectIconPath())); 6 | if IsDefined(iconRecord) { 7 | inkImageRef.SetTexturePart(this.m_statusEffectIcon, iconRecord.AtlasPartName()); 8 | inkImageRef.SetAtlasResource(this.m_statusEffectIcon, iconRecord.AtlasResourcePath()); 9 | } else { 10 | inkImageRef.SetAtlasResource(this.m_statusEffectIcon, r"base/gameplay/gui/widgets/healthbar/atlas_buffinfo.inkatlas"); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/TweakDBBatch.reds: -------------------------------------------------------------------------------- 1 | public native class TweakDBBatch { 2 | public native func SetFlat(id: TweakDBID, value: Variant) -> Bool 3 | public native func CreateRecord(id: TweakDBID, type: CName) -> Bool 4 | public native func CloneRecord(id: TweakDBID, base: TweakDBID) -> Bool 5 | public native func UpdateRecord(id: TweakDBID) -> Bool 6 | public native func RegisterEnum(id: TweakDBID) 7 | public native func RegisterName(name: CName) -> Bool 8 | public native func Commit() 9 | 10 | public func SetFlat(name: CName, value: Variant) -> Bool { 11 | if this.SetFlat(TDBID.Create(NameToString(name)), value) { 12 | this.RegisterName(name); 13 | return true; 14 | } 15 | return false; 16 | } 17 | 18 | public func CreateRecord(name: CName, type: CName) -> Bool { 19 | if this.CreateRecord(TDBID.Create(NameToString(name)), type) { 20 | this.RegisterName(name); 21 | return true; 22 | } 23 | return false; 24 | } 25 | 26 | public func CloneRecord(name: CName, base: TweakDBID) -> Bool { 27 | if this.CloneRecord(TDBID.Create(NameToString(name)), base) { 28 | this.RegisterName(name); 29 | return true; 30 | } 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/TweakDBInterface.reds: -------------------------------------------------------------------------------- 1 | @addMethod(TweakDBInterface) 2 | public final static native func GetFlat(path: TweakDBID) -> Variant 3 | 4 | @addMethod(TweakDBInterface) 5 | public final static native func GetRecord(path: TweakDBID) -> ref 6 | 7 | @addMethod(TweakDBInterface) 8 | public final static native func GetRecords(type: CName) -> array> 9 | 10 | @addMethod(TweakDBInterface) 11 | public final static native func GetRecordCount(type: CName) -> Uint32 12 | 13 | @addMethod(TweakDBInterface) 14 | public final static native func GetRecordByIndex(type: CName, index: Uint32) -> ref 15 | 16 | @addMethod(TweakDBInterface) 17 | public final static func GetRecords(keys: array) -> array> { 18 | let records: array>; 19 | for key in keys { 20 | let record = TweakDBInterface.GetRecord(key); 21 | if IsDefined(record) { 22 | ArrayPush(records, record); 23 | } 24 | } 25 | return records; 26 | } 27 | 28 | @addMethod(TweakDBInterface) 29 | public final static func GetRecordIDs(type: CName) -> array { 30 | let ids: array; 31 | for record in TweakDBInterface.GetRecords(type) { 32 | ArrayPush(ids, record.GetID()); 33 | } 34 | return ids; 35 | } 36 | -------------------------------------------------------------------------------- /scripts/TweakDBManager.reds: -------------------------------------------------------------------------------- 1 | public abstract native class TweakDBManager { 2 | public final static native func SetFlat(id: TweakDBID, value: Variant) -> Bool 3 | public final static native func CreateRecord(id: TweakDBID, type: CName) -> Bool 4 | public final static native func CloneRecord(id: TweakDBID, base: TweakDBID) -> Bool 5 | public final static native func UpdateRecord(id: TweakDBID) -> Bool 6 | public final static native func RegisterEnum(id: TweakDBID) 7 | public final static native func RegisterName(name: CName) -> Bool 8 | public final static native func StartBatch() -> ref 9 | 10 | public final static func SetFlat(name: CName, value: Variant) -> Bool { 11 | if TweakDBManager.SetFlat(TDBID.Create(NameToString(name)), value) { 12 | TweakDBManager.RegisterName(name); 13 | return true; 14 | } 15 | return false; 16 | } 17 | 18 | public final static func CreateRecord(name: CName, type: CName) -> Bool { 19 | if TweakDBManager.CreateRecord(TDBID.Create(NameToString(name)), type) { 20 | TweakDBManager.RegisterName(name); 21 | return true; 22 | } 23 | return false; 24 | } 25 | 26 | public final static func CloneRecord(name: CName, base: TweakDBID) -> Bool { 27 | if TweakDBManager.CloneRecord(TDBID.Create(NameToString(name)), base) { 28 | TweakDBManager.RegisterName(name); 29 | return true; 30 | } 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/TweakXL.reds: -------------------------------------------------------------------------------- 1 | public abstract native class TweakXL { 2 | public static native func Require(version: String) -> Bool 3 | public static native func Version() -> String 4 | } 5 | -------------------------------------------------------------------------------- /scripts/VehicleScanner.reds: -------------------------------------------------------------------------------- 1 | @wrapMethod(ScannervehicleGameController) 2 | protected cb func OnVehicleManufacturerChanged(value: Variant) -> Bool { 3 | wrappedMethod(value); 4 | if this.m_isValidVehicleManufacturer { 5 | let vehicleManufacturer = FromVariant>(value); 6 | let iconRecord = TweakDBInterface.GetUIIconRecord(TDBID.Create("UIIcon." + vehicleManufacturer.GetVehicleManufacturer())); 7 | inkImageRef.SetAtlasResource(this.m_vehicleManufacturer, iconRecord.AtlasResourcePath()); 8 | } 9 | } -------------------------------------------------------------------------------- /src/App/Application.cpp: -------------------------------------------------------------------------------- 1 | #include "Application.hpp" 2 | #include "App/Environment.hpp" 3 | #include "App/Migration.hpp" 4 | #include "App/Project.hpp" 5 | #include "App/Stats/StatService.hpp" 6 | #include "App/Tweaks/TweakService.hpp" 7 | #include "Core/Foundation/RuntimeProvider.hpp" 8 | #include "Support/MinHook/MinHookProvider.hpp" 9 | #include "Support/RED4ext/RED4extProvider.hpp" 10 | #include "Support/RedLib/RedLibProvider.hpp" 11 | #include "Support/Spdlog/SpdlogProvider.hpp" 12 | 13 | App::Application::Application(HMODULE aHandle, const RED4ext::Sdk* aSdk) 14 | { 15 | Register(aHandle) 16 | ->SetBaseImagePathDepth(2); 17 | 18 | Register(); 19 | Register() 20 | ->AppendTimestampToLogName() 21 | ->CreateRecentLogSymlink(); 22 | Register(aHandle, aSdk) 23 | ->EnableAddressLibrary() 24 | ->RegisterScripts(Env::PluginScriptsDir()); 25 | Register(); 26 | 27 | Register(Env::GameVer(), Env::GameDir(), Env::TweaksDir(), 28 | Env::InheritanceMapPath(), Env::ExtraFlatsPath(), 29 | Env::RedModSourcesDir()); 30 | Register(); 31 | } 32 | 33 | void App::Application::OnStarting() 34 | { 35 | LogInfo("{} {} is starting...", Project::Name, Project::Version.to_string()); 36 | 37 | Migration::CleanUp(Env::LegacyScriptsDir()); 38 | } 39 | -------------------------------------------------------------------------------- /src/App/Application.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Application.hpp" 4 | #include "Core/Logging/LoggingAgent.hpp" 5 | 6 | namespace App 7 | { 8 | class Application 9 | : public Core::Application 10 | , public Core::LoggingAgent 11 | { 12 | public: 13 | explicit Application(HMODULE aHandle, const RED4ext::Sdk* aSdk = nullptr); 14 | 15 | protected: 16 | void OnStarting() override; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/App/Environment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Facades/Runtime.hpp" 4 | 5 | namespace App::Env 6 | { 7 | inline auto GameDir() 8 | { 9 | return Core::Runtime::GetRootDir(); 10 | } 11 | 12 | inline auto TweaksDir() 13 | { 14 | return GameDir() / L"r6" / L"tweaks"; 15 | } 16 | 17 | inline auto RedModSourcesDir() 18 | { 19 | return GameDir() / L"tools" / L"redmod" / L"tweaks"; 20 | } 21 | 22 | inline auto LegacyScriptsDir() 23 | { 24 | return GameDir() / L"r6" / L"scripts" / L"TweakXL"; 25 | } 26 | 27 | inline auto PluginDir() 28 | { 29 | return Core::Runtime::GetModuleDir(); 30 | } 31 | 32 | inline auto PluginScriptsDir() 33 | { 34 | return PluginDir() / L"Scripts"; 35 | } 36 | 37 | inline auto PluginDataDir() 38 | { 39 | return PluginDir() / L"Data"; 40 | } 41 | 42 | inline auto ExtraFlatsPath() 43 | { 44 | return PluginDataDir() / L"ExtraFlats.dat"; 45 | } 46 | 47 | inline auto InheritanceMapPath() 48 | { 49 | return PluginDataDir() / L"InheritanceMap.dat"; 50 | } 51 | 52 | inline const auto& GameVer() 53 | { 54 | return Core::Runtime::GetHost()->GetProductVer(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/App/Facade.cpp: -------------------------------------------------------------------------------- 1 | #include "Facade.hpp" 2 | #include "App/Environment.hpp" 3 | #include "App/Project.hpp" 4 | #include "App/Tweaks/TweakService.hpp" 5 | #include "Core/Facades/Container.hpp" 6 | 7 | bool App::Facade::RegisterDir(Red::CString& aPath) 8 | { 9 | return Core::Resolve()->RegisterDirectory(aPath.c_str()); 10 | } 11 | 12 | bool App::Facade::RegisterTweak(Red::CString& aPath) 13 | { 14 | return Core::Resolve()->RegisterTweak(aPath.c_str()); 15 | } 16 | 17 | void App::Facade::ImportAll() 18 | { 19 | Core::Resolve()->ImportTweaks(); 20 | } 21 | 22 | void App::Facade::ImportDir(Red::CString& aPath) 23 | { 24 | Red::Log::Debug("[TweakXL] The method TweakXL.ImportDir() is no longer supported. Use TweakXL.Reload() instead."); 25 | } 26 | 27 | void App::Facade::ImportTweak(Red::CString& aPath) 28 | { 29 | Red::Log::Debug("[TweakXL] The method TweakXL.ImportDir() is no longer supported. Use TweakXL.Reload() instead."); 30 | } 31 | 32 | void App::Facade::ExecuteAll() 33 | { 34 | Core::Resolve()->ExecuteTweaks(); 35 | } 36 | 37 | void App::Facade::ExecuteTweak(Red::CName aName) 38 | { 39 | Core::Resolve()->ExecuteTweak(aName); 40 | } 41 | 42 | void App::Facade::ExportMetadata() 43 | { 44 | Core::Resolve()->ExportMetadata(); 45 | } 46 | 47 | void App::Facade::Reload() 48 | { 49 | Core::Resolve()->LoadTweaks(true); 50 | } 51 | 52 | bool App::Facade::Require(Red::CString& aVersion) 53 | { 54 | const auto requirement = semver::from_string_noexcept(aVersion.c_str()); 55 | return requirement.has_value() && Project::Version >= requirement.value(); 56 | } 57 | 58 | Red::CString App::Facade::GetVersion() 59 | { 60 | return Project::Version.to_string().c_str(); 61 | } 62 | -------------------------------------------------------------------------------- /src/App/Facade.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Project.hpp" 4 | 5 | namespace App 6 | { 7 | class Facade : public Red::IScriptable 8 | { 9 | public: 10 | static bool RegisterDir(Red::CString& aPath); 11 | static bool RegisterTweak(Red::CString& aPath); 12 | static void ImportAll(); 13 | static void ImportDir(Red::CString& aPath); 14 | static void ImportTweak(Red::CString& aPath); 15 | static void ExecuteAll(); 16 | static void ExecuteTweak(Red::CName aName); 17 | static void ExportMetadata(); 18 | static void Reload(); 19 | static bool Require(Red::CString& aVersion); 20 | static Red::CString GetVersion(); 21 | 22 | RTTI_IMPL_TYPEINFO(App::Facade); 23 | }; 24 | } 25 | 26 | RTTI_DEFINE_CLASS(App::Facade, App::Project::Name, { 27 | RTTI_ABSTRACT(); 28 | RTTI_METHOD(RegisterDir); 29 | RTTI_METHOD(RegisterTweak); 30 | RTTI_METHOD(ImportAll); 31 | RTTI_METHOD(ImportDir); 32 | RTTI_METHOD(ImportTweak, "Import"); 33 | RTTI_METHOD(ExecuteAll); 34 | RTTI_METHOD(ExecuteTweak, "Execute"); 35 | RTTI_METHOD(ExportMetadata); 36 | RTTI_METHOD(Reload); 37 | RTTI_METHOD(Require); 38 | RTTI_METHOD(GetVersion, "Version"); 39 | }) 40 | -------------------------------------------------------------------------------- /src/App/Migration.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace App::Migration 4 | { 5 | inline void CleanUp(const std::filesystem::path& aPath) 6 | { 7 | std::error_code error; 8 | if (std::filesystem::exists(aPath, error)) 9 | { 10 | std::filesystem::remove_all(aPath, error); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/App/Stats/StatService.cpp: -------------------------------------------------------------------------------- 1 | #include "StatService.hpp" 2 | #include "App/Tweaks/TweakService.hpp" 3 | #include "Core/Facades/Container.hpp" 4 | 5 | namespace 6 | { 7 | constexpr auto BaseStatPrefix = "BaseStats."; 8 | constexpr auto BaseStatPrefixLength = std::char_traits::length(BaseStatPrefix); 9 | constexpr auto BaseStatCount = static_cast(Red::game::data::StatType::Count); 10 | constexpr auto InvalidStat = static_cast(Red::game::data::StatType::Invalid); 11 | 12 | bool s_statTypesModified = false; 13 | } 14 | 15 | void App::StatService::OnBootstrap() 16 | { 17 | HookAfter(&OnInitializeStats).OrThrow(); 18 | } 19 | 20 | void App::StatService::OnInitializeStats(void* aSystem) 21 | { 22 | RegisterStats(aSystem, Core::Resolve()->GetChangelog().GetAffectedRecords()); 23 | RegisterStats(aSystem, Core::Resolve()->GetManager().GetEnums()); 24 | } 25 | 26 | void App::StatService::RegisterStats(void* aStatSystem, const Core::Set& aRecordIDs) 27 | { 28 | auto statRecords = Raw::StatsDataSystem::StatRecords::Ptr(aStatSystem); 29 | auto statTypeEnum = Red::GetDescriptor(); 30 | 31 | auto& tweakManager = Core::Resolve()->GetManager(); 32 | 33 | for (const auto& recordID : aRecordIDs) 34 | { 35 | const auto& recordName = tweakManager.GetName(recordID); 36 | 37 | if (!recordName.starts_with(BaseStatPrefix)) 38 | continue; 39 | 40 | auto enumNameProp = tweakManager.GetFlat({recordID, ".enumName"}); 41 | 42 | if (!enumNameProp) 43 | continue; 44 | 45 | auto enumName = enumNameProp.As().c_str(); 46 | 47 | if (statTypeEnum->HasOption(enumName)) 48 | continue; 49 | 50 | if (recordName.substr(BaseStatPrefixLength) != enumName) 51 | { 52 | LogError("{}: Enum name must match the record name.", recordName); 53 | continue; 54 | } 55 | 56 | if (statRecords->size == BaseStatCount) 57 | { 58 | // Add dummy entries for "Count" and "Invalid" 59 | statRecords->EmplaceBack(); 60 | statRecords->EmplaceBack(); 61 | } 62 | 63 | const auto enumValue = statRecords->size; 64 | 65 | statTypeEnum->AddOption(enumValue, enumName); 66 | statRecords->PushBack(recordID); 67 | 68 | if (!s_statTypesModified) 69 | { 70 | s_statTypesModified = true; 71 | Hook(&OnGetStatRange).OrThrow(); 72 | Hook(&OnGetStatFlags).OrThrow(); 73 | Hook(&OnCheckStatFlag).OrThrow(); 74 | } 75 | 76 | { 77 | const auto record = tweakManager.GetRecord(recordID); 78 | Raw::StatRecord::EnumValue::Ref(record) = enumValue; 79 | } 80 | } 81 | } 82 | 83 | uint64_t* App::StatService::OnGetStatRange(void* aSystem, uint64_t* aRange, uint32_t aStat) 84 | { 85 | if (aStat != InvalidStat) 86 | { 87 | auto& statParams = Raw::StatsDataSystem::StatParams::Ref(aSystem); 88 | auto& statLock = Raw::StatsDataSystem::StatLock::Ref(aSystem); 89 | 90 | std::shared_lock _(statLock); 91 | if (aStat < statParams.size) 92 | { 93 | *aRange = statParams[aStat].range; 94 | return aRange; 95 | } 96 | } 97 | 98 | *aRange = 0; 99 | return aRange; 100 | } 101 | 102 | uint32_t App::StatService::OnGetStatFlags(void* aSystem, uint32_t aStat) 103 | { 104 | if (aStat != InvalidStat) 105 | { 106 | auto& statParams = Raw::StatsDataSystem::StatParams::Ref(aSystem); 107 | auto& statLock = Raw::StatsDataSystem::StatLock::Ref(aSystem); 108 | 109 | std::shared_lock _(statLock); 110 | if (aStat < statParams.size) 111 | { 112 | return statParams[aStat].flags; 113 | } 114 | } 115 | 116 | return 0; 117 | } 118 | 119 | bool App::StatService::OnCheckStatFlag(void* aSystem, uint32_t aStat, uint32_t aFlag) 120 | { 121 | if (aStat != InvalidStat) 122 | { 123 | auto& statParams = Raw::StatsDataSystem::StatParams::Ref(aSystem); 124 | auto& statLock = Raw::StatsDataSystem::StatLock::Ref(aSystem); 125 | 126 | std::shared_lock _(statLock); 127 | if (aStat < statParams.size) 128 | { 129 | return statParams[aStat].flags & aFlag; 130 | } 131 | } 132 | 133 | return false; 134 | } 135 | -------------------------------------------------------------------------------- /src/App/Stats/StatService.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Feature.hpp" 4 | #include "Core/Hooking/HookingAgent.hpp" 5 | #include "Core/Logging/LoggingAgent.hpp" 6 | #include "Red/StatsDataSystem.hpp" 7 | 8 | namespace App 9 | { 10 | class StatService 11 | : public Core::Feature 12 | , public Core::HookingAgent 13 | , public Core::LoggingAgent 14 | { 15 | protected: 16 | void OnBootstrap() override; 17 | 18 | static void OnInitializeStats(void* aSystem); 19 | static uint64_t* OnGetStatRange(void* aSystem, uint64_t* aRange, uint32_t aStat); 20 | static uint32_t OnGetStatFlags(void* aSystem, uint32_t aStat); 21 | static bool OnCheckStatFlag(void* aSystem, uint32_t aStat, uint32_t aFlag); 22 | 23 | static void RegisterStats(void* aStatSystem, const Core::Set& aRecordIDs); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/App/Tweaks/Batch/TweakChangelog.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Logging/LoggingAgent.hpp" 4 | #include "Red/TweakDB/Manager.hpp" 5 | 6 | namespace App 7 | { 8 | class TweakChangelog : public Core::LoggingAgent 9 | { 10 | public: 11 | bool RegisterRecord(Red::TweakDBID aRecordId); 12 | 13 | bool RegisterAssignment(Red::TweakDBID aFlatId, Red::Instance aOldValue, Red::Instance aNewValue); 14 | bool RegisterInsertion(Red::TweakDBID aFlatId, int32_t aIndex, const Red::InstancePtr<>& aInstance); 15 | bool RegisterDeletion(Red::TweakDBID aFlatId, int32_t aIndex, const Red::InstancePtr<>& aInstance); 16 | void ForgetChanges(Red::TweakDBID aFlatId); 17 | 18 | void RegisterForeignKey(Red::TweakDBID aForeignKey, Red::TweakDBID aFlatId); 19 | void ForgetForeignKey(Red::TweakDBID aForeignKey); 20 | void ForgetForeignKeys(); 21 | 22 | void RegisterResourcePath(Red::ResourcePath aPath, Red::TweakDBID aFlatId); 23 | void ForgetResourcePath(Red::ResourcePath aPath); 24 | void ForgetResourcePaths(); 25 | 26 | void CheckForIssues(const Core::SharedPtr& aManager); 27 | void RevertChanges(const Core::SharedPtr& aManager); 28 | 29 | [[nodiscard]] const Core::Set& GetAffectedRecords() const; 30 | 31 | private: 32 | struct AssignmentEntry 33 | { 34 | Red::Instance previous; 35 | Red::Instance current; 36 | }; 37 | 38 | struct MutationEntry 39 | { 40 | Core::SortedMap> insertions; 41 | Core::SortedMap> deletions; 42 | }; 43 | 44 | Core::Set m_records; 45 | Core::Map m_assignments; 46 | Core::Map m_mutations; 47 | Core::Map m_foreignKeys; 48 | Core::Map m_resourcePaths; 49 | Core::Set m_ownedKeys; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/App/Tweaks/Batch/TweakChangeset.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Tweaks/Batch/TweakChangelog.hpp" 4 | #include "Core/Logging/LoggingAgent.hpp" 5 | #include "Red/TweakDB/Manager.hpp" 6 | 7 | namespace App 8 | { 9 | class TweakChangeset 10 | : public Core::LoggingAgent 11 | , public Core::ShareFromThis 12 | { 13 | public: 14 | struct FlatEntry 15 | { 16 | const Red::CBaseRTTIType* type; 17 | Red::InstancePtr<> value; 18 | }; 19 | 20 | struct RecordEntry 21 | { 22 | const Red::CClass* type; 23 | Red::TweakDBID sourceId; 24 | }; 25 | 26 | struct InsertionEntry 27 | { 28 | const Red::CBaseRTTIType* type; 29 | Red::InstancePtr<> value; 30 | bool unique; 31 | }; 32 | 33 | struct DeletionEntry 34 | { 35 | const Red::CBaseRTTIType* type; 36 | Red::InstancePtr<> value; 37 | }; 38 | 39 | struct MergingEntry 40 | { 41 | Red::TweakDBID sourceId; 42 | }; 43 | 44 | struct MutationEntry 45 | { 46 | Core::Vector deletions; 47 | Core::Vector appendings; 48 | Core::Vector prependings; 49 | Core::Vector appendingMerges; 50 | Core::Vector prependingMerges; 51 | Red::TweakDBID baseId; 52 | bool deleteAll; 53 | }; 54 | 55 | struct ReinheritanceEntry 56 | { 57 | Red::TweakDBID sourceId; 58 | std::string appendix; 59 | }; 60 | 61 | bool SetFlat(Red::TweakDBID aFlatId, const Red::CBaseRTTIType* aType, const Red::InstancePtr<>& aValue); 62 | bool ReinheritFlat(Red::TweakDBID aFlatId, Red::TweakDBID aSourceId, const std::string& aAppendix); 63 | 64 | bool MakeRecord(Red::TweakDBID aRecordId, const Red::CClass* aType, Red::TweakDBID aSourceId = {}); 65 | bool UpdateRecord(Red::TweakDBID aRecordId); 66 | 67 | bool AppendElement(Red::TweakDBID aFlatId, const Red::CBaseRTTIType* aType, 68 | const Red::InstancePtr<>& aValue, bool aUnique = false); 69 | bool PrependElement(Red::TweakDBID aFlatId, const Red::CBaseRTTIType* aType, 70 | const Red::InstancePtr<>& aValue, bool aUnique = false); 71 | bool RemoveElement(Red::TweakDBID aFlatId, const Red::CBaseRTTIType* aType, 72 | const Red::InstancePtr<>& aValue); 73 | bool RemoveAllElements(Red::TweakDBID aFlatId); 74 | bool AppendFrom(Red::TweakDBID aFlatId, Red::TweakDBID aSourceId); 75 | bool PrependFrom(Red::TweakDBID aFlatId, Red::TweakDBID aSourceId); 76 | 77 | bool RegisterName(Red::TweakDBID aId, const std::string& aName); 78 | 79 | const FlatEntry* GetFlat(Red::TweakDBID aFlatId); 80 | const RecordEntry* GetRecord(Red::TweakDBID aRecordId); 81 | const Red::CClass* GetRecordType(Red::TweakDBID aRecordId); 82 | bool HasRecord(Red::TweakDBID aRecordId); 83 | 84 | bool IsEmpty(); 85 | 86 | void Commit(const Core::SharedPtr& aManager, 87 | const Core::SharedPtr& aChangelog); 88 | 89 | private: 90 | using ElementChange = std::pair>; 91 | 92 | bool IsCommitFinished(); 93 | void StartCommitJob(); 94 | void FinishCommitJob(); 95 | 96 | template 97 | void StartAsyncCommitJob(J&& aJob) 98 | { 99 | StartCommitJob(); 100 | Red::JobQueue jobQueue; 101 | jobQueue.Dispatch(std::forward(aJob)); 102 | jobQueue.Dispatch([self = ToShared()]{ self->FinishCommitJob(); }); 103 | } 104 | 105 | static int32_t FindElement(const Red::CRTTIArrayType* aArrayType, void* aArray, void* aValue); 106 | static bool InArray(const Red::CRTTIArrayType* aArrayType, void* aArray, void* aValue); 107 | static bool IsSkip(const Red::CRTTIArrayType* aArrayType, void* aValue, int32_t aLevel, 108 | const Core::Vector& aChanges); 109 | 110 | Core::WeakPtr m_self; 111 | Core::Vector m_orderedRecords; 112 | Core::Map m_pendingRecords; 113 | Core::Map m_pendingMutations; 114 | Core::Map m_pendingFlats; 115 | Core::Map m_reinheritedProps; 116 | Core::Map m_pendingNames; 117 | 118 | std::mutex m_commitMutex; 119 | int32_t m_totalCommitChunks{0}; 120 | int32_t m_finishedCommitChunks{0}; 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /src/App/Tweaks/Declarative/Red/RedReader.Types.cpp: -------------------------------------------------------------------------------- 1 | #include "RedReader.hpp" 2 | 3 | Red::CName App::RedReader::GetFlatTypeName(const Core::SharedPtr& aFlat) 4 | { 5 | if (aFlat->isArray) 6 | { 7 | switch (aFlat->type) 8 | { 9 | case Red::ETweakFlatType::Int: return Red::ERTDBFlatType::IntArray; 10 | case Red::ETweakFlatType::Float: return Red::ERTDBFlatType::FloatArray; 11 | case Red::ETweakFlatType::Bool: return Red::ERTDBFlatType::BoolArray; 12 | case Red::ETweakFlatType::String: return Red::ERTDBFlatType::StringArray; 13 | case Red::ETweakFlatType::CName: return Red::ERTDBFlatType::CNameArray; 14 | case Red::ETweakFlatType::ResRef: return Red::ERTDBFlatType::ResRefArray; 15 | case Red::ETweakFlatType::LocKey: return Red::ERTDBFlatType::LocKeyArray; 16 | case Red::ETweakFlatType::ForeignKey: return Red::ERTDBFlatType::TweakDBIDArray; 17 | case Red::ETweakFlatType::Quaternion: return Red::ERTDBFlatType::QuaternionArray; 18 | case Red::ETweakFlatType::EulerAngles: return Red::ERTDBFlatType::EulerAnglesArray; 19 | case Red::ETweakFlatType::Vector3: return Red::ERTDBFlatType::Vector3Array; 20 | case Red::ETweakFlatType::Vector2: return Red::ERTDBFlatType::Vector2Array; 21 | case Red::ETweakFlatType::Color: return Red::ERTDBFlatType::ColorArray; 22 | case Red::ETweakFlatType::Undefined: break; 23 | } 24 | } 25 | else 26 | { 27 | switch (aFlat->type) 28 | { 29 | case Red::ETweakFlatType::Int: return Red::ERTDBFlatType::Int; 30 | case Red::ETweakFlatType::Float: return Red::ERTDBFlatType::Float; 31 | case Red::ETweakFlatType::Bool: return Red::ERTDBFlatType::Bool; 32 | case Red::ETweakFlatType::String: return Red::ERTDBFlatType::String; 33 | case Red::ETweakFlatType::CName: return Red::ERTDBFlatType::CName; 34 | case Red::ETweakFlatType::ResRef: return Red::ERTDBFlatType::ResRef; 35 | case Red::ETweakFlatType::LocKey: return Red::ERTDBFlatType::LocKey; 36 | case Red::ETweakFlatType::ForeignKey: return Red::ERTDBFlatType::TweakDBID; 37 | case Red::ETweakFlatType::Quaternion: return Red::ERTDBFlatType::Quaternion; 38 | case Red::ETweakFlatType::EulerAngles: return Red::ERTDBFlatType::EulerAngles; 39 | case Red::ETweakFlatType::Vector3: return Red::ERTDBFlatType::Vector3; 40 | case Red::ETweakFlatType::Vector2: return Red::ERTDBFlatType::Vector2; 41 | case Red::ETweakFlatType::Color: return Red::ERTDBFlatType::Color; 42 | case Red::ETweakFlatType::Undefined: break; 43 | } 44 | } 45 | 46 | return {}; 47 | } 48 | -------------------------------------------------------------------------------- /src/App/Tweaks/Declarative/Red/RedReader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Tweaks/Declarative/TweakReader.hpp" 4 | #include "Core/Logging/LoggingAgent.hpp" 5 | #include "Red/TweakDB/Source/Source.hpp" 6 | #include "Red/Value.hpp" 7 | 8 | namespace App 9 | { 10 | class RedReader 11 | : public BaseTweakReader 12 | , public Core::LoggingAgent 13 | { 14 | public: 15 | RedReader(Core::SharedPtr aManager, Core::SharedPtr aContext); 16 | ~RedReader() override = default; 17 | 18 | bool Load(const std::filesystem::path& aPath) override; 19 | [[nodiscard]] bool IsLoaded() const override; 20 | void Unload() override; 21 | void Read(TweakChangeset& aChangeset) override; 22 | 23 | static Red::CName GetFlatTypeName(const Red::TweakFlatPtr& aFlat); 24 | 25 | private: 26 | struct GroupState 27 | { 28 | bool isResolved{}; 29 | bool isCompatible{}; 30 | bool isRedefined{}; 31 | bool isRecord{}; 32 | bool isOriginalBase{}; 33 | bool isProcessed{}; 34 | 35 | const Red::CClass* requiredType{}; 36 | const Red::CClass* resolvedType{}; 37 | Red::TweakDBID sourceId; 38 | 39 | std::string groupPath; 40 | std::string groupName; 41 | Red::TweakDBID recordId; 42 | }; 43 | 44 | struct FlatState 45 | { 46 | bool isResolved{}; 47 | bool isCompatible{}; 48 | bool isRedefined{}; 49 | bool isProcessed{}; 50 | 51 | bool isArray{}; 52 | bool isForeignKey{}; 53 | const Red::CBaseRTTIType* requiredType{}; 54 | const Red::CClass* requiredKey{}; 55 | const Red::CBaseRTTIType* resolvedType{}; 56 | const Red::CBaseRTTIType* elementType{}; 57 | const Red::CClass* resolvedKey{}; 58 | 59 | std::string flatPath; 60 | std::string flatName; 61 | Red::TweakDBID flatId; 62 | }; 63 | 64 | using GroupStatePtr = Core::SharedPtr; 65 | using FlatStatePtr = Core::SharedPtr; 66 | 67 | GroupStatePtr HandleGroup(App::TweakChangeset& aChangeset, const Red::TweakGroupPtr& aGroup, 68 | const std::string& aParentName, const std::string& aParentPath); 69 | 70 | GroupStatePtr HandleInline(App::TweakChangeset& aChangeset, const Red::TweakGroupPtr& aGroup, 71 | const std::string& aParentName, const std::string& aParentPath, 72 | const Red::CClass* aRequiredType, int32_t aInlineIndex = 0); 73 | 74 | FlatStatePtr HandleFlat(App::TweakChangeset& aChangeset, const Red::TweakFlatPtr& aFlat, 75 | const std::string& aParentName, const std::string& aParentPath, 76 | const Red::CBaseRTTIType* aRequiredType = nullptr, 77 | const Red::CClass* aForeignType = nullptr); 78 | 79 | GroupStatePtr ResolveGroupState(App::TweakChangeset& aChangeset, const Red::TweakGroupPtr& aGroup, 80 | const std::string& aParentName, const std::string& aParentPath, 81 | const Red::CClass* aBaseType = nullptr, int32_t aInlineIndex = 0); 82 | 83 | FlatStatePtr ResolveFlatState(App::TweakChangeset& aChangeset, const Red::TweakFlatPtr& aFlat, 84 | const std::string& aParentName, const std::string& aParentPath, 85 | const Red::CBaseRTTIType* aRequiredType = nullptr, 86 | const Red::CClass* aForeignType = nullptr); 87 | 88 | Red::InstancePtr<> MakeValue(const FlatStatePtr& aState, const Red::TweakValuePtr& aValue); 89 | Red::InstancePtr<> MakeValue(const FlatStatePtr& aState, const Core::Vector& aValues = {}); 90 | 91 | bool CheckConditions(const Core::Vector& aTags); 92 | 93 | std::filesystem::path m_path; 94 | Red::TweakSourcePtr m_source; 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /src/App/Tweaks/Declarative/TweakImporter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Tweaks/Batch/TweakChangelog.hpp" 4 | #include "App/Tweaks/Batch/TweakChangeset.hpp" 5 | #include "App/Tweaks/Declarative/TweakReader.hpp" 6 | #include "App/Tweaks/TweakContext.hpp" 7 | #include "Core/Logging/LoggingAgent.hpp" 8 | #include "Red/TweakDB/Manager.hpp" 9 | 10 | namespace App 11 | { 12 | class TweakImporter : Core::LoggingAgent 13 | { 14 | public: 15 | TweakImporter(Core::SharedPtr aManager, Core::SharedPtr aContext); 16 | 17 | void ImportTweaks(const Core::Vector& aImportPaths, 18 | const Core::SharedPtr& aChangelog = nullptr, 19 | bool aDryRun = false); 20 | 21 | private: 22 | bool Read(const Core::SharedPtr& aChangeset, 23 | const std::filesystem::path& aPath, 24 | const std::filesystem::path& aDir); 25 | bool Apply(const Core::SharedPtr& aChangeset, 26 | const Core::SharedPtr& aChangelog); 27 | 28 | static bool IsFirstPriority(const std::filesystem::path& aPath); 29 | static bool IsLastPriority(const std::filesystem::path& aPath); 30 | 31 | Core::SharedPtr m_manager; 32 | Core::SharedPtr m_context; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/App/Tweaks/Declarative/TweakReader.cpp: -------------------------------------------------------------------------------- 1 | #include "TweakReader.hpp" 2 | #include "App/Utils/Str.hpp" 3 | 4 | namespace 5 | { 6 | constexpr auto PathSeparator = "."; 7 | constexpr auto HashSeparator = "|"; 8 | 9 | constexpr auto GroupSeparator = "."; 10 | constexpr auto PropSeparator = "."; 11 | constexpr auto InlineSeparator = "$"; 12 | 13 | constexpr auto IndexOpen = "["; 14 | constexpr auto IndexClose = "]"; 15 | 16 | constexpr auto ForeignKeyOpen = "<"; 17 | constexpr auto ForeignKeyClose = ">"; 18 | } 19 | 20 | App::BaseTweakReader::BaseTweakReader(Core::SharedPtr aManager, 21 | Core::SharedPtr aContext) 22 | : m_manager(std::move(aManager)) 23 | , m_reflection(m_manager->GetReflection()) 24 | , m_context(std::move(aContext)) 25 | { 26 | } 27 | 28 | bool App::BaseTweakReader::IsOriginalBaseRecord(Red::TweakDBID aRecordId) 29 | { 30 | return m_reflection->IsOriginalBaseRecord(aRecordId); 31 | } 32 | 33 | std::string App::BaseTweakReader::ComposeGroupName(const std::string& aParentName, const std::string& aGroupName) 34 | { 35 | if (aParentName.empty()) 36 | return aGroupName; 37 | 38 | if (aGroupName.empty()) 39 | return aParentName; 40 | 41 | auto groupName = aParentName; 42 | groupName.append(GroupSeparator); 43 | groupName.append(aGroupName); 44 | 45 | return groupName; 46 | } 47 | 48 | std::string App::BaseTweakReader::ComposeFlatName(const std::string& aParentName, const std::string& aFlatName) 49 | { 50 | if (aParentName.empty()) 51 | return aFlatName; 52 | 53 | if (aFlatName.empty()) 54 | return aParentName; 55 | 56 | auto flatName = aParentName; 57 | flatName.append(PropSeparator); 58 | flatName.append(aFlatName); 59 | 60 | return flatName; 61 | } 62 | 63 | std::string App::BaseTweakReader::ComposeInlineName(const std::string& aParentName, const Red::CClass* aRecordType, 64 | const std::filesystem::path& aSource, int32_t aItemIndex) 65 | { 66 | auto inlineHash = aSource.string(); 67 | inlineHash.append(HashSeparator); 68 | inlineHash.append(aParentName); 69 | inlineHash.append(HashSeparator); 70 | inlineHash.append(aRecordType->name.ToString()); 71 | 72 | if (aItemIndex >= 0) 73 | { 74 | inlineHash.append(HashSeparator); 75 | inlineHash.append(std::to_string(aItemIndex)); 76 | inlineHash.append(HashSeparator); 77 | inlineHash.append(std::to_string(++m_inlineIndexSuffix[inlineHash])); 78 | } 79 | 80 | auto inlineName = aParentName; 81 | inlineName.append(InlineSeparator); 82 | inlineName.append(ToHex(Red::FNV1a32(inlineHash.data(), inlineHash.size()))); 83 | 84 | return inlineName; 85 | } 86 | 87 | std::string App::BaseTweakReader::ComposePath(const std::string& aParentPath, const std::string& aItemName) 88 | { 89 | if (aParentPath.empty()) 90 | return aItemName; 91 | 92 | if (aItemName.empty()) 93 | return aParentPath; 94 | 95 | auto itemPath = aParentPath; 96 | itemPath.append(PathSeparator); 97 | itemPath.append(aItemName); 98 | 99 | return itemPath; 100 | } 101 | 102 | std::string App::BaseTweakReader::ComposePath(const std::string& aParentPath, int32_t aItemIndex) 103 | { 104 | if (aParentPath.empty()) 105 | return {}; 106 | 107 | if (aItemIndex < 0) 108 | return aParentPath; 109 | 110 | auto itemPath = aParentPath; 111 | itemPath.append(IndexOpen); 112 | itemPath.append(std::to_string(aItemIndex)); 113 | itemPath.append(IndexClose); 114 | 115 | return itemPath; 116 | } 117 | 118 | const Red::CBaseRTTIType* App::BaseTweakReader::ResolveFlatInstanceType(App::TweakChangeset& aChangeset, 119 | Red::TweakDBID aFlatId) 120 | { 121 | const auto existingFlat = m_manager->GetFlat(aFlatId); 122 | if (existingFlat.instance) 123 | { 124 | return existingFlat.type; 125 | } 126 | 127 | const auto pendingFlat = aChangeset.GetFlat(aFlatId); 128 | if (pendingFlat) 129 | { 130 | return pendingFlat->type; 131 | } 132 | 133 | return nullptr; 134 | } 135 | 136 | const Red::CClass* App::BaseTweakReader::ResolveRecordInstanceType(App::TweakChangeset& aChangeset, 137 | Red::TweakDBID aRecordId) 138 | { 139 | if (!aRecordId.IsValid()) 140 | return nullptr; 141 | 142 | const auto existingRecordType = m_manager->GetRecordType(aRecordId); 143 | if (existingRecordType) 144 | { 145 | return existingRecordType; 146 | } 147 | 148 | const auto pendingRecord = aChangeset.GetRecord(aRecordId); 149 | if (pendingRecord) 150 | { 151 | return pendingRecord->type; 152 | } 153 | 154 | return nullptr; 155 | } 156 | 157 | std::string App::BaseTweakReader::ToName(const Red::CClass* aType) 158 | { 159 | return m_reflection->GetRecordShortName(aType->GetName()); 160 | } 161 | 162 | std::string App::BaseTweakReader::ToName(const Red::CBaseRTTIType* aType, const Red::CClass* aKey) 163 | { 164 | if (!aType) 165 | return ""; 166 | 167 | std::string name = aType->GetName().ToString(); 168 | 169 | if (aKey) 170 | { 171 | name.append(ForeignKeyOpen); 172 | name.append(m_reflection->GetRecordShortName(aKey->name)); 173 | name.append(ForeignKeyClose); 174 | } 175 | 176 | return name; 177 | } 178 | -------------------------------------------------------------------------------- /src/App/Tweaks/Declarative/TweakReader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Tweaks/Batch/TweakChangeset.hpp" 4 | #include "App/Tweaks/TweakContext.hpp" 5 | 6 | namespace App 7 | { 8 | class ITweakReader 9 | { 10 | public: 11 | virtual ~ITweakReader() = default; 12 | virtual bool Load(const std::filesystem::path& aPath) = 0; 13 | [[nodiscard]] virtual bool IsLoaded() const = 0; 14 | virtual void Unload() = 0; 15 | virtual void Read(TweakChangeset& aChangeset) = 0; 16 | }; 17 | 18 | class BaseTweakReader : public ITweakReader 19 | { 20 | public: 21 | BaseTweakReader(Core::SharedPtr aManager, Core::SharedPtr aContext); 22 | 23 | protected: 24 | static std::string ComposePath(const std::string& aParentPath, const std::string& aItemName); 25 | static std::string ComposePath(const std::string& aParentPath, int32_t aItemIndex); 26 | 27 | static std::string ComposeGroupName(const std::string& aParentName, const std::string& aGroupName); 28 | static std::string ComposeFlatName(const std::string& aParentName, const std::string& aFlatName); 29 | std::string ComposeInlineName(const std::string& aParentName, const Red::CClass* aRecordType, 30 | const std::filesystem::path& aSource, int32_t aItemIndex = -1); 31 | 32 | const Red::CBaseRTTIType* ResolveFlatInstanceType(TweakChangeset& aChangeset, Red::TweakDBID aFlatId); 33 | const Red::CClass* ResolveRecordInstanceType(TweakChangeset& aChangeset, Red::TweakDBID aRecordId); 34 | 35 | bool IsOriginalBaseRecord(Red::TweakDBID aRecordId); 36 | 37 | std::string ToName(const Red::CClass* aType); 38 | std::string ToName(const Red::CBaseRTTIType* aType, const Red::CClass* aKey = nullptr); 39 | 40 | Core::SharedPtr m_manager; 41 | Core::SharedPtr m_reflection; 42 | Core::SharedPtr m_context; 43 | Core::Map m_inlineIndexSuffix; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/App/Tweaks/Declarative/Yaml/YamlReader.Legacy.cpp: -------------------------------------------------------------------------------- 1 | #include "YamlReader.hpp" 2 | 3 | namespace 4 | { 5 | constexpr auto TypeAttrKey = "$type"; 6 | constexpr auto ValueAttrKey = "$value"; 7 | 8 | constexpr auto LegacyGroupsNodeKey = "groups"; 9 | constexpr auto LegacyMembersNodeKey = "members"; 10 | constexpr auto LegacyFlatsNodeKey = "flats"; 11 | constexpr auto LegacyTypeNodeKey = "type"; 12 | constexpr auto LegacyValueNodeKey = "value"; 13 | } 14 | 15 | void App::YamlReader::ConvertLegacyNodes() 16 | { 17 | const auto groupsNode = m_data[LegacyGroupsNodeKey]; 18 | 19 | if (groupsNode.IsMap()) 20 | { 21 | for (const auto& groupIt : groupsNode) 22 | { 23 | const auto groupKey = groupIt.first; 24 | const auto groupNode = groupIt.second; 25 | 26 | if (!groupKey.IsDefined() || !groupNode.IsMap()) 27 | continue; 28 | 29 | const auto groupTypeNode = groupNode[LegacyTypeNodeKey]; 30 | const auto groupMembersNode = groupNode[LegacyMembersNodeKey]; 31 | 32 | if (!groupTypeNode.IsDefined() || !groupMembersNode.IsMap()) 33 | continue; 34 | 35 | auto convertedNode = YAML::Node(); 36 | convertedNode[TypeAttrKey] = groupTypeNode; 37 | 38 | for (const auto& memberIt : groupMembersNode) 39 | { 40 | const auto memberKey = memberIt.first; 41 | const auto memberNode = memberIt.second; 42 | 43 | if (!memberKey.IsDefined() || !memberNode.IsMap()) 44 | continue; 45 | 46 | const auto memberTypeNode = groupNode[LegacyTypeNodeKey]; 47 | const auto memberValueNode = groupNode[LegacyValueNodeKey]; 48 | 49 | if (!memberTypeNode.IsDefined() || !memberValueNode.IsDefined()) 50 | continue; 51 | 52 | convertedNode[memberKey] = memberValueNode; 53 | } 54 | 55 | m_data[groupKey] = convertedNode; 56 | } 57 | 58 | m_data.remove(LegacyGroupsNodeKey); 59 | } 60 | 61 | const auto flatsNode = m_data[LegacyFlatsNodeKey]; 62 | 63 | if (flatsNode.IsMap()) 64 | { 65 | for (const auto& flatIt : flatsNode) 66 | { 67 | const auto flatKey = flatIt.first; 68 | const auto flatNode = flatIt.second; 69 | 70 | if (!flatKey.IsDefined() || !flatNode.IsMap()) 71 | continue; 72 | 73 | const auto flatTypeNode = flatNode[LegacyTypeNodeKey]; 74 | const auto flatValueNode = flatNode[LegacyValueNodeKey]; 75 | 76 | if (!flatTypeNode.IsDefined() || !flatValueNode.IsDefined()) 77 | continue; 78 | 79 | auto convertedNode = YAML::Node(); 80 | convertedNode[TypeAttrKey] = flatTypeNode; 81 | convertedNode[ValueAttrKey] = flatValueNode; 82 | 83 | m_data[flatKey] = convertedNode; 84 | } 85 | 86 | m_data.remove(LegacyFlatsNodeKey); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/App/Tweaks/Declarative/Yaml/YamlReader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Tweaks/Batch/TweakChangeset.hpp" 4 | #include "App/Tweaks/Declarative/TweakReader.hpp" 5 | #include "Core/Logging/LoggingAgent.hpp" 6 | 7 | namespace App 8 | { 9 | class YamlReader 10 | : public BaseTweakReader 11 | , public Core::LoggingAgent 12 | { 13 | public: 14 | YamlReader(Core::SharedPtr aManager, Core::SharedPtr aContext); 15 | ~YamlReader() override = default; 16 | 17 | bool Load(const std::filesystem::path& aPath) override; 18 | [[nodiscard]] bool IsLoaded() const override; 19 | void Unload() override; 20 | void Read(TweakChangeset& aChangeset) override; 21 | 22 | private: 23 | enum class PropertyMode 24 | { 25 | Strict, 26 | Auto, 27 | }; 28 | 29 | void HandleTopNode(TweakChangeset& aChangeset, PropertyMode aPropMode, const std::string& aName, 30 | const YAML::Node& aNode); 31 | void HandleFlatNode(TweakChangeset& aChangeset, const std::string& aName, const YAML::Node& aNode, 32 | const Red::CBaseRTTIType* aType = nullptr); 33 | void HandleRecordNode(TweakChangeset& aChangeset, PropertyMode aPropMode, const std::string& aRecordPath, 34 | const std::string& aRecordName, const YAML::Node& aNode, const Red::CClass* aRecordType, 35 | Red::TweakDBID aSourceId = {}); 36 | bool ResolveInlineNode(App::TweakChangeset& aChangeset, const std::string& aPath, const YAML::Node& aNode, 37 | const Red::CClass*& aForeignType, Red::TweakDBID& aSourceId); 38 | bool HandleMutations(TweakChangeset& aChangeset, const std::string& aPath, const std::string& aName, 39 | const YAML::Node& aNode, const Red::CBaseRTTIType* aElementType); 40 | void UpdateFlatOwner(TweakChangeset& aChangeset, const std::string& aName); 41 | 42 | bool CheckConditions(const YAML::Node& aNode); 43 | static PropertyMode ResolvePropertyMode(const YAML::Node& aNode, PropertyMode aDefault = PropertyMode::Strict); 44 | const Red::CBaseRTTIType* ResolveFlatType(const YAML::Node& aNode); 45 | const Red::CBaseRTTIType* ResolveFlatType(Red::CName aName); 46 | const Red::CClass* ResolveRecordType(const YAML::Node& aNode); 47 | Red::TweakDBID ResolveTweakDBID(const YAML::Node& aNode); 48 | 49 | template 50 | Red::InstancePtr ConvertValue(const YAML::Node& aNode, bool aStrict = false); 51 | 52 | template 53 | bool ConvertValue(const YAML::Node& aNode, Red::InstancePtr<>& aValue, bool aStrict = false); 54 | 55 | template 56 | Red::InstancePtr> ConvertArray(const YAML::Node& aNode, bool aStrict = false); 57 | 58 | template 59 | bool ConvertArray(const YAML::Node& aNode, Red::InstancePtr<>& aValue, bool aStrict = false); 60 | 61 | Red::InstancePtr<> MakeValue(Red::CName aTypeName, const YAML::Node& aNode); 62 | Red::InstancePtr<> MakeValue(const Red::CBaseRTTIType* aType, const YAML::Node& aNode); 63 | std::pair> TryMakeValue(const YAML::Node& aNode); 64 | 65 | void ProcessTemplates(YAML::Node& aRootNode); 66 | void ConvertLegacyNodes(); 67 | 68 | std::filesystem::path m_path; 69 | YAML::Node m_data; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/Scriptable/ScriptBatch.cpp: -------------------------------------------------------------------------------- 1 | #include "ScriptBatch.hpp" 2 | #include "App/Tweaks/Executable/Scriptable/ScriptUtils.hpp" 3 | 4 | App::ScriptBatch::ScriptBatch(Core::SharedPtr aManager) 5 | : m_manager(std::move(aManager)) 6 | , m_reflection(m_manager->GetReflection()) 7 | , m_batch(m_manager->StartBatch()) 8 | { 9 | } 10 | 11 | bool App::ScriptBatch::SetFlat(Red::TweakDBID aFlatID, Red::Variant& aVariant) const 12 | { 13 | if (m_batch && !aVariant.IsEmpty()) 14 | { 15 | ConvertScriptValueForFlatValue(aVariant, m_reflection); 16 | return m_manager->SetFlat(m_batch, aFlatID, aVariant.GetType(), aVariant.GetDataPtr()); 17 | } 18 | 19 | return false; 20 | } 21 | 22 | bool App::ScriptBatch::CreateRecord(Red::TweakDBID aRecordID, Red::CName aTypeName) const 23 | { 24 | if (m_batch && aTypeName) 25 | { 26 | return m_manager->CreateRecord(m_batch, aRecordID, m_reflection->GetRecordType(aTypeName)); 27 | } 28 | 29 | return false; 30 | } 31 | 32 | bool App::ScriptBatch::CloneRecord(Red::TweakDBID aRecordID, Red::TweakDBID aSourceID) const 33 | { 34 | if (m_batch) 35 | { 36 | return m_manager->CloneRecord(m_batch, aRecordID, aSourceID); 37 | } 38 | 39 | return false; 40 | } 41 | 42 | bool App::ScriptBatch::UpdateRecord(Red::TweakDBID aRecordID) const 43 | { 44 | if (m_batch) 45 | { 46 | return m_manager->UpdateRecord(m_batch, aRecordID); 47 | } 48 | 49 | return false; 50 | } 51 | 52 | bool App::ScriptBatch::RegisterEnum(Red::TweakDBID aRecordID) const 53 | { 54 | if (m_batch && aRecordID) 55 | { 56 | m_manager->RegisterEnum(m_batch, aRecordID); 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | bool App::ScriptBatch::RegisterName(Red::CName aName) const 64 | { 65 | if (m_batch && aName) 66 | { 67 | m_manager->RegisterName(m_batch, aName.ToString(), aName.ToString()); 68 | return true; 69 | } 70 | 71 | return false; 72 | } 73 | 74 | void App::ScriptBatch::Commit() const 75 | { 76 | if (m_batch) 77 | { 78 | m_manager->CommitBatch(m_batch); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/Scriptable/ScriptBatch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/TweakDB/Manager.hpp" 4 | 5 | namespace App 6 | { 7 | struct ScriptBatch : Red::IScriptable 8 | { 9 | ScriptBatch() = default; 10 | ScriptBatch(Core::SharedPtr aManager); 11 | 12 | bool SetFlat(Red::TweakDBID aFlatID, Red::Variant& aVariant) const; 13 | bool CreateRecord(Red::TweakDBID aRecordID, Red::CName aTypeName) const; 14 | bool CloneRecord(Red::TweakDBID aRecordID, Red::TweakDBID aSourceID) const; 15 | bool UpdateRecord(Red::TweakDBID aRecordID) const; 16 | bool RegisterEnum(Red::TweakDBID aRecordID) const; 17 | bool RegisterName(Red::CName aName) const; 18 | void Commit() const; 19 | 20 | Core::SharedPtr m_manager; 21 | Core::SharedPtr m_batch; 22 | Core::SharedPtr m_reflection; 23 | 24 | RTTI_IMPL_TYPEINFO(App::ScriptBatch); 25 | RTTI_IMPL_ALLOCATOR(); 26 | }; 27 | } 28 | 29 | RTTI_DEFINE_CLASS(App::ScriptBatch, "TweakDBBatch", { 30 | RTTI_METHOD(SetFlat); 31 | RTTI_METHOD(CreateRecord); 32 | RTTI_METHOD(CloneRecord); 33 | RTTI_METHOD(UpdateRecord); 34 | RTTI_METHOD(RegisterEnum); 35 | RTTI_METHOD(RegisterName); 36 | RTTI_METHOD(Commit); 37 | }); 38 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/Scriptable/ScriptInterface.cpp: -------------------------------------------------------------------------------- 1 | #include "ScriptInterface.hpp" 2 | 3 | void App::ScriptInterface::SetReflection(Core::SharedPtr aReflection) 4 | { 5 | s_reflection = std::move(aReflection); 6 | } 7 | 8 | void App::ScriptInterface::GetFlat(Red::IScriptable*, Red::CStackFrame* aFrame, Red::Variant* aRet, void*) 9 | { 10 | Red::TweakDBID flatID; 11 | 12 | Red::GetParameter(aFrame, &flatID); 13 | aFrame->code++; 14 | 15 | if (!aRet) 16 | return; 17 | 18 | auto* tdb = Red::TweakDB::Get(); 19 | if (!tdb) 20 | { 21 | aRet->Free(); 22 | return; 23 | } 24 | 25 | auto* flat = tdb->GetFlatValue(flatID); 26 | if (!flat) 27 | { 28 | aRet->Free(); 29 | return; 30 | } 31 | 32 | auto data = flat->GetValue(); 33 | 34 | if (!data.value) 35 | { 36 | aRet->Free(); 37 | return; 38 | } 39 | 40 | aRet->Fill(data.type, data.value); 41 | } 42 | 43 | void App::ScriptInterface::GetRecord(Red::IScriptable*, Red::CStackFrame* aFrame, RecordHandle* aRet, void*) 44 | { 45 | Red::TweakDBID recordID; 46 | 47 | Red::GetParameter(aFrame, &recordID); 48 | aFrame->code++; 49 | 50 | if (!aRet) 51 | return; 52 | 53 | auto* tdb = Red::TweakDB::Get(); 54 | if (!tdb) 55 | return; 56 | 57 | RecordHandle record; 58 | tdb->TryGetRecord(recordID, record); 59 | 60 | if (!record) 61 | return; 62 | 63 | *aRet = record; 64 | } 65 | 66 | void App::ScriptInterface::GetRecords(Red::IScriptable*, Red::CStackFrame* aFrame, RecordArray* aRet, void*) 67 | { 68 | Red::CName recordTypeName; 69 | 70 | Red::GetParameter(aFrame, &recordTypeName); 71 | aFrame->code++; 72 | 73 | if (!aRet) 74 | return; 75 | 76 | auto records = FetchRecords(s_reflection->GetRecordFullName(recordTypeName)); 77 | 78 | if (!records || records->size <= 0) 79 | return; 80 | 81 | *aRet = *records; 82 | } 83 | 84 | void App::ScriptInterface::GetRecordCount(Red::IScriptable*, Red::CStackFrame* aFrame, uint32_t* aRet, void*) 85 | { 86 | Red::CName recordTypeName; 87 | 88 | Red::GetParameter(aFrame, &recordTypeName); 89 | aFrame->code++; 90 | 91 | if (!aRet) 92 | return; 93 | 94 | auto records = FetchRecords(recordTypeName); 95 | 96 | *aRet = records ? records->size : 0; 97 | } 98 | 99 | void App::ScriptInterface::GetRecordByIndex(Red::IScriptable*, Red::CStackFrame* aFrame, RecordHandle* aRet, void*) 100 | { 101 | Red::CName recordTypeName; 102 | uint32_t recordIndex; 103 | 104 | Red::GetParameter(aFrame, &recordTypeName); 105 | Red::GetParameter(aFrame, &recordIndex); 106 | aFrame->code++; 107 | 108 | if (!aRet) 109 | return; 110 | 111 | if (recordIndex < 0) 112 | return; 113 | 114 | auto records = FetchRecords(recordTypeName); 115 | 116 | if (!records || records->size <= 0 || recordIndex >= records->size) 117 | return; 118 | 119 | *aRet = records->entries[recordIndex]; 120 | } 121 | 122 | App::ScriptInterface::RecordArray* App::ScriptInterface::FetchRecords(Red::CName aTypeName) 123 | { 124 | auto* rtti = Red::CRTTISystem::Get(); 125 | if (!rtti) 126 | return nullptr; 127 | 128 | auto* recordType = rtti->GetType(aTypeName); 129 | if (!recordType) 130 | return nullptr; 131 | 132 | auto* tdb = Red::TweakDB::Get(); 133 | if (!tdb) 134 | return nullptr; 135 | 136 | std::shared_lock _(tdb->mutex01); 137 | return reinterpret_cast(tdb->recordsByType.Get(recordType)); 138 | } 139 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/Scriptable/ScriptInterface.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/TweakDB/Reflection.hpp" 4 | 5 | namespace App 6 | { 7 | class ScriptInterface : public Red::TweakDBInterface 8 | { 9 | public: 10 | static void SetReflection(Core::SharedPtr aReflection); 11 | 12 | private: 13 | using ScriptableHandle = Red::Handle; 14 | using ScriptableArray = Red::DynArray; 15 | using RecordHandle = Red::Handle; 16 | using RecordArray = Red::DynArray; 17 | 18 | static void GetFlat(Red::IScriptable*, Red::CStackFrame* aFrame, Red::Variant* aRet, void*); 19 | static void GetRecord(Red::IScriptable*, Red::CStackFrame* aFrame, RecordHandle* aRet, void*); 20 | static void GetRecords(Red::IScriptable*, Red::CStackFrame* aFrame, RecordArray* aRet, void*); 21 | static void GetRecordCount(Red::IScriptable*, Red::CStackFrame* aFrame, uint32_t* aRet, void*); 22 | static void GetRecordByIndex(Red::IScriptable*, Red::CStackFrame* aFrame, RecordHandle* aRet, void*); 23 | 24 | static RecordArray* FetchRecords(Red::CName aTypeName); 25 | 26 | inline static Core::SharedPtr s_reflection; 27 | 28 | RTTI_MEMBER_ACCESS(App::ScriptInterface); 29 | }; 30 | } 31 | 32 | RTTI_EXPAND_CLASS(Red::TweakDBInterface, App::ScriptInterface, { 33 | { 34 | auto func = type->AddFunction(&Type::GetRecords, "GetRecords", { .isFinal = true }); 35 | func->AddParam("CName", "type"); 36 | func->SetReturnType("array:handle:gamedataTweakDBRecord"); 37 | } 38 | { 39 | auto func = type->AddFunction(&Type::GetRecordCount, "GetRecordCount", { .isFinal = true }); 40 | func->AddParam("CName", "type"); 41 | func->SetReturnType("Int32"); 42 | } 43 | { 44 | auto func = type->AddFunction(&Type::GetRecordByIndex, "GetRecordByIndex", { .isFinal = true }); 45 | func->AddParam("CName", "type"); 46 | func->AddParam("Int32", "index"); 47 | func->SetReturnType("handle:gamedataTweakDBRecord"); 48 | } 49 | { 50 | auto func = type->AddFunction(&Type::GetRecord, "GetRecord", { .isFinal = true }); 51 | func->AddParam("TweakDBID", "path"); 52 | func->SetReturnType("handle:gamedataTweakDBRecord"); 53 | } 54 | { 55 | auto func = type->AddFunction(&Type::GetFlat, "GetFlat", { .isFinal = true }); 56 | func->AddParam("TweakDBID", "path"); 57 | func->SetReturnType("Variant"); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/Scriptable/ScriptManager.cpp: -------------------------------------------------------------------------------- 1 | #include "App/Tweaks/Executable/Scriptable/ScriptUtils.hpp" 2 | #include "ScriptManager.hpp" 3 | 4 | void App::ScriptManager::SetManager(Core::SharedPtr aManager) 5 | { 6 | s_manager = std::move(aManager); 7 | s_reflection = s_manager->GetReflection(); 8 | } 9 | 10 | void App::ScriptManager::SetFlat(Red::IScriptable*, Red::CStackFrame* aFrame, bool* aRet, void*) 11 | { 12 | Red::TweakDBID flatID; 13 | Red::Variant variant; 14 | 15 | Red::GetParameter(aFrame, &flatID); 16 | Red::GetParameter(aFrame, &variant); 17 | aFrame->code++; 18 | 19 | if (!s_manager || variant.IsEmpty()) 20 | return; 21 | 22 | ConvertScriptValueForFlatValue(variant, s_reflection); 23 | 24 | auto success = s_manager->SetFlat(flatID, variant.GetType(), variant.GetDataPtr()); 25 | 26 | if (aRet) 27 | { 28 | *aRet = success; 29 | } 30 | } 31 | 32 | void App::ScriptManager::CreateRecord(Red::IScriptable*, Red::CStackFrame* aFrame, bool* aRet, void*) 33 | { 34 | Red::TweakDBID recordID; 35 | Red::CName typeName; 36 | 37 | Red::GetParameter(aFrame, &recordID); 38 | Red::GetParameter(aFrame, &typeName); 39 | aFrame->code++; 40 | 41 | if (!s_manager) 42 | return; 43 | 44 | auto recordType = s_reflection->GetRecordType(typeName); 45 | 46 | if (!recordType) 47 | return; 48 | 49 | auto success = s_manager->CreateRecord(recordID, recordType); 50 | 51 | if (aRet) 52 | { 53 | *aRet = success; 54 | } 55 | } 56 | 57 | void App::ScriptManager::CloneRecord(Red::IScriptable*, Red::CStackFrame* aFrame, bool* aRet, void*) 58 | { 59 | Red::TweakDBID recordID; 60 | Red::TweakDBID sourceID; 61 | 62 | Red::GetParameter(aFrame, &recordID); 63 | Red::GetParameter(aFrame, &sourceID); 64 | aFrame->code++; 65 | 66 | if (!s_manager) 67 | return; 68 | 69 | auto success = s_manager->CloneRecord(recordID, sourceID); 70 | 71 | if (aRet) 72 | { 73 | *aRet = success; 74 | } 75 | } 76 | 77 | void App::ScriptManager::UpdateRecord(Red::IScriptable*, Red::CStackFrame* aFrame, bool* aRet, void*) 78 | { 79 | Red::TweakDBID recordID; 80 | 81 | Red::GetParameter(aFrame, &recordID); 82 | aFrame->code++; 83 | 84 | if (!s_manager) 85 | return; 86 | 87 | auto success = s_manager->UpdateRecord(recordID); 88 | 89 | if (aRet) 90 | { 91 | *aRet = success; 92 | } 93 | } 94 | 95 | void App::ScriptManager::RegisterName(Red::IScriptable*, Red::CStackFrame* aFrame, bool* aRet, void*) 96 | { 97 | Red::CName name; 98 | 99 | Red::GetParameter(aFrame, &name); 100 | aFrame->code++; 101 | 102 | if (!s_manager || name.IsNone()) 103 | return; 104 | 105 | auto id = Red::TweakDBID(name.ToString()); 106 | auto str = std::string(name.ToString()); 107 | 108 | s_manager->RegisterName(id, str); 109 | 110 | if (aRet) 111 | { 112 | *aRet = true; 113 | } 114 | } 115 | 116 | void App::ScriptManager::RegisterEnum(Red::TweakDBID aRecordID) 117 | { 118 | if (s_manager) 119 | { 120 | s_manager->RegisterEnum(aRecordID); 121 | } 122 | } 123 | 124 | Red::Handle App::ScriptManager::StartBatch() 125 | { 126 | return Red::MakeHandle(s_manager); 127 | } 128 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/Scriptable/ScriptManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Tweaks/Executable/Scriptable/ScriptBatch.hpp" 4 | #include "Red/TweakDB/Manager.hpp" 5 | 6 | namespace App 7 | { 8 | class ScriptManager : public Red::IScriptable 9 | { 10 | public: 11 | static void SetManager(Core::SharedPtr aManager); 12 | 13 | static void RegisterEnum(Red::TweakDBID aRecordID); 14 | static Red::Handle StartBatch(); 15 | 16 | private: 17 | static void SetFlat(Red::IScriptable* aContext, Red::CStackFrame* aFrame, bool* aRet, void*); 18 | static void CreateRecord(Red::IScriptable* aContext, Red::CStackFrame* aFrame, bool* aRet, void*); 19 | static void CloneRecord(Red::IScriptable* aContext, Red::CStackFrame* aFrame, bool* aRet, void*); 20 | static void UpdateRecord(Red::IScriptable* aContext, Red::CStackFrame* aFrame, bool* aRet, void*); 21 | static void RegisterName(Red::IScriptable* aContext, Red::CStackFrame* aFrame, bool* aRet, void*); 22 | 23 | inline static Core::SharedPtr s_manager; 24 | inline static Core::SharedPtr s_reflection; 25 | 26 | RTTI_IMPL_TYPEINFO(App::ScriptManager); 27 | RTTI_MEMBER_ACCESS(App::ScriptManager); 28 | }; 29 | } 30 | 31 | RTTI_DEFINE_CLASS(App::ScriptManager, "TweakDBManager", { 32 | RTTI_ABSTRACT(); 33 | RTTI_METHOD(StartBatch); 34 | RTTI_METHOD(RegisterEnum); 35 | { 36 | auto func = type->AddFunction(&Type::SetFlat, "SetFlat", { .isFinal = true }); 37 | func->AddParam("TweakDBID", "path"); 38 | func->AddParam("Variant", "value"); 39 | func->SetReturnType("Bool"); 40 | } 41 | { 42 | auto func = type->AddFunction(&Type::CreateRecord, "CreateRecord", { .isFinal = true }); 43 | func->AddParam("TweakDBID", "path"); 44 | func->AddParam("CName", "type"); 45 | func->SetReturnType("Bool"); 46 | } 47 | { 48 | auto func = type->AddFunction(&Type::CloneRecord, "CloneRecord", { .isFinal = true }); 49 | func->AddParam("TweakDBID", "path"); 50 | func->AddParam("TweakDBID", "base"); 51 | func->SetReturnType("Bool"); 52 | } 53 | { 54 | auto func = type->AddFunction(&Type::UpdateRecord, "UpdateRecord", { .isFinal = true }); 55 | func->AddParam("TweakDBID", "path"); 56 | func->SetReturnType("Bool"); 57 | } 58 | { 59 | auto func = type->AddFunction(&Type::RegisterName, "RegisterName", { .isFinal = true }); 60 | func->AddParam("TweakDBID", "path"); 61 | func->SetReturnType("Bool"); 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/Scriptable/ScriptUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Utils/Str.hpp" 4 | #include "Red/Localization.hpp" 5 | #include "Red/TweakDB/Manager.hpp" 6 | 7 | namespace App 8 | { 9 | inline void ConvertScriptValueForFlatValue(Red::Variant& aVariant, 10 | const Core::SharedPtr& aReflection) 11 | { 12 | const auto& variantType = aVariant.GetType(); 13 | 14 | if (aReflection->IsResRefToken(variantType)) 15 | { 16 | const auto rtti = Red::CRTTISystem::Get(); 17 | const auto type = rtti->GetType(Red::ERTDBFlatType::ResRef); 18 | 19 | aVariant = Red::Variant(type, aVariant.GetDataPtr()); 20 | } 21 | else if (aReflection->IsResRefTokenArray(variantType)) 22 | { 23 | const auto rtti = Red::CRTTISystem::Get(); 24 | const auto type = rtti->GetType(Red::ERTDBFlatType::ResRefArray); 25 | 26 | aVariant = Red::Variant(type, aVariant.GetDataPtr()); 27 | } 28 | else if (variantType->GetName() == Red::ERTDBFlatType::CName) 29 | { 30 | const auto str = reinterpret_cast(aVariant.GetDataPtr())->ToString(); 31 | 32 | if (strncmp(str, Red::LocKeyPrefix, Red::LocKeyPrefixLength) == 0) 33 | { 34 | const auto& value = str + Red::LocKeyPrefixLength; 35 | const auto& length = strlen(str) - Red::LocKeyPrefixLength; 36 | Red::LocKeyWrapper wrapper; 37 | 38 | if (!App::ParseInt(value, length, wrapper.primaryKey)) 39 | { 40 | wrapper.primaryKey = Red::FNV1a64(value); 41 | } 42 | 43 | aVariant.Fill(aReflection->GetFlatType(Red::ERTDBFlatType::LocKey), &wrapper); 44 | } 45 | } 46 | else if (variantType->GetName() == Red::ERTDBFlatType::String) 47 | { 48 | auto str = reinterpret_cast(aVariant.GetDataPtr()); 49 | 50 | if (strncmp(str->c_str(), Red::LocKeyPrefix, Red::LocKeyPrefixLength) == 0) 51 | { 52 | const std::string_view value{str->c_str() + Red::LocKeyPrefixLength, 53 | str->Length() - Red::LocKeyPrefixLength}; 54 | if (!IsNumeric(value)) 55 | { 56 | std::string key = Red::LocKeyPrefix + std::to_string(Red::FNV1a64(value.data())); 57 | *str = key.data(); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/Scriptable/ScriptableTweak.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace App 4 | { 5 | class ScriptableTweak : public Red::IScriptable 6 | { 7 | RTTI_IMPL_TYPEINFO(App::ScriptableTweak); 8 | }; 9 | } 10 | 11 | RTTI_DEFINE_CLASS(App::ScriptableTweak, { 12 | RTTI_ABSTRACT(); 13 | }) 14 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/TweakExecutor.cpp: -------------------------------------------------------------------------------- 1 | #include "TweakExecutor.hpp" 2 | #include "App/Tweaks/Executable/Scriptable/ScriptInterface.hpp" 3 | #include "App/Tweaks/Executable/Scriptable/ScriptManager.hpp" 4 | #include "App/Tweaks/Executable/Scriptable/ScriptableTweak.hpp" 5 | 6 | namespace 7 | { 8 | constexpr auto ApplyMethodName = "OnApply"; 9 | 10 | static const Red::ClassLocator s_scriptableTweakType; 11 | } 12 | 13 | App::TweakExecutor::TweakExecutor(Core::SharedPtr aManager) 14 | : m_manager(std::move(aManager)) 15 | , m_rtti(Red::CRTTISystem::Get()) 16 | { 17 | InitializeRuntime(); 18 | } 19 | 20 | void App::TweakExecutor::InitializeRuntime() 21 | { 22 | ScriptManager::SetManager(m_manager); 23 | ScriptInterface::SetReflection(m_manager->GetReflection()); 24 | } 25 | 26 | void App::TweakExecutor::ExecuteTweaks() 27 | { 28 | try 29 | { 30 | Red::DynArray tweakClasses; 31 | m_rtti->GetClasses(s_scriptableTweakType, tweakClasses); 32 | 33 | if (tweakClasses.size == 0) 34 | return; 35 | 36 | LogInfo("Executing scriptable tweaks..."); 37 | 38 | // ScriptedManager scopedManager(m_manager); 39 | 40 | for (auto* tweakClass : tweakClasses) 41 | Execute(tweakClass); 42 | 43 | LogInfo("Execution completed."); 44 | } 45 | catch (const std::exception& ex) 46 | { 47 | LogError(ex.what()); 48 | } 49 | catch (...) 50 | { 51 | LogError("An unknown error occurred while trying to execute tweaks."); 52 | } 53 | } 54 | 55 | void App::TweakExecutor::ExecuteTweak(Red::CName aTweakName) 56 | { 57 | auto tweakClass = m_rtti->GetClass(aTweakName); 58 | 59 | if (!tweakClass) 60 | { 61 | LogError(R"(Tweak class "{}" not found.)", aTweakName.ToString()); 62 | return; 63 | } 64 | 65 | if (!tweakClass->IsA(s_scriptableTweakType)) 66 | { 67 | LogError(R"(Tweak class "{}" must inherit from "{}".)", 68 | aTweakName.ToString(), s_scriptableTweakType->GetName().ToString()); 69 | return; 70 | } 71 | 72 | if (tweakClass->flags.isAbstract) 73 | { 74 | LogError(R"(Tweak class "{}" is abstract and cannot be executed.)", aTweakName.ToString()); 75 | return; 76 | } 77 | 78 | // ScriptedManager scopedManager(m_manager); 79 | 80 | if (Execute(tweakClass)) 81 | LogInfo("Execution completed."); 82 | } 83 | 84 | bool App::TweakExecutor::Execute(Red::CClass* aTweakClass) 85 | { 86 | try 87 | { 88 | if (aTweakClass->flags.isAbstract) 89 | return false; 90 | 91 | auto applyCallback = aTweakClass->GetFunction(ApplyMethodName); 92 | 93 | if (!applyCallback || applyCallback->flags.hasUndefinedBody) 94 | { 95 | LogError(R"(Tweak class "{}" doesn't have "{}" method.)", 96 | aTweakClass->GetName().ToString(), ApplyMethodName); 97 | return false; 98 | } 99 | 100 | auto tweakHandle = Red::Handle(reinterpret_cast(aTweakClass->CreateInstance())); 101 | 102 | if (!tweakHandle) 103 | { 104 | LogError(R"(Tweak instance "{}" cannot be constructed.)", 105 | aTweakClass->GetName().ToString()); 106 | return false; 107 | } 108 | 109 | LogInfo(R"(Executing "{}"...)", aTweakClass->GetName().ToString()); 110 | 111 | auto stack = Red::CStack(tweakHandle.instance); 112 | 113 | applyCallback->Execute(&stack); 114 | } 115 | catch (const std::exception& ex) 116 | { 117 | LogError(ex.what()); 118 | return false; 119 | } 120 | catch (...) 121 | { 122 | LogError("An unknown error occurred."); 123 | return false; 124 | } 125 | 126 | return true; 127 | } 128 | -------------------------------------------------------------------------------- /src/App/Tweaks/Executable/TweakExecutor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Logging/LoggingAgent.hpp" 4 | #include "Red/TweakDB/Manager.hpp" 5 | 6 | namespace App 7 | { 8 | class TweakExecutor : Core::LoggingAgent 9 | { 10 | public: 11 | explicit TweakExecutor(Core::SharedPtr aManager); 12 | 13 | void InitializeRuntime(); 14 | 15 | void ExecuteTweaks(); 16 | void ExecuteTweak(Red::CName aTweakName); 17 | 18 | private: 19 | bool Execute(Red::CClass* aTweakClass); 20 | 21 | Red::CRTTISystem* m_rtti; 22 | Core::SharedPtr m_manager; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/App/Tweaks/Metadata/MetadataExporter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Tweaks/Declarative/Red/RedReader.hpp" 4 | #include "Core/Logging/LoggingAgent.hpp" 5 | #include "Red/TweakDB/Manager.hpp" 6 | #include "Red/TweakDB/Source/Parser.hpp" 7 | 8 | namespace App 9 | { 10 | class MetadataExporter : Core::LoggingAgent 11 | { 12 | public: 13 | MetadataExporter(Core::SharedPtr aManager); 14 | 15 | bool LoadSource(const std::filesystem::path& aSourceDir); 16 | 17 | bool ExportInheritanceMap(const std::filesystem::path& aOutPath, bool aGeneratedComment = false); 18 | bool ExportExtraFlats(const std::filesystem::path& aOutPath, bool aGeneratedComment = false); 19 | 20 | private: 21 | void ResolveGroups(); 22 | void ResolveInlines(const Red::TweakGroupPtr& aOwner, const Red::TweakGroupPtr& aParent, int32_t aCounter = -1); 23 | 24 | static bool IsDebugGroup(const Red::TweakGroupPtr& aGroup); 25 | 26 | Core::SharedPtr m_manager; 27 | Core::SharedPtr m_reflection; 28 | Core::Vector m_sources; 29 | Core::Map m_groups; 30 | Core::Map m_records; 31 | bool m_resolved; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/App/Tweaks/Metadata/MetadataImporter.cpp: -------------------------------------------------------------------------------- 1 | #include "MetadataImporter.hpp" 2 | 3 | App::MetadataImporter::MetadataImporter(Core::SharedPtr aManager) 4 | : m_manager(std::move(aManager)) 5 | , m_reflection(m_manager->GetReflection()) 6 | { 7 | } 8 | 9 | bool App::MetadataImporter::ImportInheritanceMap(const std::filesystem::path& aPath) 10 | { 11 | std::error_code error; 12 | if (!std::filesystem::exists(aPath, error)) 13 | return false; 14 | 15 | if (aPath.extension() == ".dat") 16 | { 17 | std::ifstream in(aPath, std::ios::binary); 18 | 19 | size_t numberOfEntries; 20 | in.read(reinterpret_cast(&numberOfEntries), sizeof(numberOfEntries)); 21 | 22 | while (numberOfEntries > 0) 23 | { 24 | Red::TweakDBID recordID; 25 | size_t numberOfChildren; 26 | 27 | in.read(reinterpret_cast(&recordID), sizeof(recordID)); 28 | in.read(reinterpret_cast(&numberOfChildren), sizeof(numberOfChildren)); 29 | 30 | Core::Set descendantIDs; 31 | 32 | while (numberOfChildren > 0) 33 | { 34 | Red::TweakDBID descendantID; 35 | in.read(reinterpret_cast(&descendantID), sizeof(descendantID)); 36 | descendantIDs.insert(descendantID); 37 | 38 | --numberOfChildren; 39 | } 40 | 41 | m_reflection->RegisterDescendants(recordID, descendantIDs); 42 | 43 | --numberOfEntries; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | if (aPath.extension() == ".yaml") 50 | { 51 | auto data = YAML::LoadFile(aPath.string()); 52 | if (!data.IsDefined() || !data.IsMap()) 53 | return false; 54 | 55 | Core::Set descendantIDs; 56 | 57 | for (const auto& topNodeIt : data) 58 | { 59 | const auto recordID = Red::TweakDBID(topNodeIt.first.Scalar()); 60 | 61 | if (!recordID) 62 | return false; 63 | 64 | const auto& descendantNames = topNodeIt.second; 65 | 66 | if (!descendantNames.IsSequence()) 67 | return false; 68 | 69 | descendantIDs.clear(); 70 | 71 | for (const auto& descendantName : descendantNames) 72 | { 73 | const auto descendantID = Red::TweakDBID(descendantName.Scalar()); 74 | 75 | if (!descendantID) 76 | return false; 77 | 78 | descendantIDs.insert(descendantID); 79 | } 80 | 81 | if (descendantIDs.empty()) 82 | return false; 83 | 84 | m_reflection->RegisterDescendants(recordID, descendantIDs); 85 | } 86 | 87 | return true; 88 | } 89 | 90 | return false; 91 | } 92 | 93 | bool App::MetadataImporter::ImportExtraFlats(const std::filesystem::path& aPath) 94 | { 95 | std::error_code error; 96 | if (!std::filesystem::exists(aPath, error)) 97 | return false; 98 | 99 | if (aPath.extension() == ".dat") 100 | { 101 | std::ifstream in(aPath, std::ios::binary); 102 | 103 | size_t numberOfEntries; 104 | in.read(reinterpret_cast(&numberOfEntries), sizeof(numberOfEntries)); 105 | 106 | while (numberOfEntries > 0) 107 | { 108 | Red::CName recordType; 109 | size_t numberOfFlats; 110 | 111 | in.read(reinterpret_cast(&recordType), sizeof(recordType)); 112 | in.read(reinterpret_cast(&numberOfFlats), sizeof(numberOfFlats)); 113 | 114 | while (numberOfFlats > 0) 115 | { 116 | uint8_t propNameLen; 117 | char propName[254]; 118 | Red::CName propType; 119 | Red::CName foreignType; 120 | 121 | in.read(reinterpret_cast(&propNameLen), sizeof(propNameLen)); 122 | in.read(reinterpret_cast(&propName), propNameLen); 123 | in.read(reinterpret_cast(&propType), sizeof(propType)); 124 | in.read(reinterpret_cast(&foreignType), sizeof(foreignType)); 125 | 126 | m_reflection->RegisterExtraFlat(recordType, {propName, propNameLen}, propType, foreignType); 127 | 128 | --numberOfFlats; 129 | } 130 | 131 | --numberOfEntries; 132 | } 133 | 134 | return true ; 135 | } 136 | 137 | if (aPath.extension() == ".yaml") 138 | { 139 | auto data = YAML::LoadFile(aPath.string()); 140 | if (!data.IsDefined() || !data.IsMap()) 141 | return false; 142 | 143 | for (const auto& topNodeIt : data) 144 | { 145 | const auto recordType = m_reflection->GetRecordFullName(topNodeIt.first.Scalar().data()); 146 | 147 | if (!m_reflection->IsRecordType(recordType)) 148 | return false; 149 | 150 | const auto& extraFlats = topNodeIt.second; 151 | 152 | if (!extraFlats.IsMap()) 153 | return false; 154 | 155 | for (const auto& extraFlatIt : extraFlats) 156 | { 157 | const auto& propName = extraFlatIt.first.Scalar(); 158 | const auto& propDataNode = extraFlatIt.second; 159 | 160 | if (!propDataNode.IsMap()) 161 | return false; 162 | 163 | const auto& propTypeNode = propDataNode["flatType"]; 164 | 165 | if (!propTypeNode.IsScalar()) 166 | return false; 167 | 168 | const auto propType = Red::CName(propTypeNode.Scalar().data()); 169 | 170 | if (!m_reflection->IsFlatType(propType)) 171 | return false; 172 | 173 | const auto& foreignTypeNode = propDataNode["foreignType"]; 174 | 175 | auto foreignType = Red::CName(); 176 | 177 | if (foreignTypeNode.IsDefined()) 178 | { 179 | if (!foreignTypeNode.IsScalar()) 180 | return false; 181 | 182 | foreignType = m_reflection->GetRecordFullName(foreignTypeNode.Scalar().data()); 183 | 184 | if (!m_reflection->IsRecordType(foreignType)) 185 | return false; 186 | } 187 | 188 | m_reflection->RegisterExtraFlat(recordType, propName, propType, foreignType); 189 | } 190 | } 191 | 192 | return true; 193 | } 194 | 195 | return false; 196 | } 197 | -------------------------------------------------------------------------------- /src/App/Tweaks/Metadata/MetadataImporter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/TweakDB/Manager.hpp" 4 | 5 | namespace App 6 | { 7 | class MetadataImporter 8 | { 9 | public: 10 | MetadataImporter(Core::SharedPtr aManager); 11 | 12 | bool ImportInheritanceMap(const std::filesystem::path& aPath); 13 | bool ImportExtraFlats(const std::filesystem::path& aPath); 14 | 15 | private: 16 | Core::SharedPtr m_manager; 17 | Core::SharedPtr m_reflection; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/App/Tweaks/TweakContext.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Runtime/HostImage.hpp" 4 | 5 | namespace App 6 | { 7 | class TweakContext 8 | { 9 | public: 10 | TweakContext(const Core::SemvVer& aProductVer) 11 | : m_gameVersion(static_cast(aProductVer.major), 12 | static_cast(aProductVer.minor), 13 | static_cast(aProductVer.patch)) 14 | , m_isEpisodeOne(false) 15 | { 16 | Red::CallGlobal("IsEP1", m_isEpisodeOne); 17 | } 18 | 19 | [[nodiscard]] inline bool CheckGameVersion(const std::string& aCondition) const 20 | { 21 | return semver::range::satisfies(m_gameVersion, aCondition); 22 | } 23 | 24 | [[nodiscard]] inline bool CheckInstalledDLC(const std::string& aCondition) const 25 | { 26 | if (aCondition == "EP1") { 27 | return m_isEpisodeOne; 28 | } 29 | 30 | return aCondition.empty(); 31 | } 32 | 33 | private: 34 | semver::version m_gameVersion; 35 | bool m_isEpisodeOne; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/App/Tweaks/TweakService.cpp: -------------------------------------------------------------------------------- 1 | #include "TweakService.hpp" 2 | #include "App/Tweaks/Declarative/TweakImporter.hpp" 3 | #include "App/Tweaks/Executable/TweakExecutor.hpp" 4 | #include "App/Tweaks/Metadata/MetadataExporter.hpp" 5 | #include "App/Tweaks/Metadata/MetadataImporter.hpp" 6 | #include "Red/TweakDB/Raws.hpp" 7 | 8 | App::TweakService::TweakService(const Core::SemvVer& aProductVer, std::filesystem::path aGameDir, 9 | std::filesystem::path aTweaksDir, std::filesystem::path aInheritanceMapPath, 10 | std::filesystem::path aExtraFlatsPath, std::filesystem::path aSourcesDir) 11 | : m_gameDir(std::move(aGameDir)) 12 | , m_tweaksDir(std::move(aTweaksDir)) 13 | , m_sourcesDir(std::move(aSourcesDir)) 14 | , m_inheritanceMapPath(std::move(aInheritanceMapPath)) 15 | , m_extraFlatsPath(std::move(aExtraFlatsPath)) 16 | , m_productVer(aProductVer) 17 | { 18 | m_importPaths.push_back(m_tweaksDir); 19 | } 20 | 21 | void App::TweakService::OnBootstrap() 22 | { 23 | CreateTweaksDir(); 24 | 25 | HookAfter([&](bool& aSuccess) { 26 | if (aSuccess) 27 | { 28 | m_reflection = Core::MakeShared(); 29 | m_manager = Core::MakeShared(m_reflection); 30 | m_context = Core::MakeShared(m_productVer); 31 | m_importer = Core::MakeShared(m_manager, m_context); 32 | m_executor = Core::MakeShared(m_manager); 33 | m_changelog = Core::MakeShared(); 34 | 35 | if (ImportMetadata()) 36 | { 37 | EnsureRuntimeAccess(); 38 | ApplyPatches(); 39 | LoadTweaks(false); 40 | } 41 | } 42 | }); 43 | 44 | HookAfter([&]() { 45 | EnsureRuntimeAccess(); 46 | CheckForIssues(); 47 | }); 48 | } 49 | 50 | void App::TweakService::LoadTweaks(bool aCheckForIssues) 51 | { 52 | if (m_manager) 53 | { 54 | m_importer->ImportTweaks(m_importPaths, m_changelog); 55 | m_executor->ExecuteTweaks(); 56 | 57 | if (aCheckForIssues) 58 | { 59 | m_changelog->CheckForIssues(m_manager); 60 | } 61 | } 62 | } 63 | 64 | void App::TweakService::ImportTweaks() 65 | { 66 | if (m_manager) 67 | { 68 | m_importer->ImportTweaks(m_importPaths, m_changelog); 69 | } 70 | } 71 | 72 | void App::TweakService::ExecuteTweaks() 73 | { 74 | if (m_manager) 75 | { 76 | m_executor->ExecuteTweaks(); 77 | } 78 | } 79 | 80 | void App::TweakService::ExecuteTweak(Red::CName aName) 81 | { 82 | if (m_manager) 83 | { 84 | m_executor->ExecuteTweak(aName); 85 | } 86 | } 87 | 88 | void App::TweakService::EnsureRuntimeAccess() 89 | { 90 | if (m_manager) 91 | { 92 | m_manager->GetTweakDB()->unk160 = 0; 93 | } 94 | } 95 | 96 | void App::TweakService::ApplyPatches() 97 | { 98 | if (m_manager) 99 | { 100 | m_manager->CloneRecord("Vendors.IsPresent", "Vendors.Always_Present"); 101 | m_manager->RegisterName("Vendors.IsPresent"); 102 | } 103 | } 104 | 105 | void App::TweakService::CheckForIssues() 106 | { 107 | if (m_manager && m_changelog) 108 | { 109 | m_changelog->CheckForIssues(m_manager); 110 | } 111 | } 112 | 113 | void App::TweakService::CreateTweaksDir() 114 | { 115 | std::error_code error; 116 | 117 | if (!std::filesystem::exists(m_tweaksDir, error)) 118 | { 119 | if (!std::filesystem::create_directories(m_tweaksDir, error)) 120 | { 121 | LogWarning("Cannot create tweaks directory \"{}\": {}.", 122 | std::filesystem::relative(m_tweaksDir, m_gameDir).string(), error.message()); 123 | } 124 | } 125 | } 126 | 127 | bool App::TweakService::RegisterTweak(std::filesystem::path aPath) 128 | { 129 | std::error_code error; 130 | 131 | if (aPath.is_relative()) 132 | { 133 | aPath = m_gameDir / aPath; 134 | } 135 | 136 | if (!std::filesystem::exists(aPath, error) || !std::filesystem::is_regular_file(aPath, error)) 137 | { 138 | LogError("Can't register non-existing tweak \"{}\".", 139 | std::filesystem::relative(aPath, m_gameDir).string()); 140 | return false; 141 | } 142 | 143 | m_importPaths.emplace_back(std::move(aPath)); 144 | return true; 145 | } 146 | 147 | bool App::TweakService::RegisterDirectory(std::filesystem::path aPath) 148 | { 149 | std::error_code error; 150 | 151 | if (aPath.is_relative()) 152 | { 153 | aPath = m_gameDir / aPath; 154 | } 155 | 156 | if (!std::filesystem::exists(aPath, error) || !std::filesystem::is_directory(aPath, error)) 157 | { 158 | LogError("Can't register non-existing tweak directory \"{}\".", 159 | std::filesystem::relative(aPath, m_gameDir).string()); 160 | return false; 161 | } 162 | 163 | m_importPaths.emplace_back(std::move(aPath)); 164 | return true; 165 | } 166 | 167 | bool App::TweakService::ImportMetadata() 168 | { 169 | MetadataImporter importer{m_manager}; 170 | 171 | LogInfo("Loading inheritance metadata..."); 172 | 173 | if (!importer.ImportInheritanceMap(m_inheritanceMapPath)) 174 | { 175 | LogError("Can't load inheritance metadata from \"{}\".", m_inheritanceMapPath.string()); 176 | return false; 177 | } 178 | 179 | LogInfo("Loading extra flats metadata..."); 180 | 181 | if (!importer.ImportExtraFlats(m_extraFlatsPath)) 182 | { 183 | LogError("Can't load extra flats metadata from \"{}\".", m_extraFlatsPath.string()); 184 | return false; 185 | } 186 | 187 | return true; 188 | } 189 | 190 | void App::TweakService::ExportMetadata() 191 | { 192 | MetadataExporter exporter{m_manager}; 193 | exporter.LoadSource(m_sourcesDir); 194 | exporter.ExportInheritanceMap(m_inheritanceMapPath); 195 | exporter.ExportExtraFlats(m_extraFlatsPath); 196 | exporter.ExportInheritanceMap(m_inheritanceMapPath.replace_extension(".yaml")); 197 | exporter.ExportExtraFlats(m_extraFlatsPath.replace_extension(".yaml")); 198 | } 199 | 200 | Red::TweakDBManager& App::TweakService::GetManager() 201 | { 202 | return *m_manager; 203 | } 204 | 205 | Red::TweakDBReflection& App::TweakService::GetReflection() 206 | { 207 | return *m_reflection; 208 | } 209 | 210 | App::TweakChangelog& App::TweakService::GetChangelog() 211 | { 212 | return *m_changelog; 213 | } 214 | -------------------------------------------------------------------------------- /src/App/Tweaks/TweakService.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Tweaks/Batch/TweakChangelog.hpp" 4 | #include "App/Tweaks/Declarative/TweakImporter.hpp" 5 | #include "App/Tweaks/Executable/TweakExecutor.hpp" 6 | #include "App/Tweaks/TweakContext.hpp" 7 | #include "Core/Foundation/Feature.hpp" 8 | #include "Core/Hooking/HookingAgent.hpp" 9 | #include "Core/Logging/LoggingAgent.hpp" 10 | #include "Core/Runtime/HostImage.hpp" 11 | #include "Red/TweakDB/Manager.hpp" 12 | #include "Red/TweakDB/Reflection.hpp" 13 | 14 | namespace App 15 | { 16 | class TweakService 17 | : public Core::Feature 18 | , public Core::HookingAgent 19 | , public Core::LoggingAgent 20 | { 21 | public: 22 | TweakService(const Core::SemvVer& aProductVer, std::filesystem::path aGameDir, std::filesystem::path aTweaksDir, 23 | std::filesystem::path aInheritanceMapPath, std::filesystem::path aExtraFlatsPath, 24 | std::filesystem::path aSourcesDir); 25 | 26 | bool RegisterTweak(std::filesystem::path aPath); 27 | bool RegisterDirectory(std::filesystem::path aPath); 28 | 29 | void LoadTweaks(bool aCheckForIssues); 30 | void ImportTweaks(); 31 | void ExecuteTweaks(); 32 | void ExecuteTweak(Red::CName aName); 33 | void CheckForIssues(); 34 | 35 | bool ImportMetadata(); 36 | void ExportMetadata(); 37 | 38 | Red::TweakDBManager& GetManager(); 39 | Red::TweakDBReflection& GetReflection(); 40 | App::TweakChangelog& GetChangelog(); 41 | 42 | protected: 43 | void OnBootstrap() override; 44 | void CreateTweaksDir(); 45 | void EnsureRuntimeAccess(); 46 | void ApplyPatches(); 47 | 48 | std::filesystem::path m_gameDir; 49 | std::filesystem::path m_tweaksDir; 50 | std::filesystem::path m_sourcesDir; 51 | std::filesystem::path m_inheritanceMapPath; 52 | std::filesystem::path m_extraFlatsPath; 53 | const Core::SemvVer& m_productVer; 54 | Core::Vector m_importPaths; 55 | Core::SharedPtr m_reflection; 56 | Core::SharedPtr m_manager; 57 | Core::SharedPtr m_changelog; 58 | Core::SharedPtr m_importer; 59 | Core::SharedPtr m_executor; 60 | Core::SharedPtr m_context; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/App/Utils/Str.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace App 4 | { 5 | template 6 | requires std::is_integral_v 7 | bool ParseInt(const char* aIn, size_t aLength, T& aOut, const int aRadix = 10) 8 | { 9 | using Temp = std::conditional_t, int64_t, uint64_t>; 10 | 11 | Temp out; 12 | char* end; 13 | 14 | if constexpr (std::is_signed_v) 15 | out = std::strtoll(aIn, &end, aRadix); 16 | else 17 | out = std::strtoull(aIn, &end, aRadix); 18 | 19 | if (end != aIn + aLength) 20 | return false; 21 | 22 | aOut = static_cast(out); 23 | return true; 24 | } 25 | 26 | template 27 | requires std::is_integral_v 28 | bool ParseInt(const std::string& aIn, T& aOut, const int aRadix = 10) 29 | { 30 | return ParseInt(aIn.c_str(), aIn.size(), aOut, aRadix); 31 | } 32 | 33 | template 34 | requires std::is_integral_v 35 | T ParseInt(const std::string& aIn, const int aRadix = 10) 36 | { 37 | using Temp = std::conditional_t, int64_t, uint64_t>; 38 | 39 | Temp out; 40 | 41 | if constexpr (std::is_signed_v) 42 | out = std::strtoll(aIn.c_str(), nullptr, aRadix); 43 | else 44 | out = std::strtoull(aIn.c_str(), nullptr, aRadix); 45 | 46 | return static_cast(out); 47 | } 48 | 49 | template 50 | requires std::is_floating_point_v 51 | bool ParseFloat(const std::string& aIn, T& aOut, const char* aSuffix = nullptr) 52 | { 53 | char* end; 54 | 55 | if constexpr (std::is_same_v) 56 | aOut = std::strtof(aIn.c_str(), &end); 57 | else if constexpr (std::is_same_v) 58 | aOut = std::strtod(aIn.c_str(), &end); 59 | else if constexpr (std::is_same_v) 60 | aOut = std::strtold(aIn.c_str(), &end); 61 | 62 | if (end != aIn.c_str() + aIn.size()) 63 | { 64 | return aSuffix && strcmp(end, aSuffix) == 0; 65 | } 66 | 67 | return true; 68 | } 69 | 70 | inline bool IsNumeric(const std::string& aIn, size_t aStart = 0) 71 | { 72 | return aIn.find_first_not_of("0123456789", aStart) == std::string::npos; 73 | } 74 | 75 | inline bool IsNumeric(const std::string_view& aIn, size_t aStart = 0) 76 | { 77 | return aIn.find_first_not_of("0123456789", aStart) == std::string::npos; 78 | } 79 | 80 | template 81 | requires std::is_integral_v 82 | inline std::string ToHex(T aValue) 83 | { 84 | constexpr auto max = 2 * sizeof(uint64_t); 85 | constexpr auto len = 2 * sizeof(aValue); 86 | static char buffer[max + 1]; 87 | snprintf(buffer, max + 1, "%016X", aValue); 88 | return {buffer + (max - len), len}; 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /src/Red/Addresses/Library.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red::AddressLib 4 | { 5 | constexpr uint32_t Main = 240386859; 6 | 7 | constexpr uint32_t StatsDataSystem_InitializeRecords = 1299190886; 8 | constexpr uint32_t StatsDataSystem_InitializeParams = 3652194890; 9 | constexpr uint32_t StatsDataSystem_GetStatRange = 1444748215; 10 | constexpr uint32_t StatsDataSystem_GetStatFlags = 3123320294; 11 | constexpr uint32_t StatsDataSystem_CheckStatFlag = 2954893634; 12 | 13 | constexpr uint32_t TweakDB_Init = 3062572522; 14 | constexpr uint32_t TweakDB_Load = 3602585178; // game::data::TweakDB::LoadOptimized 15 | constexpr uint32_t TweakDB_TryLoad = 3512345737; 16 | constexpr uint32_t TweakDB_CreateRecord = 838931066; // game::data::AddRecord 17 | 18 | constexpr uint32_t TweakDBID_Derive = 326438016; 19 | } 20 | -------------------------------------------------------------------------------- /src/Red/Localization.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | constexpr auto LocKeyPrefix = "LocKey#"; 6 | constexpr auto LocKeyPrefixLength = std::char_traits::length(LocKeyPrefix); 7 | } 8 | -------------------------------------------------------------------------------- /src/Red/StatsDataSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | struct StatParams 6 | { 7 | #pragma pack(push, 1) 8 | union 9 | { 10 | uint64_t range; 11 | struct 12 | { 13 | float min; 14 | float max; 15 | }; 16 | }; 17 | #pragma pack(pop) 18 | uint32_t flags; 19 | }; 20 | } 21 | 22 | namespace Raw::StatsDataSystem 23 | { 24 | using StatRecords = Core::OffsetPtr<0xD8, Red::DynArray>; 25 | using StatParams = Core::OffsetPtr<0xE8, Red::DynArray>; 26 | using StatLock = Core::OffsetPtr<0xFC, Red::SharedMutex>; 27 | 28 | constexpr auto InitializeRecords = Core::RawFunc< 29 | /* addr = */ Red::AddressLib::StatsDataSystem_InitializeRecords, 30 | /* type = */ void (*)(void* aSystem)>(); 31 | 32 | constexpr auto InitializeParams = Core::RawFunc< 33 | /* addr = */ Red::AddressLib::StatsDataSystem_InitializeParams, 34 | /* type = */ void (*)(void* aSystem)>(); 35 | 36 | constexpr auto GetStatRange = Core::RawFunc< 37 | /* addr = */ Red::AddressLib::StatsDataSystem_GetStatRange, 38 | /* type = */ uint64_t* (*)(void* aSystem, uint64_t*, uint32_t aStat)>(); 39 | 40 | constexpr auto GetStatFlags = Core::RawFunc< 41 | /* addr = */ Red::AddressLib::StatsDataSystem_GetStatFlags, 42 | /* type = */ uint32_t (*)(void* aSystem, uint32_t aStat)>(); 43 | 44 | constexpr auto CheckStatFlag = Core::RawFunc< 45 | /* addr = */ Red::AddressLib::StatsDataSystem_CheckStatFlag, 46 | /* type = */ bool (*)(void* aSystem, uint32_t aStat, uint32_t aFlag)>(); 47 | } 48 | 49 | namespace Raw::StatRecord 50 | { 51 | using EnumValue = Core::OffsetPtr<0xD8, uint32_t>; 52 | } 53 | -------------------------------------------------------------------------------- /src/Red/TweakDB/Alias.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | using TweakDBFlatValue = RED4ext::TweakDB::FlatValue; 6 | using TweakDBOffset = int32_t; 7 | using TweakDBRecord = RED4ext::gamedataTweakDBRecord; 8 | using LocKeyWrapper = RED4ext::gamedataLocKeyWrapper; 9 | } 10 | -------------------------------------------------------------------------------- /src/Red/TweakDB/Buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/TweakDB/Alias.hpp" 4 | 5 | namespace Red 6 | { 7 | class TweakDBBuffer 8 | { 9 | public: 10 | static constexpr int32_t InvalidOffset = -1; 11 | 12 | struct BufferStats 13 | { 14 | float initTime = 0.0; // ms 15 | float updateTime = 0.0; // ms 16 | size_t poolSize = 0; // bytes 17 | size_t poolValues = 0; 18 | size_t knownTypes = 0; 19 | size_t flatEntries = 0; 20 | }; 21 | 22 | TweakDBBuffer(); 23 | explicit TweakDBBuffer(Red::TweakDB* aTweakDb); 24 | 25 | int32_t AllocateValue(const Red::Value<>& aData); 26 | int32_t AllocateValue(const Red::CBaseRTTIType* aType, Red::Instance aInstance); 27 | int32_t AllocateDefault(const Red::CBaseRTTIType* aType); 28 | 29 | Red::Value<> GetValue(int32_t aOffset); 30 | Red::Instance GetValuePtr(int32_t aOffset); 31 | uint64_t GetValueHash(int32_t aOffset); 32 | 33 | [[nodiscard]] BufferStats GetStats() const; 34 | 35 | void Invalidate(); 36 | 37 | static uint64_t ComputeHash(const Red::CBaseRTTIType* aType, Red::Instance aInstance, uint32_t aSize = 0, 38 | uint64_t aSeed = 0xCBF29CE484222325); 39 | 40 | private: 41 | struct FlatTypeInfo 42 | { 43 | Red::CBaseRTTIType* type; 44 | uintptr_t offset; 45 | }; 46 | 47 | using FlatValueMap = Core::Map; // ValueHash -> BufferOffset 48 | using FlatPoolMap = Core::Map; // TypeName -> FlatPool 49 | using FlatDefaultMap = Core::Map; // TypeName -> BufferOffset 50 | using FlatTypeMap = Core::Map; // VFT -> FlatTypeInfo 51 | 52 | inline Red::Value<> ResolveOffset(int32_t aOffset); 53 | 54 | void CreatePools(); 55 | void FillDefaults(); 56 | void SyncBufferData(); 57 | void SyncBufferBounds(); 58 | void UpdateStats(float updateTime = 0); 59 | 60 | Red::TweakDB* m_tweakDb; 61 | FlatPoolMap m_pools; 62 | FlatDefaultMap m_defaults; 63 | FlatTypeMap m_types; 64 | uintptr_t m_bufferEnd; 65 | uintptr_t m_offsetEnd; 66 | BufferStats m_stats; 67 | std::shared_mutex m_poolMutex; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /src/Red/TweakDB/Manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/TweakDB/Alias.hpp" 4 | #include "Red/TweakDB/Buffer.hpp" 5 | #include "Red/TweakDB/Reflection.hpp" 6 | 7 | namespace Red 8 | { 9 | class TweakDBManager 10 | { 11 | public: 12 | class Batch 13 | { 14 | Core::Set flats; 15 | Core::Map records; 16 | Core::Map names; 17 | std::shared_mutex mutex; 18 | friend TweakDBManager; 19 | }; 20 | 21 | using BatchPtr = Core::SharedPtr; 22 | 23 | TweakDBManager(); 24 | explicit TweakDBManager(Red::TweakDB* aTweakDb); 25 | explicit TweakDBManager(Core::SharedPtr aReflection); 26 | 27 | TweakDBManager(const TweakDBManager&) = delete; 28 | TweakDBManager& operator=(const TweakDBManager&) = delete; 29 | 30 | Red::Value<> GetFlat(Red::TweakDBID aFlatId); 31 | Red::Value<> GetDefault(const Red::CBaseRTTIType* aType); 32 | Red::Handle GetRecord(Red::TweakDBID aRecordId); 33 | const Red::CClass* GetRecordType(Red::TweakDBID aRecordId); 34 | bool IsFlatExists(Red::TweakDBID aFlatId); 35 | bool IsRecordExists(Red::TweakDBID aRecordId); 36 | bool SetFlat(Red::TweakDBID aFlatId, const Red::CBaseRTTIType* aType, Red::Instance aInstance); 37 | bool SetFlat(Red::TweakDBID aFlatId, const Red::Value<>& aData); 38 | bool CreateRecord(Red::TweakDBID aRecordId, const Red::CClass* aType); 39 | bool CloneRecord(Red::TweakDBID aRecordId, Red::TweakDBID aSourceId); 40 | bool InheritProps(Red::TweakDBID aRecordId, Red::TweakDBID aSourceId); 41 | bool UpdateRecord(Red::TweakDBID aRecordId); 42 | void RegisterEnum(Red::TweakDBID aRecordId); 43 | void RegisterName(const std::string& aName, const Red::CClass* aType = nullptr); 44 | void RegisterName(Red::TweakDBID aId, const std::string& aName, const Red::CClass* aType = nullptr); 45 | const Core::Set& GetEnums(); 46 | std::string_view GetName(Red::TweakDBID aId); 47 | 48 | BatchPtr StartBatch(); 49 | const Core::Set& GetFlats(const BatchPtr& aBatch); 50 | Red::Value<> GetFlat(const BatchPtr& aBatch, Red::TweakDBID aFlatId); 51 | const Red::CClass* GetRecordType(const BatchPtr& aBatch, Red::TweakDBID aRecordId); 52 | bool IsFlatExists(const BatchPtr& aBatch, Red::TweakDBID aFlatId); 53 | bool IsRecordExists(const BatchPtr& aBatch, Red::TweakDBID aRecordId); 54 | bool SetFlat(const BatchPtr& aBatch, Red::TweakDBID aFlatId, const Red::CBaseRTTIType* aType, Red::Instance aValue); 55 | bool SetFlat(const BatchPtr& aBatch, Red::TweakDBID aFlatId, const Red::Value<>& aData); 56 | bool CreateRecord(const BatchPtr& aBatch, Red::TweakDBID aRecordId, const Red::CClass* aType); 57 | bool CloneRecord(const BatchPtr& aBatch, Red::TweakDBID aRecordId, Red::TweakDBID aSourceId); 58 | bool InheritProps(const BatchPtr& aBatch, Red::TweakDBID aRecordId, Red::TweakDBID aSourceId); 59 | bool UpdateRecord(const BatchPtr& aBatch, Red::TweakDBID aRecordId); 60 | void RegisterEnum(const BatchPtr& aBatch, Red::TweakDBID aRecordId); 61 | void RegisterName(const BatchPtr& aBatch, Red::TweakDBID aId, const std::string& aName); 62 | void CommitBatch(const BatchPtr& aBatch); 63 | 64 | void Invalidate(); 65 | 66 | Red::TweakDB* GetTweakDB(); 67 | Core::SharedPtr& GetReflection(); 68 | 69 | private: 70 | template 71 | inline bool AssignFlat(Red::SortedUniqueArray& aFlats, Red::TweakDBID aFlatId, 72 | const Red::CBaseRTTIType* aType, Red::Instance aInstance, 73 | SharedLockable& aMutex); 74 | inline void InheritFlats(Red::SortedUniqueArray& aFlats, Red::TweakDBID aRecordId, 75 | const Red::TweakDBRecordInfo* aRecordInfo); 76 | inline void InheritFlats(Red::SortedUniqueArray& aFlats, Red::TweakDBID aRecordId, 77 | const Red::TweakDBRecordInfo* aRecordInfo, Red::TweakDBID aSourceId); 78 | 79 | inline bool AssignFlat(const Red::TweakDBManager::BatchPtr& aBatch, Red::TweakDBID aFlatId, 80 | const Red::Value<>& aValue); 81 | inline void InheritFlats(const Red::TweakDBManager::BatchPtr& aBatch, Red::TweakDBID aRecordId, 82 | const Red::TweakDBRecordInfo* aRecordInfo); 83 | inline void InheritFlats(const Red::TweakDBManager::BatchPtr& aBatch, Red::TweakDBID aRecordId, 84 | const Red::TweakDBRecordInfo* aRecordInfo, Red::TweakDBID aSourceId); 85 | 86 | void CreateBaseName(Red::TweakDBID aId, const std::string& aName); 87 | void CreateExtraNames(Red::TweakDBID aId, const std::string& aName, const Red::CClass* aType = nullptr); 88 | 89 | Red::TweakDB* m_tweakDb; 90 | Core::SharedPtr m_buffer; 91 | Core::SharedPtr m_reflection; 92 | Core::Map m_knownNames; 93 | Core::Set m_knownEnums; 94 | std::shared_mutex m_mutex; 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /src/Red/TweakDB/Raws.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Alias.hpp" 4 | 5 | namespace Raw 6 | { 7 | constexpr auto InitTweakDB = Core::RawFunc< 8 | /* addr = */ Red::AddressLib::TweakDB_Init, 9 | /* type = */ void (*)(void*, void*)>(); 10 | 11 | constexpr auto LoadTweakDB = Core::RawFunc< 12 | /* addr = */ Red::AddressLib::TweakDB_Load, 13 | /* type = */ void (*)(Red::TweakDB*, Red::CString&)>(); 14 | 15 | constexpr auto TryLoadTweakDB = Core::RawFunc< 16 | /* addr = */ Red::AddressLib::TweakDB_TryLoad, 17 | /* type = */ bool (*)(void* a1, Red::TweakDB*, Red::CString&, void* a4)>(); 18 | 19 | constexpr auto CreateRecord = Core::RawFunc< 20 | /* addr = */ Red::AddressLib::TweakDB_CreateRecord, 21 | /* type = */ void (*)(Red::TweakDB*, uint32_t, Red::TweakDBID)>(); 22 | 23 | constexpr auto CreateTweakDBID = Core::RawFunc< 24 | /* addr = */ Red::AddressLib::TweakDBID_Derive, 25 | /* type = */ void (*)(const Red::TweakDBID*, const Red::TweakDBID*, const char*)>(); 26 | } 27 | -------------------------------------------------------------------------------- /src/Red/TweakDB/Source/Errors.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/TweakDB/Source/Grammar.hpp" 4 | 5 | #define RED_TWEAK_ERROR(rule, msg) template<> static constexpr auto message = msg; 6 | 7 | namespace Red 8 | { 9 | struct TweakError 10 | { 11 | template 12 | static constexpr const char* message = nullptr; 13 | 14 | RED_TWEAK_ERROR(TweakGrammar::package_name, "Expected package name"); 15 | RED_TWEAK_ERROR(TweakGrammar::using_name, "Expected package name"); 16 | 17 | RED_TWEAK_ERROR(TweakGrammar::tag_name, "Expected tag name"); 18 | RED_TWEAK_ERROR(TweakGrammar::tag_sfx, "Expected ']'"); 19 | 20 | RED_TWEAK_ERROR(TweakGrammar::group_name, "Expected group name"); 21 | RED_TWEAK_ERROR(TweakGrammar::group_base, "Expected group name"); 22 | RED_TWEAK_ERROR(TweakGrammar::group_begin, "Expected '{'"); // if pos == operator, then missing flat type 23 | RED_TWEAK_ERROR(TweakGrammar::group_end, "Expected '}'"); 24 | 25 | RED_TWEAK_ERROR(TweakGrammar::inline_base, "Expected group name"); 26 | RED_TWEAK_ERROR(TweakGrammar::inline_end, "Expected '}'"); 27 | 28 | RED_TWEAK_ERROR(TweakGrammar::flat_name, "Expected flat name"); 29 | RED_TWEAK_ERROR(TweakGrammar::flat_op, "Expected assignment operator"); 30 | RED_TWEAK_ERROR(TweakGrammar::flat_value, "Invalid value"); 31 | RED_TWEAK_ERROR(TweakGrammar::flat_end, "Expected ';'"); 32 | 33 | RED_TWEAK_ERROR(TweakGrammar::array_item, "Invalid array item"); 34 | RED_TWEAK_ERROR(TweakGrammar::array_sep, "Expected ','"); 35 | RED_TWEAK_ERROR(TweakGrammar::array_end, "Expected ']'"); 36 | 37 | RED_TWEAK_ERROR(TweakGrammar::source_with_package_member, "Expected group or flat definition"); 38 | RED_TWEAK_ERROR(TweakGrammar::source_no_package_member, "Expected group definition"); 39 | RED_TWEAK_ERROR(TweakGrammar::source, "Invalid tweak file"); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/Red/TweakDB/Source/Parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/TweakDB/Source/Grammar.hpp" 4 | #include "Red/TweakDB/Source/Errors.hpp" 5 | #include "Red/TweakDB/Source/Source.hpp" 6 | 7 | namespace Red 8 | { 9 | class TweakParser 10 | { 11 | public: 12 | static Core::SharedPtr Parse(const std::filesystem::path& aPath); 13 | 14 | private: 15 | struct ParseState 16 | { 17 | Core::Vector tags; 18 | Core::SharedPtr group; 19 | Core::SharedPtr flat; 20 | Core::SharedPtr value; 21 | 22 | using Parent = std::pair, Core::SharedPtr>; 23 | Core::Vector nested; 24 | Core::SharedPtr closed; 25 | 26 | std::string flatType; 27 | std::string foreignType; 28 | bool isArray = false; 29 | bool hasType = false; 30 | }; 31 | 32 | template 33 | using ParseControl = tao::pegtl::must_if::control; 34 | 35 | template 36 | struct ParseAction {}; 37 | 38 | static ETweakFlatType ResolveType(const std::string& aInput); 39 | static ETweakFlatOp ResolveOperation(const std::string& aInput); 40 | 41 | static std::string FormatError(const std::filesystem::path& aPath, const tao::pegtl::position& aPosition, 42 | const std::string_view& aMessage); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/Red/TweakDB/Source/Source.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | struct TweakGroup; 6 | struct TweakFlat; 7 | struct TweakValue; 8 | struct TweakInline; 9 | 10 | enum class ETweakValueType 11 | { 12 | Undefined, 13 | Bool, 14 | Number, 15 | String, 16 | Struct, 17 | Inline, 18 | }; 19 | 20 | enum class ETweakFlatType 21 | { 22 | Undefined, 23 | Int, 24 | Float, 25 | Bool, 26 | String, 27 | CName, 28 | LocKey, 29 | ResRef, 30 | Quaternion, 31 | EulerAngles, 32 | Vector3, 33 | Vector2, 34 | Color, 35 | ForeignKey, 36 | }; 37 | 38 | enum class ETweakFlatOp 39 | { 40 | Undefined, 41 | Assign, 42 | Append, 43 | Remove, 44 | }; 45 | 46 | struct TweakValue 47 | { 48 | ETweakValueType type{ETweakValueType::Undefined}; 49 | Core::Vector data; 50 | Core::SharedPtr group; 51 | }; 52 | 53 | struct TweakFlat 54 | { 55 | std::string name; 56 | ETweakFlatType type{ETweakFlatType::Undefined}; 57 | std::string foreignType; 58 | bool isArray{false}; 59 | ETweakFlatOp operation{ETweakFlatOp::Undefined}; 60 | Core::Vector> values; 61 | Core::Vector tags; 62 | }; 63 | 64 | struct TweakGroup 65 | { 66 | std::string name; 67 | std::string base; 68 | Core::Vector> flats; 69 | Core::Vector tags; 70 | bool isSchema{false}; 71 | bool isQuery{false}; 72 | }; 73 | 74 | struct TweakInline 75 | { 76 | Core::SharedPtr group; 77 | Core::SharedPtr owner; 78 | Core::SharedPtr parent; 79 | }; 80 | 81 | struct TweakSource 82 | { 83 | static constexpr auto Extension = L".tweak"; 84 | static constexpr auto SchemaPackage = "RTDB"; 85 | static constexpr auto QueryPackage = "Query"; 86 | 87 | std::string package; 88 | Core::Vector usings; 89 | Core::Vector> groups; 90 | Core::Vector> flats; 91 | Core::Vector> inlines; 92 | bool isPackage{false}; 93 | bool isSchema{false}; 94 | bool isQuery{false}; 95 | }; 96 | 97 | using TweakGroupPtr = Core::SharedPtr; 98 | using TweakFlatPtr = Core::SharedPtr; 99 | using TweakValuePtr = Core::SharedPtr; 100 | using TweakInlinePtr = Core::SharedPtr; 101 | using TweakSourcePtr = Core::SharedPtr; 102 | } 103 | -------------------------------------------------------------------------------- /src/Red/Value.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | using Instance = void*; 6 | 7 | template 8 | using InstancePtr = Core::SharedPtr; 9 | 10 | template 11 | InstancePtr MakeInstance(Args&&... args) 12 | { 13 | return Core::MakeShared(std::forward(args)...); 14 | } 15 | 16 | template 17 | struct Value 18 | { 19 | Value(CBaseRTTIType* aType = nullptr, T* aInstance = nullptr) 20 | : type(aType) 21 | , instance(aInstance) 22 | { 23 | } 24 | 25 | Value(const CBaseRTTIType* aType, T* aInstance = nullptr) 26 | : type(const_cast(aType)) 27 | , instance(aInstance) 28 | { 29 | } 30 | 31 | Value(const CStackType& aData) 32 | : type(aData.type) 33 | , instance(aData.value) 34 | { 35 | } 36 | 37 | [[nodiscard]] inline T* operator->() const 38 | { 39 | return instance; 40 | } 41 | 42 | [[nodiscard]] inline operator T*() const 43 | { 44 | return instance; 45 | } 46 | 47 | [[nodiscard]] inline operator CStackType*() const 48 | { 49 | return reinterpret_cast(this); 50 | } 51 | 52 | [[nodiscard]] inline operator const CStackType&() const 53 | { 54 | return *reinterpret_cast(this); 55 | } 56 | 57 | explicit operator bool() const noexcept 58 | { 59 | return instance != nullptr; 60 | } 61 | 62 | template 63 | requires (std::is_pointer_v) 64 | inline U As() 65 | { 66 | return *reinterpret_cast(instance); 67 | } 68 | 69 | template 70 | requires (!std::is_pointer_v) 71 | inline U& As() 72 | { 73 | return *reinterpret_cast(instance); 74 | } 75 | 76 | constexpr bool operator==(const Value& aRhs) const noexcept 77 | { 78 | return type == aRhs.type && (!type || instance == aRhs.instance || type->IsEqual(instance, aRhs.instance)); 79 | } 80 | 81 | CBaseRTTIType* type; 82 | T* instance; 83 | }; 84 | 85 | template 86 | struct ManagedValue : Value 87 | { 88 | using Data = Value; 89 | 90 | ManagedValue() 91 | : Data(nullptr, nullptr) 92 | { 93 | } 94 | 95 | ManagedValue(const CBaseRTTIType* aType, void* aInstance = nullptr) 96 | : Data(aType) 97 | { 98 | Data::instance = Data::type->GetAllocator()->AllocAligned(Data::type->GetSize(), Data::type->GetAlignment()).memory; 99 | 100 | std::memset(Data::instance, 0, Data::type->GetSize()); 101 | Data::type->Construct(Data::instance); 102 | 103 | if (aInstance) 104 | { 105 | Data::type->Assign(Data::instance, aInstance); 106 | } 107 | } 108 | 109 | template 110 | requires (!std::is_void_v) 111 | ManagedValue(CBaseRTTIType* aType, Args&&... aArgs) 112 | : Data(aType) 113 | { 114 | Data::instance = Data::type->GetAllocator()->AllocAligned(Data::type->GetSize(), Data::type->GetAlignment()).memory; 115 | new (Data::instance) T(std::forward(aArgs)...); 116 | } 117 | 118 | ~ManagedValue() 119 | { 120 | if (Data::type && Data::instance) 121 | { 122 | Data::type->Destruct(Data::instance); 123 | Data::type->GetAllocator()->Free(Data::instance); 124 | } 125 | } 126 | 127 | ManagedValue& operator=(void* aInstance) 128 | { 129 | Data::type->Assign(Data::instance, aInstance); 130 | } 131 | }; 132 | 133 | template 134 | using ValuePtr = Core::SharedPtr>; 135 | 136 | template 137 | requires (!std::is_void_v) 138 | ValuePtr MakeValue(const CBaseRTTIType* aType, Args&&... aArgs) 139 | { 140 | return Core::MakeShared>(aType, std::forward(aArgs)...); 141 | } 142 | 143 | template 144 | ValuePtr MakeValue(const CBaseRTTIType* aType, void* aInstance = nullptr) 145 | { 146 | return Core::MakeShared>(aType, aInstance); 147 | } 148 | 149 | template 150 | requires (!std::is_void_v) 151 | ValuePtr MakeValue(CName aTypeName, Args&&... aArgs) 152 | { 153 | return Core::MakeShared>(CRTTISystem::Get()->GetType(aTypeName), std::forward(aArgs)...); 154 | } 155 | 156 | template 157 | ValuePtr MakeValue(CName aTypeName, void* aInstance = nullptr) 158 | { 159 | return Core::MakeShared>(CRTTISystem::Get()->GetType(aTypeName), aInstance); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "App/Application.hpp" 2 | #include "App/Project.hpp" 3 | #include "Core/Facades/Hook.hpp" 4 | #include "Core/Facades/Runtime.hpp" 5 | 6 | namespace 7 | { 8 | Core::UniquePtr g_app; 9 | } 10 | 11 | // RED4ext 12 | 13 | RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::PluginHandle aHandle, RED4ext::EMainReason aReason, 14 | const RED4ext::Sdk* aSdk) 15 | { 16 | switch (aReason) 17 | { 18 | case RED4ext::EMainReason::Load: 19 | { 20 | g_app = Core::MakeUnique(aHandle, aSdk); 21 | g_app->Bootstrap(); 22 | break; 23 | } 24 | case RED4ext::EMainReason::Unload: 25 | { 26 | g_app->Shutdown(); 27 | g_app = nullptr; 28 | break; 29 | } 30 | } 31 | 32 | return true; 33 | } 34 | 35 | RED4EXT_C_EXPORT void RED4EXT_CALL Query(RED4ext::PluginInfo* aInfo) 36 | { 37 | aInfo->name = App::Project::NameW; 38 | aInfo->author = App::Project::AuthorW; 39 | aInfo->version = RED4EXT_SEMVER(App::Project::Version.major, 40 | App::Project::Version.minor, 41 | App::Project::Version.patch); 42 | 43 | aInfo->runtime = RED4EXT_RUNTIME_INDEPENDENT; 44 | aInfo->sdk = RED4EXT_SDK_LATEST; 45 | } 46 | 47 | RED4EXT_C_EXPORT uint32_t RED4EXT_CALL Supports() 48 | { 49 | return RED4EXT_API_VERSION_LATEST; 50 | } 51 | 52 | // ASI 53 | 54 | BOOL APIENTRY DllMain(HMODULE aHandle, DWORD aReason, LPVOID) 55 | { 56 | using GameMain = Core::RawFunc; 58 | 59 | static const bool s_isGame = Core::Runtime::IsEXE(L"Cyberpunk2077.exe"); 60 | static const bool s_isASI = Core::Runtime::IsASI(aHandle); 61 | 62 | switch (aReason) // NOLINT(hicpp-multiway-paths-covered) 63 | { 64 | case DLL_PROCESS_ATTACH: 65 | { 66 | DisableThreadLibraryCalls(aHandle); 67 | 68 | if (s_isGame && s_isASI) 69 | { 70 | g_app = Core::MakeUnique(aHandle); 71 | 72 | Core::Hook::Before(+[]() { 73 | g_app->Bootstrap(); 74 | }); 75 | } 76 | break; 77 | } 78 | case DLL_PROCESS_DETACH: 79 | { 80 | if (s_isGame && s_isASI) 81 | { 82 | g_app->Shutdown(); 83 | g_app = nullptr; 84 | } 85 | break; 86 | } 87 | } 88 | 89 | return TRUE; 90 | } 91 | -------------------------------------------------------------------------------- /src/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.hpp" 2 | -------------------------------------------------------------------------------- /src/pch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "Core/Raw.hpp" 44 | #include "Core/Stl.hpp" 45 | 46 | #include "Red/Alias.hpp" 47 | #include "Red/Engine.hpp" 48 | #include "Red/TypeInfo.hpp" 49 | #include "Red/Specializations.hpp" 50 | #include "Red/Utils.hpp" 51 | #include "Red/Value.hpp" 52 | 53 | #include "Red/Addresses/Library.hpp" 54 | -------------------------------------------------------------------------------- /src/rtti.cpp: -------------------------------------------------------------------------------- 1 | #include "App/Facade.hpp" 2 | -------------------------------------------------------------------------------- /support/red4ext/TweakXL.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class TweakXL 14 | { 15 | public: 16 | inline static bool RegisterTweak(std::filesystem::path aPath) 17 | { 18 | std::error_code error; 19 | 20 | if (!std::filesystem::exists(aPath, error) || !std::filesystem::is_regular_file(aPath, error)) 21 | { 22 | return false; 23 | } 24 | 25 | return RegisterPathOrQueue(std::move(aPath)); 26 | } 27 | 28 | inline static bool RegisterTweak(HMODULE aHandle, std::filesystem::path aPath) 29 | { 30 | if (aPath.is_relative()) 31 | { 32 | aPath = GetModulePath(aHandle) / aPath; 33 | } 34 | 35 | return RegisterTweak(std::move(aPath)); 36 | } 37 | 38 | inline static bool RegisterTweaks(std::filesystem::path aPath) 39 | { 40 | std::error_code error; 41 | 42 | if (!std::filesystem::exists(aPath, error) || !std::filesystem::is_directory(aPath, error)) 43 | { 44 | return false; 45 | } 46 | 47 | return RegisterPathOrQueue(std::move(aPath)); 48 | } 49 | 50 | inline static bool RegisterTweaks(HMODULE aHandle, std::filesystem::path aPath) 51 | { 52 | if (aPath.is_relative()) 53 | { 54 | aPath = GetModulePath(aHandle) / aPath; 55 | } 56 | 57 | return RegisterTweaks(std::move(aPath)); 58 | } 59 | 60 | private: 61 | TweakXL() = default; 62 | 63 | inline static bool Initialize() 64 | { 65 | if (s_facade) 66 | return true; 67 | 68 | if (!RED4ext::Memory::Vault::Get()->poolRegistry.Get(RED4ext::Memory::PoolEngine::Name)) 69 | return false; 70 | 71 | auto facade = RED4ext::CRTTISystem::Get()->GetClass("TweakXL"); 72 | 73 | if (!facade || !facade->GetFunction("Version")) 74 | return false; 75 | 76 | s_facade = facade; 77 | return true; 78 | } 79 | 80 | inline static bool RegisterPathOrQueue(std::filesystem::path aPath) 81 | { 82 | if (!Initialize()) 83 | { 84 | s_paths.push_back(std::move(aPath)); 85 | 86 | if (s_paths.size() == 1) 87 | { 88 | RegisterPendingPaths(); 89 | } 90 | 91 | return true; 92 | } 93 | 94 | return RegisterPath(aPath); 95 | } 96 | 97 | inline static bool RegisterPath(const std::filesystem::path& aPath) 98 | { 99 | bool success; 100 | RED4ext::CString path(aPath.string()); 101 | 102 | auto rtti = RED4ext::CRTTISystem::Get(); 103 | RED4ext::CStackType arg(rtti->GetType("String"), &path); 104 | RED4ext::CStackType result(rtti->GetType("Bool"), &success); 105 | RED4ext::CStack stack(nullptr, &arg, 1, &result); 106 | 107 | std::error_code error; 108 | RED4ext::CName method(std::filesystem::is_directory(aPath, error) ? "RegisterDir" : "RegisterTweak"); 109 | 110 | s_facade->GetFunction(method)->Execute(&stack); 111 | 112 | return success; 113 | } 114 | 115 | static inline void RegisterPendingPaths() 116 | { 117 | if (Initialize()) 118 | { 119 | for (const auto& path : s_paths) 120 | { 121 | RegisterPath(path); 122 | } 123 | 124 | s_paths.clear(); 125 | } 126 | else 127 | { 128 | RED4ext::CRTTISystem::Get()->AddPostRegisterCallback(&RegisterPendingPaths); 129 | } 130 | } 131 | 132 | inline static std::filesystem::path GetModulePath(HMODULE aHandle) 133 | { 134 | wchar_t path[MAX_PATH]{0}; 135 | auto length = GetModuleFileNameW(aHandle, path, MAX_PATH); 136 | 137 | return std::filesystem::path(path).parent_path(); 138 | } 139 | 140 | inline static RED4ext::CClass* s_facade; 141 | inline static std::vector s_paths; 142 | }; 143 | -------------------------------------------------------------------------------- /tools/dist/package-mod.ps1: -------------------------------------------------------------------------------- 1 | param ($ReleaseBin, $ProjectName = "TweakXL") 2 | 3 | $StageDir = "build/package" 4 | $DistDir = "build/dist" 5 | $Version = & $($PSScriptRoot + "\steps\get-version.ps1") 6 | 7 | & $($PSScriptRoot + "\steps\compose-red4ext.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} -ReleaseBin ${ReleaseBin} 8 | & $($PSScriptRoot + "\steps\compose-redscripts.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} -Version ${Version} 9 | & $($PSScriptRoot + "\steps\compose-data.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} 10 | & $($PSScriptRoot + "\steps\compose-licenses.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} 11 | & $($PSScriptRoot + "\steps\make-tweaks-dir.ps1") -StageDir ${StageDir} 12 | & $($PSScriptRoot + "\steps\create-zip-from-stage.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} -DistDir ${DistDir} -Version ${Version} 13 | 14 | Remove-Item -Recurse ${StageDir} 15 | -------------------------------------------------------------------------------- /tools/dist/steps/compose-data.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ReleaseBin, $ProjectName) 2 | 3 | $DataDir = "${StageDir}/red4ext/plugins/${ProjectName}/Data" 4 | 5 | New-Item -ItemType directory -Force -Path ${DataDir} | Out-Null 6 | Copy-Item -Path "data/*.dat" -Destination ${DataDir} 7 | -------------------------------------------------------------------------------- /tools/dist/steps/compose-licenses.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ReleaseBin, $ProjectName) 2 | 3 | $DataDir = "${StageDir}/red4ext/plugins/${ProjectName}" 4 | 5 | New-Item -ItemType directory -Force -Path ${DataDir} | Out-Null 6 | Copy-Item -Path "LICENSE" -Destination ${DataDir} 7 | Copy-Item -Path "THIRD_PARTY_LICENSES" -Destination ${DataDir} 8 | -------------------------------------------------------------------------------- /tools/dist/steps/compose-red4ext.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ReleaseBin, $ProjectName) 2 | 3 | $PluginDir = "${StageDir}/red4ext/plugins/${ProjectName}" 4 | 5 | New-Item -ItemType directory -Force -Path ${PluginDir} | Out-Null 6 | Copy-Item -Path ${ReleaseBin} -Destination "${PluginDir}/${ProjectName}.dll" 7 | -------------------------------------------------------------------------------- /tools/dist/steps/compose-redscripts.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ProjectName, $Version, $GlobalScope = "${ProjectName}.Global") 2 | 3 | $ScriptsDir = "${StageDir}/red4ext/plugins/${ProjectName}/Scripts" 4 | 5 | $SourceFiles = Get-ChildItem -Path "scripts" -Filter *.reds -Recurse 6 | $Bundles = @{} 7 | 8 | foreach ($ScriptFile in $SourceFiles) { 9 | $Content = Get-Content $ScriptFile.FullName 10 | $Module = ($Content | Select-String -Pattern "^module\s+(.+)" -List | %{$_.matches.groups[1].Value}) 11 | $Imports = ($Content | Select-String -Pattern "^import\s+(.+)" -List | %{$_.matches.groups[1].Value}) 12 | $Source = ($Content | Select-String -Pattern "^\s*(//|module\s|import\s)" -NotMatch | Select-String -Pattern "^\s*$" -NotMatch) | Out-String 13 | $Scope = $Module ?? $GlobalScope 14 | 15 | if ($Bundles[$Scope] -eq $null) { 16 | $Bundles[$Scope] = @{ 17 | Scope = $Scope 18 | Module = $Module 19 | Imports = @{} 20 | Sources = [System.Collections.ArrayList]@() 21 | } 22 | } 23 | 24 | $Bundle = $Bundles[$Scope] 25 | $Bundle.Sources.Add($Source.Trim()) > $null 26 | 27 | if ($Imports -ne $null) { 28 | foreach ($Import in $Imports) { 29 | $Bundle.Imports[$Import] = 1 30 | } 31 | } 32 | } 33 | 34 | New-Item -ItemType directory -Force -Path ${ScriptsDir} | Out-Null 35 | 36 | foreach ($Bundle in $Bundles.Values) { 37 | $BundleFile = "${ScriptsDir}/$($Bundle.Scope).reds" 38 | Out-File -FilePath ${BundleFile} -Encoding ascii -InputObject "// ${ProjectName} ${Version}" 39 | 40 | if ($Bundle.Module) { 41 | Out-File -FilePath ${BundleFile} -Encoding ascii -InputObject "module $($Bundle.Module)" -Append 42 | } 43 | 44 | foreach ($Import in $Bundle.Imports.Keys) { 45 | Out-File -FilePath ${BundleFile} -Encoding ascii -InputObject "import ${Import}" -Append 46 | } 47 | 48 | foreach ($Source in $Bundle.Sources) { 49 | if ($Source) { 50 | Out-File -FilePath ${BundleFile} -Encoding ascii -InputObject "`n${Source}" -Append 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tools/dist/steps/create-zip-from-stage.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ProjectName, $DistDir, $Version, $Suffix = "") 2 | 3 | if ($Version -eq $null) { 4 | $Version = Select-String -Path "src/App/Project.hpp" -Pattern """(\d+\.\d+\.\d+)""" -List | % {"$($_.Matches.Groups[1])"} 5 | } 6 | 7 | New-Item -ItemType directory -Force -Path ${DistDir} | Out-Null 8 | Compress-Archive -Path "${StageDir}/*" -CompressionLevel Optimal -Force -DestinationPath "${DistDir}/${ProjectName}-${Version}${Suffix}.zip" 9 | -------------------------------------------------------------------------------- /tools/dist/steps/get-version.ps1: -------------------------------------------------------------------------------- 1 | Select-String -Path "src/App/Project.hpp" -Pattern """(\d+\.\d+\.\d+)""" -List | %{"$($_.Matches.Groups[1])"} | Write-Output 2 | -------------------------------------------------------------------------------- /tools/dist/steps/install-from-stage.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $GameDir) 2 | 3 | Copy-Item -Path "${StageDir}/*" -Recurse -Force -Destination ${GameDir} 4 | -------------------------------------------------------------------------------- /tools/dist/steps/make-tweaks-dir.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir) 2 | 3 | $TweaksDir = "${StageDir}/r6/tweaks" 4 | 5 | New-Item -ItemType directory -Force -Path ${TweaksDir} | Out-Null 6 | -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | set_xmakever("2.5.9") 2 | 3 | set_project("TweakXL") 4 | set_version("1.10.9", {build = "%y%m%d%H%M"}) 5 | 6 | set_arch("x64") 7 | set_languages("cxx2a") 8 | add_cxxflags("/MP /GR- /EHsc") 9 | 10 | if is_mode("debug") then 11 | set_symbols("debug") 12 | set_optimize("none") 13 | add_cxxflags("/Od /Ob0 /Zi /RTC1") 14 | elseif is_mode("release") then 15 | set_symbols("hidden") 16 | set_strip("all") 17 | set_optimize("fastest") 18 | add_cxxflags("/Ob2") 19 | elseif is_mode("releasedbg") then 20 | set_symbols("debug") 21 | set_strip("all") 22 | set_optimize("fastest") 23 | add_cxxflags("/Ob1 /Zi") 24 | end 25 | 26 | if is_mode("debug") then 27 | set_runtimes("MDd") 28 | else 29 | set_runtimes("MD") 30 | end 31 | 32 | add_requires("hopscotch-map", "minhook", "spdlog", "tiltedcore", "yaml-cpp") 33 | 34 | target("TweakXL") 35 | set_default(true) 36 | set_kind("shared") 37 | set_filename("TweakXL.dll") 38 | set_pcxxheader("src/pch.hpp") 39 | add_files("src/**.cpp", "src/**.rc", "lib/**.cpp") 40 | add_headerfiles("src/**.hpp", "lib/**.hpp") 41 | add_includedirs("src/", "lib/") 42 | add_deps("RED4ext.SDK", "nameof", "semver", "wil", "pegtl") 43 | add_packages("hopscotch-map", "minhook", "spdlog", "tiltedcore", "yaml-cpp") 44 | add_syslinks("Version", "User32") 45 | add_defines("WINVER=0x0601", "WIN32_LEAN_AND_MEAN", "NOMINMAX") 46 | set_configdir("src") 47 | add_configfiles("config/Project.hpp.in", {prefixdir = "App"}) 48 | add_configfiles("config/Version.rc.in", {prefixdir = "App"}) 49 | set_configvar("AUTHOR", "psiberx") 50 | set_configvar("NAME", "TweakXL") 51 | 52 | target("RED4ext.SDK") 53 | set_default(false) 54 | set_kind("static") 55 | set_group("vendor") 56 | add_headerfiles("vendor/RED4ext.SDK/include/**.hpp") 57 | add_includedirs("vendor/RED4ext.SDK/include/", { public = true }) 58 | 59 | target("nameof") 60 | set_default(false) 61 | set_kind("static") 62 | set_group("vendor") 63 | add_headerfiles("vendor/nameof/include/**.hpp") 64 | add_includedirs("vendor/nameof/include/", { public = true }) 65 | 66 | target("semver") 67 | set_default(false) 68 | set_kind("static") 69 | set_group("vendor") 70 | add_headerfiles("vendor/semver/include/**.hpp") 71 | add_includedirs("vendor/semver/include/", { public = true }) 72 | 73 | target("pegtl") 74 | set_default(false) 75 | set_kind("static") 76 | set_group("vendor") 77 | add_headerfiles("vendor/pegtl/include/**.hpp") 78 | add_includedirs("vendor/pegtl/include/", { public = true }) 79 | 80 | target("wil") 81 | set_default(false) 82 | set_kind("static") 83 | set_group("vendor") 84 | add_headerfiles("vendor/wil/include/**.h") 85 | add_includedirs("vendor/wil/include/", { public = true }) 86 | 87 | add_rules("plugin.vsxmake.autoupdate") 88 | --------------------------------------------------------------------------------