├── .clang-format ├── .editorconfig ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── THIRD_PARTY_LICENSES ├── bundle ├── ArchiveXL.cpmodproj ├── scripts │ ├── AXL_GenerateCustomizationFixes.wscript │ └── AXL_GenerateCustomizationResources.wscript └── source │ ├── raw │ └── archive_xl │ │ ├── characters │ │ ├── common │ │ │ ├── eyes │ │ │ │ ├── heb__01.mi.json │ │ │ │ ├── hel_pma.mi.json │ │ │ │ └── hel_pwa.mi.json │ │ │ └── hair │ │ │ │ ├── h1_base_color_patch.mesh.json │ │ │ │ └── textures │ │ │ │ └── hair_profiles │ │ │ │ └── base_material.mi.json │ │ └── head │ │ │ └── player_base_heads │ │ │ ├── appearances │ │ │ └── head │ │ │ │ ├── eyebrows │ │ │ │ ├── heb_000_pma__basehead_01.app.json │ │ │ │ └── heb_000_pwa__basehead_01.app.json │ │ │ │ ├── he_000_pma__basehead.app.json │ │ │ │ ├── he_000_pwa__basehead.app.json │ │ │ │ ├── hel_000_pma__basehead.app.json │ │ │ │ └── hel_000_pwa__basehead.app.json │ │ │ ├── player_female_average │ │ │ ├── h0_000_pwa_c__basehead │ │ │ │ ├── he_000_pwa_c__basehead_patch.mesh.json │ │ │ │ ├── heb_000_pwa__basehead_01.mesh.json │ │ │ │ └── hel_000_pwa_c__basehead_patch.mesh.json │ │ │ └── heb_000_pwa__basehead_01.morphtarget.json │ │ │ └── player_man_average │ │ │ ├── h0_000_pma_c__basehead │ │ │ ├── he_000_pma_c__basehead_patch.mesh.json │ │ │ ├── heb_000_pma__basehead_01.mesh.json │ │ │ └── hel_000_pma_c__basehead_patch.mesh.json │ │ │ └── heb_000_pma__basehead_01.morphtarget.json │ │ └── common │ │ └── null.morphtarget.json │ └── resources │ ├── Migration.xl │ ├── PhotoModeScope.xl │ ├── PlayerBaseScope.xl │ ├── PlayerCustomizationBeardFix.xl │ ├── PlayerCustomizationBeardScope.xl │ ├── PlayerCustomizationBrowsFix.xl │ ├── PlayerCustomizationBrowsPatch.xl │ ├── PlayerCustomizationBrowsScope.xl │ ├── PlayerCustomizationEyesFix.xl │ ├── PlayerCustomizationEyesPatch.xl │ ├── PlayerCustomizationEyesScope.xl │ ├── PlayerCustomizationHairFix.xl │ ├── PlayerCustomizationHairPatch.xl │ ├── PlayerCustomizationHairScope.xl │ ├── PlayerCustomizationLashesFix.xl │ ├── PlayerCustomizationLashesPatch.xl │ ├── PlayerCustomizationLashesScope.xl │ ├── PlayerCustomizationScope.xl │ └── QuestBaseScope.xl ├── config ├── Project.hpp.in └── Version.rc.in ├── 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 ├── DynamicAppearance.reds ├── Facade.reds └── Module.reds ├── src ├── App │ ├── Application.cpp │ ├── Application.hpp │ ├── Archives │ │ ├── ArchiveService.cpp │ │ └── ArchiveService.hpp │ ├── Environment.hpp │ ├── Extensions │ │ ├── Animation │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── Attachment │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── Customization │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── ExtensionBase.hpp │ │ ├── ExtensionLoader.cpp │ │ ├── ExtensionLoader.hpp │ │ ├── ExtensionService.cpp │ │ ├── ExtensionService.hpp │ │ ├── FactoryIndex │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── Garment │ │ │ ├── ChunkMask.hpp │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Dynamic.cpp │ │ │ ├── Dynamic.hpp │ │ │ ├── Extension.cpp │ │ │ ├── Extension.hpp │ │ │ ├── Prefix.cpp │ │ │ ├── Prefix.hpp │ │ │ ├── States.cpp │ │ │ ├── States.hpp │ │ │ ├── Tags.cpp │ │ │ ├── Tags.hpp │ │ │ ├── Wrapper.cpp │ │ │ └── Wrapper.hpp │ │ ├── InkSpawner │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── Journal │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── Localization │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ ├── Extension.hpp │ │ │ ├── Language.cpp │ │ │ └── Language.hpp │ │ ├── Mesh │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── PhotoMode │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── PuppetState │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ ├── Extension.hpp │ │ │ ├── Handler.cpp │ │ │ ├── Handler.hpp │ │ │ ├── System.cpp │ │ │ └── System.hpp │ │ ├── QuestPhase │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── ResourceLink │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── ResourceMeta │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── ResourcePatch │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ ├── Transmog │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ │ └── WorldStreaming │ │ │ ├── Config.cpp │ │ │ ├── Config.hpp │ │ │ ├── Extension.cpp │ │ │ └── Extension.hpp │ ├── Facade.cpp │ ├── Facade.hpp │ ├── Migration.hpp │ ├── Patches │ │ ├── EntitySpawnerPatch.cpp │ │ ├── EntitySpawnerPatch.hpp │ │ ├── WorldWidgetLimitPatch.cpp │ │ └── WorldWidgetLimitPatch.hpp │ ├── Shared │ │ ├── ResourcePathRegistry.cpp │ │ └── ResourcePathRegistry.hpp │ └── Utils │ │ ├── Num.hpp │ │ ├── Registers.asm │ │ ├── Registers.hpp │ │ └── Str.hpp ├── Red │ ├── Addresses │ │ └── Library.hpp │ ├── AnimatedComponent.hpp │ ├── AppearanceChanger.hpp │ ├── AppearanceResource.hpp │ ├── AttachmentSlots.hpp │ ├── Buffer.hpp │ ├── CharacterCustomization.hpp │ ├── Common.hpp │ ├── CommunitySystem.hpp │ ├── Entity.hpp │ ├── EntityBuilder.hpp │ ├── EntitySpawner.hpp │ ├── EntityTemplate.hpp │ ├── FactoryIndex.hpp │ ├── GameApplication.hpp │ ├── GameEngine.hpp │ ├── GarmentAssembler.hpp │ ├── ImpostorComponent.hpp │ ├── InkSpawner.hpp │ ├── ItemObject.hpp │ ├── JobHandle.hpp │ ├── JournalManager.hpp │ ├── JournalTree.hpp │ ├── Localization.hpp │ ├── MappinSystem.hpp │ ├── Mesh.hpp │ ├── MorphTarget.hpp │ ├── PersistencySystem.hpp │ ├── PhotoMode.hpp │ ├── QuestLoader.hpp │ ├── QuestPhase.hpp │ ├── QuestsSystem.hpp │ ├── ResourceDepot.hpp │ ├── ResourceLoader.hpp │ ├── ResourcePath.hpp │ ├── Serialization.hpp │ ├── SharedStorage.hpp │ ├── StreamingSector.hpp │ ├── StreamingWorld.hpp │ ├── TPPRepresentationComponent.hpp │ ├── TagList.hpp │ ├── Threads.hpp │ ├── TransactionSystem.hpp │ ├── TweakDB.hpp │ └── VisualTagsPreset.hpp ├── main.cpp ├── pch.cpp ├── pch.hpp └── rtti.cpp ├── support ├── hints │ └── ArchiveXL.toml ├── red4ext │ └── ArchiveXL.hpp └── templates │ ├── factory.csv.json │ ├── localization.json │ └── mod.archive.xl ├── tools └── dist │ ├── package-mod.ps1 │ └── steps │ ├── compose-bundle.ps1 │ ├── compose-hints.ps1 │ ├── compose-licenses.ps1 │ ├── compose-red4ext.ps1 │ ├── compose-redscripts.ps1 │ ├── create-zip-from-stage.ps1 │ ├── get-version.ps1 │ └── install-from-stage.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,xl}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.json] 17 | indent_style = space 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vs 3 | /.vscode 4 | /.xmake 5 | /bundle/packed 6 | /bundle/source/archive 7 | /bundle/*.xml 8 | /bundle/*.zip 9 | /bundle/custom_refs.txt 10 | /bundle/fileTreeState.json 11 | /build 12 | /dev 13 | /src/App/Project.hpp 14 | /src/App/Version.rc 15 | /tools/wkit/layout.xml 16 | __pycache__ 17 | CMakeLists.txt 18 | -------------------------------------------------------------------------------- /.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/nameof"] 11 | path = vendor/nameof 12 | url = https://github.com/Neargye/nameof.git 13 | -------------------------------------------------------------------------------- /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 | # ArchiveXL 2 | 3 | ArchiveXL is a modding tool that allows you to load custom resources without touching original game files, 4 | thus allowing multiple mods to expand same resources without conflicts. 5 | 6 | With the mod you can: 7 | 8 | - Load custom entity factories (necessary for item additions) 9 | - Add localization texts that can be used in scripts, resources and TweakDB 10 | - Edit existing localization texts without overwriting original resources 11 | - Override submeshes visibility of entity parts 12 | - Add visual tags to a clothing item 13 | - Spawn widgets from any library without registering dependencies 14 | 15 | ## Getting Started 16 | 17 | ### Compatibility 18 | 19 | - Cyberpunk 2077 2.21 20 | - [redscript](https://github.com/jac3km4/redscript) 0.5.27+ 21 | 22 | ### Installation 23 | 24 | 1. Install requirements: 25 | - [RED4ext](https://docs.red4ext.com/getting-started/installing-red4ext) 1.27.0+ 26 | 2. Extract the release archive `ArchiveXL-x.x.x.zip` into the Cyberpunk 2077 directory. 27 | 28 | ## Documentation 29 | 30 | - [Dynamic appearances](https://github.com/psiberx/cp2077-archive-xl/wiki#dynamic-appearances) 31 | - [Body types](https://github.com/psiberx/cp2077-archive-xl/wiki#body-types) 32 | - [Appearance suffixes](https://github.com/psiberx/cp2077-archive-xl/wiki#appearance-suffixes) 33 | - [Components overrides](https://github.com/psiberx/cp2077-archive-xl/wiki#components-overrides) 34 | - [Visual tags](https://github.com/psiberx/cp2077-archive-xl/wiki#visual-tags) 35 | - [Extending resources](https://github.com/psiberx/cp2077-archive-xl/wiki#extending-resources) 36 | -------------------------------------------------------------------------------- /bundle/ArchiveXL.cpmodproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | ArchiveXL 4 | ArchiveXL 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bundle/source/raw/archive_xl/characters/common/eyes/heb__01.mi.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WolvenKitVersion": "8.15.1-nightly.2024-12-22", 4 | "WKitJsonVersion": "0.0.9", 5 | "GameVersion": 2200, 6 | "ExportedDateTime": "2025-01-05T02:46:11.0092071Z", 7 | "DataType": "CR2W" 8 | }, 9 | "Data": { 10 | "Version": 195, 11 | "BuildVersion": 0, 12 | "RootChunk": { 13 | "$type": "CMaterialInstance", 14 | "audioTag": { 15 | "$type": "CName", 16 | "$storage": "string", 17 | "$value": "None" 18 | }, 19 | "baseMaterial": { 20 | "DepotPath": { 21 | "$type": "ResourcePath", 22 | "$storage": "string", 23 | "$value": "base\\materials\\mesh_decal_double_diffuse.mt" 24 | }, 25 | "Flags": "Default" 26 | }, 27 | "cookingPlatform": "PLATFORM_PC", 28 | "enableMask": 0, 29 | "metadata": null, 30 | "resourceVersion": 4, 31 | "values": [ 32 | { 33 | "$type": "rRef:ITexture", 34 | "SecondaryDiffuseAlpha": { 35 | "DepotPath": { 36 | "$type": "ResourcePath", 37 | "$storage": "string", 38 | "$value": "base\\characters\\common\\character_customisation_items\\eyebrows\\textures\\heb_wa__base_ds01.xbm" 39 | }, 40 | "Flags": "Default" 41 | } 42 | }, 43 | { 44 | "$type": "rRef:ITexture", 45 | "DiffuseTexture": { 46 | "DepotPath": { 47 | "$type": "ResourcePath", 48 | "$storage": "string", 49 | "$value": "base\\characters\\common\\character_customisation_items\\eyebrows\\textures\\heb__base_d01.xbm" 50 | }, 51 | "Flags": "Default" 52 | } 53 | }, 54 | { 55 | "$type": "rRef:ITexture", 56 | "NormalTexture": { 57 | "DepotPath": { 58 | "$type": "ResourcePath", 59 | "$storage": "string", 60 | "$value": "base\\characters\\common\\character_customisation_items\\eyebrows\\textures\\heb__base_n01.xbm" 61 | }, 62 | "Flags": "Default" 63 | } 64 | } 65 | ] 66 | }, 67 | "EmbeddedFiles": [] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /bundle/source/raw/archive_xl/characters/common/eyes/hel_pma.mi.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WolvenKitVersion": "8.15.1-nightly.2024-12-22", 4 | "WKitJsonVersion": "0.0.9", 5 | "GameVersion": 2200, 6 | "ExportedDateTime": "2025-01-05T02:46:11.0092071Z", 7 | "DataType": "CR2W" 8 | }, 9 | "Data": { 10 | "Version": 195, 11 | "BuildVersion": 0, 12 | "RootChunk": { 13 | "$type": "CMaterialInstance", 14 | "audioTag": { 15 | "$type": "CName", 16 | "$storage": "string", 17 | "$value": "None" 18 | }, 19 | "baseMaterial": { 20 | "DepotPath": { 21 | "$type": "ResourcePath", 22 | "$storage": "string", 23 | "$value": "base\\characters\\common\\character_customisation_items\\eyelashes\\source\\eyelashes__default.mi" 24 | }, 25 | "Flags": "Default" 26 | }, 27 | "cookingPlatform": "PLATFORM_PC", 28 | "enableMask": 0, 29 | "metadata": null, 30 | "resourceVersion": 4, 31 | "values": [ 32 | { 33 | "$type": "rRef:ITexture", 34 | "Strand_Alpha": { 35 | "DepotPath": { 36 | "$type": "ResourcePath", 37 | "$storage": "string", 38 | "$value": "base\\characters\\common\\eyes\\textures\\eyelashes_ma_01_lod1.xbm" 39 | }, 40 | "Flags": "Default" 41 | } 42 | } 43 | ] 44 | }, 45 | "EmbeddedFiles": [] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bundle/source/raw/archive_xl/characters/common/eyes/hel_pwa.mi.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WolvenKitVersion": "8.15.1-nightly.2024-12-22", 4 | "WKitJsonVersion": "0.0.9", 5 | "GameVersion": 2200, 6 | "ExportedDateTime": "2025-01-05T02:46:11.0092071Z", 7 | "DataType": "CR2W" 8 | }, 9 | "Data": { 10 | "Version": 195, 11 | "BuildVersion": 0, 12 | "RootChunk": { 13 | "$type": "CMaterialInstance", 14 | "audioTag": { 15 | "$type": "CName", 16 | "$storage": "string", 17 | "$value": "None" 18 | }, 19 | "baseMaterial": { 20 | "DepotPath": { 21 | "$type": "ResourcePath", 22 | "$storage": "string", 23 | "$value": "base\\characters\\common\\character_customisation_items\\eyelashes\\source\\eyelashes__default.mi" 24 | }, 25 | "Flags": "Default" 26 | }, 27 | "cookingPlatform": "PLATFORM_PC", 28 | "enableMask": 0, 29 | "metadata": null, 30 | "resourceVersion": 4, 31 | "values": [ 32 | { 33 | "$type": "rRef:ITexture", 34 | "Strand_Alpha": { 35 | "DepotPath": { 36 | "$type": "ResourcePath", 37 | "$storage": "string", 38 | "$value": "base\\characters\\common\\hair\\textures\\hb1_brow_beard_lashes\\hb1_01__lash_single_d.xbm" 39 | }, 40 | "Flags": "Default" 41 | } 42 | } 43 | ] 44 | }, 45 | "EmbeddedFiles": [] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bundle/source/raw/archive_xl/characters/common/hair/textures/hair_profiles/base_material.mi.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WolvenKitVersion": "8.15.1-nightly.2024-12-22", 4 | "WKitJsonVersion": "0.0.9", 5 | "GameVersion": 2200, 6 | "ExportedDateTime": "2025-01-05T02:46:11.0092071Z", 7 | "DataType": "CR2W" 8 | }, 9 | "Data": { 10 | "Version": 195, 11 | "BuildVersion": 0, 12 | "RootChunk": { 13 | "$type": "CMaterialInstance", 14 | "audioTag": { 15 | "$type": "CName", 16 | "$storage": "string", 17 | "$value": "None" 18 | }, 19 | "baseMaterial": { 20 | "DepotPath": { 21 | "$type": "ResourcePath", 22 | "$storage": "string", 23 | "$value": "" 24 | }, 25 | "Flags": "Default" 26 | }, 27 | "cookingPlatform": "PLATFORM_PC", 28 | "enableMask": 0, 29 | "metadata": null, 30 | "resourceVersion": 4, 31 | "values": [] 32 | }, 33 | "EmbeddedFiles": [] 34 | } 35 | } -------------------------------------------------------------------------------- /bundle/source/raw/archive_xl/characters/head/player_base_heads/player_female_average/heb_000_pwa__basehead_01.morphtarget.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WolvenKitVersion": "8.15.1-nightly.2024-12-22", 4 | "WKitJsonVersion": "0.0.9", 5 | "GameVersion": 2200, 6 | "ExportedDateTime": "2025-01-02T10:21:38.3952972Z", 7 | "DataType": "CR2W" 8 | }, 9 | "Data": { 10 | "Version": 195, 11 | "BuildVersion": 0, 12 | "RootChunk": { 13 | "$type": "MorphTargetMesh", 14 | "baseMesh": { 15 | "DepotPath": { 16 | "$type": "ResourcePath", 17 | "$storage": "string", 18 | "$value": "archive_xl\\characters\\head\\player_base_heads\\player_female_average\\h0_000_pwa_c__basehead\\heb_000_pwa__basehead_01.mesh" 19 | }, 20 | "Flags": "Default" 21 | }, 22 | "baseMeshAppearance": { 23 | "$type": "CName", 24 | "$storage": "string", 25 | "$value": "default" 26 | }, 27 | "baseTexture": { 28 | "DepotPath": { 29 | "$type": "ResourcePath", 30 | "$storage": "string", 31 | "$value": "engine\\textures\\editor\\normal.xbm" 32 | }, 33 | "Flags": "Default" 34 | }, 35 | "baseTextureParamName": { 36 | "$type": "CName", 37 | "$storage": "string", 38 | "$value": "Normal" 39 | }, 40 | "blob": null, 41 | "boundingBox": { 42 | "$type": "Box", 43 | "Max": { 44 | "$type": "Vector4", 45 | "W": 0, 46 | "X": 0, 47 | "Y": 0, 48 | "Z": 0 49 | }, 50 | "Min": { 51 | "$type": "Vector4", 52 | "W": 0, 53 | "X": 0, 54 | "Y": 0, 55 | "Z": 0 56 | } 57 | }, 58 | "cookingPlatform": "PLATFORM_PC", 59 | "resourceVersion": 2, 60 | "targets": [] 61 | }, 62 | "EmbeddedFiles": [] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /bundle/source/raw/archive_xl/characters/head/player_base_heads/player_man_average/heb_000_pma__basehead_01.morphtarget.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WolvenKitVersion": "8.15.1-nightly.2024-12-22", 4 | "WKitJsonVersion": "0.0.9", 5 | "GameVersion": 2200, 6 | "ExportedDateTime": "2025-01-04T09:05:47.8167670Z", 7 | "DataType": "CR2W" 8 | }, 9 | "Data": { 10 | "Version": 195, 11 | "BuildVersion": 0, 12 | "RootChunk": { 13 | "$type": "MorphTargetMesh", 14 | "baseMesh": { 15 | "DepotPath": { 16 | "$type": "ResourcePath", 17 | "$storage": "string", 18 | "$value": "archive_xl\\characters\\head\\player_base_heads\\player_man_average\\h0_000_pma_c__basehead\\heb_000_pma__basehead_01.mesh" 19 | }, 20 | "Flags": "Default" 21 | }, 22 | "baseMeshAppearance": { 23 | "$type": "CName", 24 | "$storage": "string", 25 | "$value": "default" 26 | }, 27 | "baseTexture": { 28 | "DepotPath": { 29 | "$type": "ResourcePath", 30 | "$storage": "string", 31 | "$value": "engine\\textures\\editor\\white.xbm" 32 | }, 33 | "Flags": "Default" 34 | }, 35 | "baseTextureParamName": { 36 | "$type": "CName", 37 | "$storage": "string", 38 | "$value": "NormalsBlendingModeAlpha" 39 | }, 40 | "blob": null, 41 | "boundingBox": { 42 | "$type": "Box", 43 | "Max": { 44 | "$type": "Vector4", 45 | "W": 0, 46 | "X": 0, 47 | "Y": 0, 48 | "Z": 0 49 | }, 50 | "Min": { 51 | "$type": "Vector4", 52 | "W": 0, 53 | "X": 0, 54 | "Y": 0, 55 | "Z": 0 56 | } 57 | }, 58 | "cookingPlatform": "PLATFORM_PC", 59 | "resourceVersion": 2, 60 | "targets": [] 61 | }, 62 | "EmbeddedFiles": [] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /bundle/source/raw/archive_xl/common/null.morphtarget.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WolvenKitVersion": "8.15.1-nightly.2024-12-22", 4 | "WKitJsonVersion": "0.0.9", 5 | "GameVersion": 2200, 6 | "ExportedDateTime": "2025-05-15T19:43:25.2967155Z", 7 | "DataType": "CR2W" 8 | }, 9 | "Data": { 10 | "Version": 195, 11 | "BuildVersion": 0, 12 | "RootChunk": { 13 | "$type": "MorphTargetMesh", 14 | "baseMesh": { 15 | "DepotPath": { 16 | "$type": "ResourcePath", 17 | "$storage": "uint64", 18 | "$value": "0" 19 | }, 20 | "Flags": "Default" 21 | }, 22 | "baseMeshAppearance": { 23 | "$type": "CName", 24 | "$storage": "string", 25 | "$value": "None" 26 | }, 27 | "baseTexture": { 28 | "DepotPath": { 29 | "$type": "ResourcePath", 30 | "$storage": "uint64", 31 | "$value": "0" 32 | }, 33 | "Flags": "Default" 34 | }, 35 | "baseTextureParamName": { 36 | "$type": "CName", 37 | "$storage": "string", 38 | "$value": "None" 39 | }, 40 | "blob": null, 41 | "boundingBox": { 42 | "$type": "Box", 43 | "Max": { 44 | "$type": "Vector4", 45 | "W": 0, 46 | "X": 0, 47 | "Y": 0, 48 | "Z": 0 49 | }, 50 | "Min": { 51 | "$type": "Vector4", 52 | "W": 0, 53 | "X": 0, 54 | "Y": 0, 55 | "Z": 0 56 | } 57 | }, 58 | "cookingPlatform": "PLATFORM_None", 59 | "resourceVersion": 0, 60 | "targets": [] 61 | }, 62 | "EmbeddedFiles": [] 63 | } 64 | } -------------------------------------------------------------------------------- /bundle/source/resources/PhotoModeScope.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | scope: 3 | photomode_wa.ent: 4 | - base\characters\entities\player\photo_mode\player_wa_photomode.ent 5 | - base\characters\entities\player\photo_mode\alt_cunningham\alt_photomode.ent 6 | - base\characters\entities\player\photo_mode\blue_moon\bmuc_photomode.ent 7 | - base\characters\entities\player\photo_mode\evelyn_parker\evelyn_photomode.ent 8 | - base\characters\entities\player\photo_mode\hanako_arasaka\hanako_photomode.ent 9 | - base\characters\entities\player\photo_mode\judy_alvarez\judy_photomode.ent 10 | - base\characters\entities\player\photo_mode\lizzy_wizzy\lizzy_photomode.ent 11 | - base\characters\entities\player\photo_mode\meredith_stout\meredith_photomode.ent 12 | - base\characters\entities\player\photo_mode\panam_palmer\panam_photomode.ent 13 | - base\characters\entities\player\photo_mode\purple_force\pfuc_photomode.ent 14 | - base\characters\entities\player\photo_mode\red_menace\rmuc_photomode.ent 15 | - base\characters\entities\player\photo_mode\rogue_amendiares\old_rogue_photomode.ent 16 | - base\characters\entities\player\photo_mode\rogue_amendiares\young_rogue_photomode.ent 17 | - ep1\characters\entities\player\photo_mode\myers\myers_photomode.ent 18 | - ep1\characters\entities\player\photo_mode\player_wa_photomode_ep1.ent 19 | - ep1\characters\entities\player\photo_mode\songbird\songbird_photomode.ent 20 | photomode_ma.ent: 21 | - base\characters\entities\player\photo_mode\player_ma_photomode.ent 22 | - base\characters\entities\player\photo_mode\johnny_photomode.ent 23 | - base\characters\entities\player\photo_mode\altjohnny_silverhand\altjohnny_photomode.ent 24 | - base\characters\entities\player\photo_mode\goro_takemura\goro_photomode.ent 25 | - base\characters\entities\player\photo_mode\johnny_silverhand\johnny_photomode_entity.ent 26 | - base\characters\entities\player\photo_mode\kerry_eurodyne\kerry_photomode.ent 27 | - base\characters\entities\player\photo_mode\viktor_vektor\viktor_photomode.ent 28 | - ep1\characters\entities\player\photo_mode\player_ma_photomode_ep1.ent 29 | photomode_mb.ent: 30 | - base\characters\entities\player\photo_mode\jackie_welles\jackie_photomode.ent 31 | - base\characters\entities\player\photo_mode\river_ward\river_photomode.ent 32 | - ep1\characters\entities\player\photo_mode\kurt\kurt_photomode.ent 33 | - ep1\characters\entities\player\photo_mode\solomon_reed\reed_photomode.ent 34 | photomode_mm.ent: 35 | - base\characters\entities\player\photo_mode\adam_smasher\adam_smasher.ent 36 | photomode_cat.ent: 37 | - base\quest\minor_quests\mq000\characters\nibbles.ent 38 | -------------------------------------------------------------------------------- /bundle/source/resources/PlayerBaseScope.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | scope: 3 | player.ent: 4 | - player_ma.ent 5 | - player_wa.ent 6 | player_ma.ent: 7 | - base\characters\entities\player\photo_mode\player_ma_photomode.ent 8 | - base\characters\entities\player\player_ma_fpp.ent 9 | - base\characters\entities\player\player_ma_tpp.ent 10 | - base\characters\entities\player\player_ma_tpp_cutscene.ent 11 | - base\characters\entities\player\player_ma_tpp_cutscene_no_impostor.ent 12 | - base\characters\entities\player\player_ma_tpp_reflexion.ent 13 | - ep1\characters\entities\player\photo_mode\player_ma_photomode_ep1.ent 14 | - ep1\characters\entities\player\player_ma_fpp_ep1.ent 15 | - ep1\characters\entities\player\player_ma_tpp_ep1.ent 16 | player_wa.ent: 17 | - base\characters\entities\player\photo_mode\player_wa_photomode.ent 18 | - base\characters\entities\player\player_wa_fpp.ent 19 | - base\characters\entities\player\player_wa_tpp.ent 20 | - base\characters\entities\player\player_wa_tpp_cutscene.ent 21 | - base\characters\entities\player\player_wa_tpp_cutscene_no_impostor.ent 22 | - base\characters\entities\player\player_wa_tpp_reflexion.ent 23 | - ep1\characters\entities\player\photo_mode\player_wa_photomode_ep1.ent 24 | - ep1\characters\entities\player\player_wa_fpp_ep1.ent 25 | - ep1\characters\entities\player\player_wa_tpp_ep1.ent 26 | -------------------------------------------------------------------------------- /bundle/source/resources/PlayerCustomizationBrowsPatch.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | copy: 3 | base\characters\head\player_base_heads\player_female_average\heb_000_pwa__morphs.morphtarget: 4 | - archive_xl\characters\head\player_base_heads\player_female_average\heb_000_pwa__morphs.morphtarget 5 | base\characters\head\player_base_heads\player_female_average\h0_000_pwa_c__basehead\heb_000_pwa_c__basehead.mesh: 6 | - archive_xl\characters\head\player_base_heads\player_female_average\h0_000_pwa_c__basehead\heb_000_pwa_c__basehead.mesh 7 | base\characters\head\player_base_heads\player_man_average\heb_000_pma__morphs.morphtarget: 8 | - archive_xl\characters\head\player_base_heads\player_man_average\heb_000_pma__morphs.morphtarget 9 | base\characters\head\player_base_heads\player_man_average\h0_000_pma_c__basehead\heb_000_pma_c__basehead.mesh: 10 | - archive_xl\characters\head\player_base_heads\player_man_average\h0_000_pma_c__basehead\heb_000_pma_c__basehead.mesh 11 | patch: 12 | archive_xl\characters\head\player_base_heads\player_female_average\heb_000_pwa__morphs.morphtarget: 13 | props: [ blob, boundingBox, targets ] 14 | targets: [ player_wa_base_brows.morphtarget ] 15 | archive_xl\characters\head\player_base_heads\player_female_average\h0_000_pwa_c__basehead\heb_000_pwa_c__basehead.mesh: 16 | props: [ renderResourceBlob ] 17 | targets: [ player_wa_base_brows.mesh ] 18 | archive_xl\characters\head\player_base_heads\player_man_average\heb_000_pma__morphs.morphtarget: 19 | props: [ blob, boundingBox, targets ] 20 | targets: [ player_ma_base_brows.morphtarget ] 21 | archive_xl\characters\head\player_base_heads\player_man_average\h0_000_pma_c__basehead\heb_000_pma_c__basehead.mesh: 22 | props: [ renderResourceBlob ] 23 | targets: [ player_ma_base_brows.mesh ] 24 | -------------------------------------------------------------------------------- /bundle/source/resources/PlayerCustomizationEyesPatch.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | copy: 3 | base\characters\head\player_base_heads\player_female_average\he_000_pwa__morphs.morphtarget: 4 | - archive_xl\characters\head\player_base_heads\player_female_average\he_000_pwa__morphs_normal_fix.morphtarget 5 | base\characters\head\player_base_heads\player_man_average\he_000_pma__morphs.morphtarget: 6 | - archive_xl\characters\head\player_base_heads\player_man_average\he_000_pma__morphs_normal_fix.morphtarget 7 | patch: 8 | archive_xl\characters\head\player_base_heads\player_female_average\h0_000_pwa_c__basehead\he_000_pwa_c__basehead_patch.mesh: 9 | props: [ appearances ] 10 | targets: [ player_wa_base_eyes.mesh ] 11 | archive_xl\characters\head\player_base_heads\player_man_average\h0_000_pma_c__basehead\he_000_pma_c__basehead_patch.mesh: 12 | props: [ appearances ] 13 | targets: [ player_ma_base_eyes.mesh ] 14 | archive_xl\common\null.morphtarget: 15 | props: [ baseTexture, baseTextureParamName ] 16 | targets: 17 | - archive_xl\characters\head\player_base_heads\player_female_average\he_000_pwa__morphs_normal_fix.morphtarget 18 | - archive_xl\characters\head\player_base_heads\player_man_average\he_000_pma__morphs_normal_fix.morphtarget 19 | -------------------------------------------------------------------------------- /bundle/source/resources/PlayerCustomizationEyesScope.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | scope: 3 | player_customization.app: 4 | - player_ma_eyes.app 5 | - player_wa_eyes.app 6 | player_ma_eyes.app: 7 | - archive_xl\characters\head\player_base_heads\appearances\head\he_000_pma__basehead.app 8 | player_ma_eyes.morphtarget: 9 | - player_ma_base_eyes.morphtarget 10 | - archive_xl\characters\head\player_base_heads\player_man_average\he_000_pma__morphs_normal_fix.morphtarget 11 | player_ma_base_eyes.morphtarget: 12 | - base\characters\head\player_base_heads\player_man_average\he_000_pma__morphs.morphtarget 13 | player_ma_eyes.mesh: 14 | - player_ma_base_eyes.mesh 15 | player_ma_base_eyes.mesh: 16 | - base\characters\head\player_base_heads\player_man_average\h0_000_pma_c__basehead\he_000_pma_c__basehead.mesh 17 | player_wa_eyes.app: 18 | - archive_xl\characters\head\player_base_heads\appearances\head\he_000_pwa__basehead.app 19 | player_wa_eyes.morphtarget: 20 | - player_wa_base_eyes.morphtarget 21 | - archive_xl\characters\head\player_base_heads\player_female_average\he_000_pwa__morphs_normal_fix.morphtarget 22 | player_wa_base_eyes.morphtarget: 23 | - base\characters\head\player_base_heads\player_female_average\he_000_pwa__morphs.morphtarget 24 | player_wa_eyes.mesh: 25 | - player_wa_base_eyes.mesh 26 | player_wa_base_eyes.mesh: 27 | - base\characters\head\player_base_heads\player_female_average\h0_000_pwa_c__basehead\he_000_pwa_c__basehead.mesh 28 | -------------------------------------------------------------------------------- /bundle/source/resources/PlayerCustomizationHairPatch.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | patch: 3 | archive_xl\characters\common\hair\h1_base_color_patch.mesh: 4 | props: [ appearances ] 5 | targets: [ player_ma_hair.mesh, player_wa_hair.mesh ] 6 | -------------------------------------------------------------------------------- /bundle/source/resources/PlayerCustomizationLashesPatch.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | patch: 3 | archive_xl\characters\head\player_base_heads\player_female_average\h0_000_pwa_c__basehead\hel_000_pwa_c__basehead_patch.mesh: 4 | props: [ appearances ] 5 | targets: [ player_wa_base_lashes.mesh ] 6 | archive_xl\characters\head\player_base_heads\player_man_average\h0_000_pma_c__basehead\hel_000_pma_c__basehead_patch.mesh: 7 | props: [ appearances ] 8 | targets: [ player_ma_base_lashes.mesh ] 9 | -------------------------------------------------------------------------------- /bundle/source/resources/PlayerCustomizationLashesScope.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | scope: 3 | player_customization.app: 4 | - player_ma_lashes.app 5 | - player_wa_lashes.app 6 | player_ma_lashes.app: 7 | - archive_xl\characters\head\player_base_heads\appearances\head\hel_000_pma__basehead.app 8 | player_ma_lashes.morphtarget: 9 | - player_ma_base_lashes.morphtarget 10 | player_ma_base_lashes.morphtarget: 11 | - base\characters\head\player_base_heads\player_man_average\he_000_pma__morphs.morphtarget 12 | player_ma_lashes.mesh: 13 | - player_ma_base_lashes.mesh 14 | player_ma_base_lashes.mesh: 15 | - base\characters\head\player_base_heads\player_man_average\h0_000_pma_c__basehead\he_000_pma_c__basehead.mesh 16 | player_wa_lashes.app: 17 | - archive_xl\characters\head\player_base_heads\appearances\head\hel_000_pwa__basehead.app 18 | player_wa_lashes.morphtarget: 19 | - player_wa_base_lashes.morphtarget 20 | player_wa_base_lashes.morphtarget: 21 | - base\characters\head\player_base_heads\player_female_average\he_000_pwa__morphs.morphtarget 22 | player_wa_lashes.mesh: 23 | - player_wa_base_lashes.mesh 24 | player_wa_base_lashes.mesh: 25 | - base\characters\head\player_base_heads\player_female_average\h0_000_pwa_c__basehead\he_000_pwa_c__basehead.mesh 26 | -------------------------------------------------------------------------------- /bundle/source/resources/PlayerCustomizationScope.xl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-archive-xl/30d36f116cb1b6d246b56cbb002696e56f9d8f3d/bundle/source/resources/PlayerCustomizationScope.xl -------------------------------------------------------------------------------- /bundle/source/resources/QuestBaseScope.xl: -------------------------------------------------------------------------------- 1 | resource: 2 | scope: 3 | cyberpunk2077.quest: 4 | - cyberpunk2077_main.quest 5 | - cyberpunk2077_ep1.quest 6 | cyberpunk2077_main.quest: 7 | - base\quest\cyberpunk2077.quest 8 | cyberpunk2077_ep1.quest: 9 | - ep1\quest\ep1_standalone.quest 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 || !RED4ext::Detail::HasStaticAllocator) && 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 || !RED4ext::Detail::HasStaticAllocator) && 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/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.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/DynamicAppearance.reds: -------------------------------------------------------------------------------- 1 | module ArchiveXL.DynamicAppearance 2 | 3 | public func OverrideDynamicAppearanceCondition(app: String, attr: String, value: String) -> String { 4 | if !StrContains(app, "!") { 5 | return app; 6 | } 7 | 8 | attr += "="; 9 | 10 | let base: String; 11 | let dynamic: String; 12 | StrSplitFirst(app, "!", base, dynamic); 13 | 14 | let variant: String; 15 | let hash: String; 16 | StrSplitFirst(dynamic, "%", variant, hash); 17 | 18 | let parts = StrSplit(variant, "+", false); 19 | let n = ArraySize(parts); 20 | let i = 0; 21 | while i < n { 22 | if StrFindFirst(parts[i], attr) == 0 { 23 | parts[i] = attr + value; 24 | break; 25 | } 26 | i += 1; 27 | } 28 | if i >= n { 29 | ArrayPush(parts, attr + value); 30 | n += 1; 31 | } 32 | 33 | dynamic = parts[0]; 34 | let i = 1; 35 | while i < n { 36 | dynamic += "+" + parts[i]; 37 | i += 1; 38 | } 39 | 40 | if StrLen(hash) > 0 { 41 | dynamic += "%" + hash; 42 | } 43 | 44 | return base + "!" + dynamic; 45 | } 46 | 47 | public func ConvertAppearanceNameToTPP(app: String) -> String { 48 | if !StrContains(app, "!") { 49 | return StrReplace(app, "&FPP", "&TPP"); 50 | } 51 | 52 | return OverrideDynamicAppearanceCondition(app, "camera", "tpp"); 53 | } 54 | 55 | public func ConvertAppearanceNameToFPP(app: String) -> String { 56 | if !StrContains(app, "!") { 57 | return StrReplace(app, "&TPP", "&FPP"); 58 | } 59 | 60 | return OverrideDynamicAppearanceCondition(app, "camera", "fpp"); 61 | } 62 | 63 | public func ConvertAppearanceNameToPartialSleeves(app: String) -> String { 64 | if !StrContains(app, "!") { 65 | return StrReplace(app, "&Full", "&Part"); 66 | } 67 | 68 | return OverrideDynamicAppearanceCondition(app, "sleeves", "part"); 69 | } 70 | 71 | public func ConvertAppearanceNameToFullSleeves(app: String) -> String { 72 | if !StrContains(app, "!") { 73 | return StrReplace(app, "&Part", "&Full"); 74 | } 75 | 76 | return OverrideDynamicAppearanceCondition(app, "sleeves", "full"); 77 | } 78 | -------------------------------------------------------------------------------- /scripts/Facade.reds: -------------------------------------------------------------------------------- 1 | public abstract native class ArchiveXL { 2 | public static native func GetBodyType(puppet: wref) -> CName 3 | public static native func EnableGarmentOffsets() 4 | public static native func DisableGarmentOffsets() 5 | public static native func Require(version: String) -> Bool 6 | public static native func Version() -> String 7 | } 8 | -------------------------------------------------------------------------------- /scripts/Module.reds: -------------------------------------------------------------------------------- 1 | module ArchiveXL 2 | -------------------------------------------------------------------------------- /src/App/Application.cpp: -------------------------------------------------------------------------------- 1 | #include "Application.hpp" 2 | #include "App/Archives/ArchiveService.hpp" 3 | #include "App/Environment.hpp" 4 | #include "App/Extensions/ExtensionService.hpp" 5 | #include "App/Migration.hpp" 6 | #include "App/Patches/EntitySpawnerPatch.hpp" 7 | #include "App/Patches/WorldWidgetLimitPatch.hpp" 8 | #include "App/Project.hpp" 9 | #include "App/Shared/ResourcePathRegistry.hpp" 10 | #include "Core/Foundation/RuntimeProvider.hpp" 11 | #include "Support/MinHook/MinHookProvider.hpp" 12 | #include "Support/RED4ext/RED4extProvider.hpp" 13 | #include "Support/RedLib/RedLibProvider.hpp" 14 | #include "Support/Spdlog/SpdlogProvider.hpp" 15 | 16 | App::Application::Application(HMODULE aHandle, const RED4ext::Sdk* aSdk) 17 | { 18 | Register(aHandle) 19 | ->SetBaseImagePathDepth(2); 20 | 21 | Register(); 22 | Register() 23 | ->AppendTimestampToLogName() 24 | ->CreateRecentLogSymlink(); 25 | Register(aHandle, aSdk) 26 | ->EnableAddressLibrary() 27 | ->RegisterScripts(Env::ScriptsDir()); 28 | Register(); 29 | 30 | Register(); 31 | Register(Env::GameDir(), Env::BundleDir()); 32 | Register(Env::BundleDir()); 33 | Register(); 34 | Register(); 35 | } 36 | 37 | void App::Application::OnStarting() 38 | { 39 | LogInfo("{} {} is starting...", Project::Name, Project::Version.to_string()); 40 | 41 | Migration::CleanUp(Env::LegacyBundleDir()); 42 | Migration::CleanUp(Env::LegacyScriptsDir()); 43 | } 44 | -------------------------------------------------------------------------------- /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/Archives/ArchiveService.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/ResourceDepot.hpp" 7 | 8 | namespace App 9 | { 10 | class ArchiveService 11 | : public Core::Feature 12 | , public Core::HookingAgent 13 | , public Core::LoggingAgent 14 | { 15 | public: 16 | explicit ArchiveService(std::filesystem::path aGameDir, std::filesystem::path aBundleDir = {}); 17 | 18 | bool RegisterArchive(std::filesystem::path aPath); 19 | bool RegisterDirectory(std::filesystem::path aPath); 20 | 21 | protected: 22 | void OnBootstrap() override; 23 | void OnShutdown() override; 24 | 25 | void OnInitializeArchives(Red::ResourceDepot* aDepot); 26 | static Red::ArchiveGroup& ResolveArchiveGroup(Red::ResourceDepot* aDepot, const Red::CString& aBasePath); 27 | 28 | Core::Vector m_archives; 29 | Core::Vector m_dirs; 30 | std::filesystem::path m_bundleDir; 31 | std::filesystem::path m_gameDir; 32 | bool m_loaded; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/App/Environment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Facades/Runtime.hpp" 4 | 5 | namespace App::Env 6 | { 7 | inline std::filesystem::path GameDir() 8 | { 9 | return Core::Runtime::GetRootDir(); 10 | } 11 | 12 | inline std::filesystem::path BundleDir() 13 | { 14 | return Core::Runtime::GetModuleDir() / L"Bundle"; 15 | } 16 | 17 | inline std::filesystem::path ScriptsDir() 18 | { 19 | return Core::Runtime::GetModuleDir() / L"Scripts"; 20 | } 21 | 22 | inline std::filesystem::path LegacyBundleDir() 23 | { 24 | return Core::Runtime::GetModuleDir() / L"Archive"; 25 | } 26 | 27 | inline std::filesystem::path LegacyScriptsDir() 28 | { 29 | return Core::Runtime::GetRootDir() / L"r6" / L"scripts" / L"ArchiveXL"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/App/Extensions/Animation/Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.hpp" 2 | #include "App/Utils/Num.hpp" 3 | 4 | namespace 5 | { 6 | constexpr auto AnimationsNodeKey = "animations"; 7 | constexpr auto EntityNodeKey = "entity"; 8 | constexpr auto AnimSetNodeKey = "set"; 9 | constexpr auto VariablesNodeKey = "vars"; 10 | constexpr auto PriorityNodeKey = "priority"; 11 | constexpr auto ComponentNodeKey = "component"; 12 | } 13 | 14 | bool App::AnimationsConfig::IsDefined() 15 | { 16 | return !animations.empty(); 17 | } 18 | 19 | void App::AnimationsConfig::LoadYAML(const YAML::Node& aNode) 20 | { 21 | const auto& animationsNode = aNode[AnimationsNodeKey]; 22 | 23 | if (!animationsNode.IsDefined()) 24 | return; 25 | 26 | if (!animationsNode.IsSequence()) 27 | { 28 | issues.emplace_back("Bad format. Expected list of anim entries."); 29 | return; 30 | } 31 | 32 | bool malformed = false; 33 | 34 | for (const auto& entryNode : animationsNode) 35 | { 36 | const auto& entityNode = entryNode[EntityNodeKey]; 37 | 38 | if (!entityNode.IsDefined()) 39 | { 40 | malformed = true; 41 | continue; 42 | } 43 | 44 | AnimationEntry entry; 45 | 46 | if (entityNode.IsScalar()) 47 | { 48 | entry.entities.insert(entityNode.Scalar()); 49 | } 50 | else if (entityNode.IsScalar()) 51 | { 52 | for (const auto& pathNode : entityNode) 53 | { 54 | if (pathNode.IsScalar()) 55 | { 56 | entry.entities.insert(pathNode.Scalar()); 57 | } 58 | } 59 | } 60 | 61 | if (entry.entities.empty()) 62 | { 63 | malformed = true; 64 | continue; 65 | } 66 | 67 | const auto& animSetNode = entryNode[AnimSetNodeKey]; 68 | 69 | if (!animSetNode.IsDefined() || !animSetNode.IsScalar()) 70 | { 71 | malformed = true; 72 | continue; 73 | } 74 | 75 | entry.set = animSetNode.Scalar(); 76 | 77 | const auto& variablesNode = entryNode[VariablesNodeKey]; 78 | if (variablesNode.IsDefined() && variablesNode.IsSequence()) 79 | { 80 | for (const auto& variableNode : variablesNode) 81 | { 82 | if (variablesNode.IsScalar()) 83 | { 84 | entry.variables.push_back(variableNode.Scalar()); 85 | } 86 | } 87 | } 88 | 89 | const auto& priorityNode = entryNode[PriorityNodeKey]; 90 | if (priorityNode.IsDefined() && priorityNode.IsScalar()) 91 | { 92 | ParseInt(priorityNode.Scalar(), entry.priority); 93 | } 94 | 95 | const auto& componentNode = entryNode[ComponentNodeKey]; 96 | if (componentNode.IsDefined() && componentNode.IsScalar()) 97 | { 98 | entry.component = componentNode.Scalar(); 99 | } 100 | 101 | animations.emplace_back(std::move(entry)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/App/Extensions/Animation/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct AnimationEntry 8 | { 9 | Core::Set entities; 10 | std::string component = "root"; 11 | std::string set; 12 | uint8_t priority = 128; 13 | Core::Vector variables; 14 | }; 15 | 16 | struct AnimationsConfig : ExtensionConfig 17 | { 18 | using ExtensionConfig::ExtensionConfig; 19 | 20 | bool IsDefined() override; 21 | void LoadYAML(const YAML::Node& aNode) override; 22 | 23 | Core::Vector animations; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/App/Extensions/Animation/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/Animation/Config.hpp" 5 | #include "App/Shared/ResourcePathRegistry.hpp" 6 | 7 | namespace App 8 | { 9 | class AnimationExtension : public ConfigurableExtensionImpl 10 | { 11 | public: 12 | std::string_view GetName() override; 13 | bool Load() override; 14 | bool Unload() override; 15 | void Configure() override; 16 | 17 | private: 18 | static void OnInitializeAnimations(Red::entAnimatedComponent* aComponent); 19 | 20 | inline static Core::Map> s_animsByTarget; 21 | inline static Core::SharedPtr s_resourcePathRegistry; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/App/Extensions/Customization/Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.hpp" 2 | 3 | namespace 4 | { 5 | constexpr auto CustomizationNodeKey = "customizations"; 6 | constexpr auto MaleNodeKey = "male"; 7 | constexpr auto FemaleNodeKey = "female"; 8 | } 9 | 10 | bool App::CustomizationConfig::IsDefined() 11 | { 12 | return !maleOptions.empty() || !femaleOptions.empty(); 13 | } 14 | 15 | void App::CustomizationConfig::LoadYAML(const YAML::Node& aNode) 16 | { 17 | const auto& rootNode = aNode[CustomizationNodeKey]; 18 | 19 | if (!rootNode.IsDefined()) 20 | return; 21 | 22 | bool malformed = false; 23 | 24 | if (!ReadOptions(rootNode[MaleNodeKey], maleOptions)) 25 | malformed = true; 26 | 27 | if (!ReadOptions(rootNode[FemaleNodeKey], femaleOptions)) 28 | malformed = true; 29 | 30 | if (malformed) 31 | issues.emplace_back("Bad format. Expected resource path or list of paths."); 32 | } 33 | 34 | bool App::CustomizationConfig::ReadOptions(const YAML::Node& aNode, Core::Vector& aOptions) 35 | { 36 | bool malformed = false; 37 | 38 | if (aNode.IsDefined()) 39 | { 40 | switch (aNode.Type()) 41 | { 42 | case YAML::NodeType::Sequence: 43 | { 44 | for (const auto& pathNode : aNode) 45 | { 46 | if (pathNode.IsScalar()) 47 | aOptions.emplace_back(pathNode.Scalar()); 48 | else 49 | malformed = true; 50 | } 51 | break; 52 | } 53 | case YAML::NodeType::Scalar: 54 | { 55 | aOptions.emplace_back(aNode.Scalar()); 56 | break; 57 | } 58 | default: 59 | { 60 | malformed = true; 61 | } 62 | } 63 | } 64 | 65 | return !malformed; 66 | } 67 | -------------------------------------------------------------------------------- /src/App/Extensions/Customization/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct CustomizationConfig : ExtensionConfig 8 | { 9 | using ExtensionConfig::ExtensionConfig; 10 | 11 | bool IsDefined() override; 12 | void LoadYAML(const YAML::Node& aNode) override; 13 | 14 | Core::Vector maleOptions; 15 | Core::Vector femaleOptions; 16 | 17 | private: 18 | bool ReadOptions(const YAML::Node& aNode, Core::Vector& aOptions); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/App/Extensions/ExtensionBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Hooking/HookingAgent.hpp" 4 | #include "Core/Logging/LoggingAgent.hpp" 5 | 6 | namespace App 7 | { 8 | class Extension 9 | : public Core::HookingAgent 10 | , public Core::LoggingAgent 11 | { 12 | public: 13 | Extension() = default; 14 | virtual ~Extension() = default; 15 | 16 | virtual std::string_view GetName() = 0; 17 | virtual bool Load() { return true; } 18 | virtual bool Unload() { return true; } 19 | virtual void OnDepotReady() {} 20 | virtual void OnTweakDBReady() {} 21 | }; 22 | 23 | class ConfigurableExtension : public Extension 24 | { 25 | public: 26 | virtual bool AddConfig(const std::string& aConfigName, const YAML::Node& aConfigNode) = 0; 27 | virtual void ResetConfigs() {} 28 | virtual void Configure() {} 29 | virtual void Reload() {} 30 | }; 31 | 32 | struct ExtensionConfig 33 | { 34 | virtual bool IsDefined() = 0; 35 | virtual void LoadYAML(const YAML::Node& aNode) = 0; 36 | 37 | [[nodiscard]] bool HasIssues() const 38 | { 39 | return !issues.empty(); 40 | } 41 | 42 | std::string name; 43 | Core::Vector issues; 44 | }; 45 | 46 | template 47 | requires std::is_base_of_v 48 | class ConfigurableExtensionImpl : public ConfigurableExtension 49 | { 50 | public: 51 | bool AddConfig(const std::string& aName, const YAML::Node& aNode) override 52 | { 53 | C config; 54 | config.name = aName; 55 | config.LoadYAML(aNode); 56 | 57 | if (config.HasIssues()) 58 | { 59 | for (const auto& issue : config.issues) 60 | { 61 | LogError(issue.data()); 62 | } 63 | } 64 | 65 | if (config.IsDefined()) 66 | { 67 | m_configs.emplace_back(std::move(config)); 68 | } 69 | 70 | return !config.HasIssues(); 71 | } 72 | 73 | void ResetConfigs() override 74 | { 75 | m_configs.clear(); 76 | } 77 | 78 | protected: 79 | Core::Vector m_configs; 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/App/Extensions/ExtensionLoader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ExtensionBase.hpp" 4 | #include "Core/Logging/LoggingAgent.hpp" 5 | 6 | namespace App 7 | { 8 | class ExtensionLoader : public Core::LoggingAgent 9 | { 10 | public: 11 | ExtensionLoader(std::filesystem::path aBundleDir, std::wstring aConfigExt); 12 | 13 | template 14 | requires std::is_base_of_v 15 | void Add(Args&&... aArgs) 16 | { 17 | auto module = Core::MakeShared(std::forward(aArgs)...); 18 | 19 | if constexpr (std::is_base_of_v) 20 | m_configurables.emplace_back(module); 21 | 22 | m_modules.emplace_back(module); 23 | } 24 | 25 | void Configure(); 26 | void Load(); 27 | void OnDepotReady(); 28 | void OnTweakDBReady(); 29 | void Unload(); 30 | void Reload(); 31 | 32 | private: 33 | bool AddConfig(const std::filesystem::path& aPath, const std::filesystem::path& aDir, bool aSilent = false); 34 | 35 | Core::Vector> m_modules; 36 | Core::Vector> m_configurables; 37 | std::filesystem::path m_bundleDir; 38 | std::wstring m_customConfigExt; 39 | bool m_loaded; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/App/Extensions/ExtensionService.cpp: -------------------------------------------------------------------------------- 1 | #include "ExtensionService.hpp" 2 | #include "App/Extensions/Animation/Extension.hpp" 3 | #include "App/Extensions/Attachment/Extension.hpp" 4 | #include "App/Extensions/Customization/Extension.hpp" 5 | #include "App/Extensions/FactoryIndex/Extension.hpp" 6 | #include "App/Extensions/Garment/Extension.hpp" 7 | #include "App/Extensions/InkSpawner/Extension.hpp" 8 | #include "App/Extensions/Journal/Extension.hpp" 9 | #include "App/Extensions/Localization/Extension.hpp" 10 | #include "App/Extensions/Mesh/Extension.hpp" 11 | #include "App/Extensions/PuppetState/Extension.hpp" 12 | #include "App/Extensions/QuestPhase/Extension.hpp" 13 | #include "App/Extensions/ResourceLink/Extension.hpp" 14 | #include "App/Extensions/ResourceMeta/Extension.hpp" 15 | #include "App/Extensions/ResourcePatch/Extension.hpp" 16 | #include "App/Extensions/Transmog/Extension.hpp" 17 | #include "App/Extensions/WorldStreaming/Extension.hpp" 18 | #include "Red/GameApplication.hpp" 19 | #include "Red/GameEngine.hpp" 20 | #include "Red/ResourceLoader.hpp" 21 | #include "Red/TweakDB.hpp" 22 | 23 | App::ExtensionService::ExtensionService(std::filesystem::path aBundleDir) 24 | : m_bundleDir(std::move(aBundleDir)) 25 | { 26 | } 27 | 28 | void App::ExtensionService::OnBootstrap() 29 | { 30 | m_loader = Core::MakeUnique(m_bundleDir, L".xl"); 31 | 32 | m_loader->Add(); 33 | m_loader->Add(); 34 | m_loader->Add(); 35 | m_loader->Add(); 36 | m_loader->Add(); 37 | m_loader->Add(); 38 | m_loader->Add(); 39 | m_loader->Add(); 40 | m_loader->Add(); 41 | m_loader->Add(); 42 | m_loader->Add(); 43 | m_loader->Add(); 44 | m_loader->Add(); 45 | m_loader->Add(); 46 | m_loader->Add(); 47 | m_loader->Add(); 48 | 49 | HookOnceAfter([&]() { 50 | m_loader->Configure(); 51 | m_loader->Load(); 52 | }); 53 | 54 | HookOnceAfter([&]() { 55 | m_loader->OnDepotReady(); 56 | }); 57 | 58 | HookAfter([&]() { 59 | m_loader->OnTweakDBReady(); 60 | }); 61 | } 62 | 63 | void App::ExtensionService::OnShutdown() 64 | { 65 | m_loader->Unload(); 66 | m_loader = nullptr; 67 | } 68 | 69 | void App::ExtensionService::Configure() 70 | { 71 | std::unique_lock _(m_reloadMutex); 72 | 73 | if (!IsHooked()) 74 | { 75 | HookAfter([&]() { 76 | std::unique_lock _(m_reloadMutex); 77 | 78 | m_loader->Configure(); 79 | m_loader->Reload(); 80 | 81 | Unhook(); 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/App/Extensions/ExtensionService.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionLoader.hpp" 4 | #include "Core/Foundation/Feature.hpp" 5 | #include "Core/Hooking/HookingAgent.hpp" 6 | #include "Core/Logging/LoggingAgent.hpp" 7 | 8 | namespace App 9 | { 10 | class ExtensionService 11 | : public Core::Feature 12 | , public Core::HookingAgent 13 | , public Core::LoggingAgent 14 | { 15 | public: 16 | explicit ExtensionService(std::filesystem::path aBundleDir = {}); 17 | 18 | void Configure(); 19 | 20 | protected: 21 | void OnBootstrap() override; 22 | void OnShutdown() override; 23 | 24 | Core::UniquePtr m_loader; 25 | std::filesystem::path m_bundleDir; 26 | std::mutex m_reloadMutex; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/App/Extensions/FactoryIndex/Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.hpp" 2 | 3 | namespace 4 | { 5 | constexpr auto FactoryIndexNodeKey = "factories"; 6 | } 7 | 8 | bool App::FactoryIndexConfig::IsDefined() 9 | { 10 | return !factories.empty(); 11 | } 12 | 13 | void App::FactoryIndexConfig::LoadYAML(const YAML::Node& aNode) 14 | { 15 | const auto& factoriesNode = aNode[FactoryIndexNodeKey]; 16 | 17 | if (!factoriesNode.IsDefined()) 18 | return; 19 | 20 | bool malformed = false; 21 | 22 | switch (factoriesNode.Type()) 23 | { 24 | case YAML::NodeType::Sequence: 25 | { 26 | for (const auto& pathNode : factoriesNode) 27 | { 28 | if (pathNode.IsScalar()) 29 | factories.emplace_back(pathNode.Scalar()); 30 | else 31 | malformed = true; 32 | } 33 | break; 34 | } 35 | case YAML::NodeType::Scalar: 36 | { 37 | factories.emplace_back(factoriesNode.Scalar()); 38 | break; 39 | } 40 | default: 41 | { 42 | malformed = true; 43 | } 44 | } 45 | 46 | if (malformed) 47 | issues.emplace_back("Bad format. Expected resource path or list of paths."); 48 | } 49 | -------------------------------------------------------------------------------- /src/App/Extensions/FactoryIndex/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct FactoryIndexConfig : ExtensionConfig 8 | { 9 | using ExtensionConfig::ExtensionConfig; 10 | 11 | bool IsDefined() override; 12 | void LoadYAML(const YAML::Node& aNode) override; 13 | 14 | Core::Vector factories; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/App/Extensions/FactoryIndex/Extension.cpp: -------------------------------------------------------------------------------- 1 | #include "Extension.hpp" 2 | #include "Core/Facades/Container.hpp" 3 | #include "Red/FactoryIndex.hpp" 4 | 5 | namespace 6 | { 7 | constexpr auto ExtensionName = "FactoryIndex"; 8 | constexpr auto LastFactory = Red::ResourcePath(R"(base\gameplay\factories\vehicles\vehicles.csv)"); 9 | } 10 | 11 | std::string_view App::FactoryIndexExtension::GetName() 12 | { 13 | return ExtensionName; 14 | } 15 | 16 | bool App::FactoryIndexExtension::Load() 17 | { 18 | HookAfter(&OnLoadFactoryAsync).OrThrow(); 19 | 20 | s_resourcePathRegistry = Core::Resolve(); 21 | 22 | return true; 23 | } 24 | 25 | bool App::FactoryIndexExtension::Unload() 26 | { 27 | Unhook(); 28 | 29 | return true; 30 | } 31 | 32 | void App::FactoryIndexExtension::Configure() 33 | { 34 | s_factories.clear(); 35 | 36 | auto depot = Red::ResourceDepot::Get(); 37 | 38 | Core::Set invalidPaths; 39 | 40 | for (const auto& unit : m_configs) 41 | { 42 | for (const auto& factoryPathStr : unit.factories) 43 | { 44 | auto factoryPath = Red::ResourcePath(factoryPathStr.c_str()); 45 | 46 | if (!depot->ResourceExists(factoryPath)) 47 | { 48 | if (!invalidPaths.contains(factoryPath)) 49 | { 50 | LogError("[{}] Factory \"{}\" doesn't exist. Skipped.", ExtensionName, factoryPathStr); 51 | invalidPaths.insert(factoryPath); 52 | } 53 | continue; 54 | } 55 | 56 | s_factories.insert_or_assign(factoryPath, factoryPathStr); 57 | } 58 | } 59 | 60 | for (const auto& [knownPath, knownPathStr] : s_factories) 61 | { 62 | s_resourcePathRegistry->RegisterPath(knownPath, knownPathStr); 63 | } 64 | 65 | m_configs.clear(); 66 | } 67 | 68 | void App::FactoryIndexExtension::OnLoadFactoryAsync(uintptr_t aIndex, Red::ResourcePath aPath, uintptr_t aContext) 69 | { 70 | if (aPath != LastFactory) 71 | return; 72 | 73 | LogInfo("[{}] Initializing factory index...", ExtensionName); 74 | 75 | if (!s_factories.empty()) 76 | { 77 | for (const auto& [factoryPath, factoryPathStr] : s_factories) 78 | { 79 | LogInfo("[{}] Loading factory \"{}\"...", ExtensionName, factoryPathStr); 80 | 81 | Raw::FactoryIndex::LoadFactoryAsync(aIndex, factoryPath, aContext); 82 | } 83 | 84 | LogInfo("[{}] All factories loaded.", ExtensionName); 85 | } 86 | else 87 | { 88 | LogInfo("[{}] No factories to load.", ExtensionName); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/App/Extensions/FactoryIndex/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/FactoryIndex/Config.hpp" 5 | #include "App/Shared/ResourcePathRegistry.hpp" 6 | 7 | namespace App 8 | { 9 | class FactoryIndexExtension : public ConfigurableExtensionImpl 10 | { 11 | public: 12 | std::string_view GetName() override; 13 | bool Load() override; 14 | bool Unload() override; 15 | void Configure() override; 16 | 17 | private: 18 | static void OnLoadFactoryAsync(uintptr_t aIndex, Red::ResourcePath aPath, uintptr_t aContext); 19 | 20 | inline static Core::Map s_factories; 21 | inline static Core::SharedPtr s_resourcePathRegistry; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/App/Extensions/Garment/ChunkMask.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct ChunkMask 4 | { 5 | constexpr ChunkMask(bool aSet, std::initializer_list aChunks) noexcept 6 | { 7 | Set(aSet, aChunks); 8 | } 9 | 10 | constexpr explicit ChunkMask(std::initializer_list aChunks) noexcept 11 | { 12 | Set(false, aChunks); 13 | } 14 | 15 | ChunkMask(bool aSet, const std::vector& aChunks) noexcept 16 | { 17 | Set(aSet, aChunks); 18 | } 19 | 20 | explicit ChunkMask(const std::vector& aChunks) noexcept 21 | { 22 | Set(false, aChunks); 23 | } 24 | 25 | constexpr ChunkMask(bool aSet, uint64_t aMask) noexcept 26 | : set(aSet) 27 | , mask(aMask) 28 | { 29 | } 30 | 31 | constexpr explicit ChunkMask(uint64_t aMask) noexcept 32 | : ChunkMask(false, aMask) 33 | { 34 | } 35 | 36 | constexpr explicit ChunkMask(bool aSet) noexcept 37 | : set(aSet) 38 | , mask(aSet ? ~0ull : 0ull) 39 | { 40 | } 41 | 42 | constexpr operator bool() const noexcept 43 | { 44 | return set; 45 | } 46 | 47 | constexpr operator uint64_t() const noexcept 48 | { 49 | return mask; 50 | } 51 | 52 | template 53 | constexpr void Set(bool aSet, const T& aChunks) noexcept 54 | { 55 | set = aSet; 56 | mask = 0ull; 57 | 58 | for (const auto& chunk : aChunks) 59 | { 60 | mask |= 1 << chunk; 61 | } 62 | 63 | if (!set && mask) 64 | { 65 | mask = ~mask; 66 | } 67 | } 68 | 69 | bool set; 70 | uint64_t mask; 71 | }; 72 | -------------------------------------------------------------------------------- /src/App/Extensions/Garment/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/Garment/Tags.hpp" 5 | 6 | namespace App 7 | { 8 | struct GarmentOverrideConfig : ExtensionConfig 9 | { 10 | using ExtensionConfig::ExtensionConfig; 11 | 12 | bool IsDefined() override; 13 | void LoadYAML(const YAML::Node& aNode) override; 14 | 15 | Core::Map tags; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/App/Extensions/Garment/Prefix.cpp: -------------------------------------------------------------------------------- 1 | #include "Prefix.hpp" 2 | 3 | Red::CName App::ComponentPrefixResolver::GetPrefix(Red::CName aComponentName) 4 | { 5 | if (!aComponentName) 6 | return {}; 7 | 8 | auto it = m_resolved.find(aComponentName); 9 | 10 | if (it == m_resolved.end()) 11 | { 12 | const auto nameStr = aComponentName.ToString(); 13 | 14 | size_t prefixEnd = 2; 15 | constexpr size_t prefixMaxLen = 6; 16 | while (prefixEnd < prefixMaxLen && nameStr[prefixEnd] && nameStr[prefixEnd] != '_') 17 | ++prefixEnd; 18 | 19 | uint64_t prefixHash = 0; 20 | if (prefixEnd < prefixMaxLen && nameStr[prefixEnd] == '_') 21 | prefixHash = Red::CNamePool::Add(std::string().append(nameStr, prefixEnd + 1).c_str()); 22 | 23 | it = m_resolved.emplace(aComponentName, prefixHash).first; 24 | } 25 | 26 | return it.value(); 27 | } 28 | 29 | Core::SharedPtr& App::ComponentPrefixResolver::Get() 30 | { 31 | static auto s_instance = Core::MakeShared(); 32 | return s_instance; 33 | } 34 | -------------------------------------------------------------------------------- /src/App/Extensions/Garment/Prefix.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace App 4 | { 5 | class ComponentPrefixResolver 6 | { 7 | public: 8 | [[nodiscard]] Red::CName GetPrefix(Red::CName aComponentName); 9 | 10 | [[nodiscard]] static Core::SharedPtr& Get(); 11 | 12 | private: 13 | Core::Map m_resolved; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/App/Extensions/Garment/Tags.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ChunkMask.hpp" 4 | 5 | namespace App 6 | { 7 | using OverrideTagDefinition = Core::Map; 8 | 9 | class OverrideTagManager 10 | { 11 | public: 12 | OverrideTagManager() noexcept; 13 | 14 | void DefineTag(Red::CName aTag, OverrideTagDefinition aDefinition); 15 | [[nodiscard]] OverrideTagDefinition& GetOverrides(Red::CName aTag); 16 | 17 | private: 18 | constexpr auto Hide() 19 | { 20 | return ChunkMask(false); 21 | } 22 | 23 | constexpr auto Hide(std::initializer_list aChunks) 24 | { 25 | return ChunkMask(false, aChunks); 26 | } 27 | 28 | constexpr auto Show(std::initializer_list aChunks) 29 | { 30 | return ChunkMask(true, aChunks); 31 | } 32 | 33 | Core::Map m_definitions; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/App/Extensions/Garment/Wrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace App 4 | { 5 | enum class ComponentType 6 | { 7 | Unsupported, 8 | MeshComponent, 9 | SkinnedMeshComponent, 10 | GarmentSkinnedMeshComponent, 11 | MorphTargetSkinnedMeshComponent, 12 | }; 13 | 14 | class ComponentWrapper 15 | { 16 | public: 17 | explicit ComponentWrapper(Red::IComponent* aComponent); 18 | explicit ComponentWrapper(const Red::Handle& aComponent); 19 | 20 | [[nodiscard]] bool IsMeshComponent() const; 21 | [[nodiscard]] bool IsGarmentComponent() const; 22 | [[nodiscard]] uint64_t GetUniqueId(); 23 | 24 | [[nodiscard]] bool IsEnabled() const; 25 | bool SetEnabled(bool isEnabled) const; 26 | 27 | [[nodiscard]] Red::ResourcePath GetResourcePath() const; 28 | bool SetResourcePath(Red::ResourcePath aPath) const; 29 | 30 | bool LoadResource(bool aRefresh, bool aWait) const; 31 | [[nodiscard]] Red::SharedPtr> LoadResourceToken(bool aWait = false) const; 32 | [[nodiscard]] Red::SharedPtr> LoadMeshToken(bool aWait = false) const; 33 | 34 | [[nodiscard]] Red::CName GetAppearanceName() const; 35 | bool SetAppearanceName(Red::CName aAppearance) const; 36 | bool LoadAppearance() const; 37 | 38 | [[nodiscard]] uint64_t GetChunkMask() const; 39 | bool SetChunkMask(uint64_t aChunkMask) const; 40 | 41 | bool RefreshAppearance() const; 42 | 43 | private: 44 | Red::IComponent* m_component; 45 | ComponentType m_componentType; 46 | uint64_t m_uniqueId; 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /src/App/Extensions/InkSpawner/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "Red/InkSpawner.hpp" 5 | 6 | namespace App 7 | { 8 | class InkSpawnerExtension : public Extension 9 | { 10 | public: 11 | std::string_view GetName() override; 12 | bool Load() override; 13 | bool Unload() override; 14 | 15 | private: 16 | static uintptr_t OnSpawnLocal(Red::ink::WidgetLibraryResource& aLibrary, 17 | Red::Handle& aInstance, 18 | Red::CName aItemName); 19 | 20 | static uintptr_t OnSpawnExternal(Red::ink::WidgetLibraryResource& aLibrary, 21 | Red::Handle& aInstance, 22 | Red::ResourcePath aExternalPath, 23 | Red::CName aItemName); 24 | 25 | static bool OnAsyncSpawnLocal(Red::ink::WidgetLibraryResource& aLibrary, 26 | Red::InkSpawningInfo& aSpawningInfo, 27 | Red::CName aItemName); 28 | 29 | static bool OnAsyncSpawnExternal(Red::ink::WidgetLibraryResource& aLibrary, 30 | Red::InkSpawningInfo& aSpawningInfo, 31 | Red::ResourcePath aExternalPath, 32 | Red::CName aItemName); 33 | 34 | static void OnFinishAsyncSpawn(Red::InkSpawningContext& aContext, 35 | Red::Handle& aInstance); 36 | 37 | inline static void InjectDependency(Red::ink::WidgetLibraryResource& aLibrary, 38 | Red::ResourcePath aExternalPath); 39 | 40 | inline static void InjectController(Red::Handle& aInstance, 41 | Red::CName aControllerName); 42 | 43 | inline static void InheritProperties(Red::IScriptable* aTarget, Red::IScriptable* aSource); 44 | 45 | inline static Red::SharedSpinLock s_mutex; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/App/Extensions/Journal/Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.hpp" 2 | 3 | namespace 4 | { 5 | constexpr auto JournalNodeKey = "journal"; 6 | } 7 | 8 | bool App::JournalConfig::IsDefined() 9 | { 10 | return !journals.empty(); 11 | } 12 | 13 | void App::JournalConfig::LoadYAML(const YAML::Node& aNode) 14 | { 15 | const auto& rootNode = aNode[JournalNodeKey]; 16 | 17 | if (!rootNode.IsDefined()) 18 | return; 19 | 20 | bool malformed = false; 21 | 22 | switch (rootNode.Type()) 23 | { 24 | case YAML::NodeType::Sequence: 25 | { 26 | for (const auto& pathNode : rootNode) 27 | { 28 | if (pathNode.IsScalar()) 29 | journals.emplace_back(pathNode.Scalar()); 30 | else 31 | malformed = true; 32 | } 33 | break; 34 | } 35 | case YAML::NodeType::Scalar: 36 | { 37 | journals.emplace_back(aNode.Scalar()); 38 | break; 39 | } 40 | default: 41 | { 42 | malformed = true; 43 | } 44 | } 45 | 46 | if (malformed) 47 | issues.emplace_back("Bad format. Expected resource path or list of paths."); 48 | } 49 | -------------------------------------------------------------------------------- /src/App/Extensions/Journal/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct JournalConfig : ExtensionConfig 8 | { 9 | using ExtensionConfig::ExtensionConfig; 10 | 11 | bool IsDefined() override; 12 | void LoadYAML(const YAML::Node& aNode) override; 13 | 14 | Core::Vector journals; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/App/Extensions/Journal/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/Journal/Config.hpp" 4 | #include "App/Extensions/ExtensionBase.hpp" 5 | #include "Red/JournalManager.hpp" 6 | #include "Red/JournalTree.hpp" 7 | #include "Red/MappinSystem.hpp" 8 | 9 | namespace App 10 | { 11 | class JournalExtension : public ConfigurableExtensionImpl 12 | { 13 | public: 14 | std::string_view GetName() override; 15 | bool Load() override; 16 | void Reload() override; 17 | bool Unload() override; 18 | 19 | private: 20 | using EntrySearchResult = std::tuple; 21 | 22 | struct JournalMappin 23 | { 24 | Red::NodeRef reference; 25 | Red::Vector3 offset; 26 | bool isPointOfInterest; 27 | }; 28 | 29 | void OnLoadJournal(uintptr_t a1, Red::JobGroup& aJobGroup); 30 | void OnInitializeRoot(Red::game::JournalRootFolderEntry* aRoot, uintptr_t, uintptr_t, Red::JobQueue& aJobQueue); 31 | static void OnMappinDataLoaded(void* aMappinSystem, Red::worldRuntimeScene*); 32 | static void* OnGetMappinData(void* aMappinSystem, uint32_t aHash); 33 | static void* OnGetPoiData(void* aMappinSystem, uint32_t aHash); 34 | 35 | static EntrySearchResult FindEntry(Red::game::JournalEntry* aParent, Red::CString& aPath); 36 | static bool MergeEntries(Red::game::JournalContainerEntry* aTarget, Red::game::JournalContainerEntry* aSource, 37 | const std::string& aPath = ""); 38 | static bool MergeEntry(Red::game::JournalEntry* aTarget, Red::game::JournalEntry* aSource, 39 | const std::string& aPath, bool aEditProps = false); 40 | static void ProcessNewEntries(Red::game::JournalEntry* aEntry, const std::string& aPath, bool aRecursive); 41 | static void ConvertLocKeys(Red::game::JournalEntry* aEntry); 42 | static void CollectMappin(Red::game::JournalEntry* aEntry, const std::string& aPath); 43 | static void ResolveCookedMappin(void* aMappinSystem, uint32_t aHash, const JournalMappin& aJournalMappin, 44 | void*& aCookedMappin); 45 | static bool ResolveMappinPosition(uint32_t aJournalHash, const JournalMappin& aMappin, Red::Vector3& aResult); 46 | static void ResetResourceData(); 47 | static void ResetRuntimeData(); 48 | static void ReloadJournal(); 49 | 50 | static std::string MakePath(const std::string& aPath, const std::string& aStep); 51 | static uint32_t CalculateJournalHash(const std::string& aPath); 52 | 53 | inline static Core::Vector>> s_resources; 54 | inline static Core::Map s_paths; 55 | inline static Core::Map s_mappins; 56 | inline static Red::SharedSpinLock s_mappinsLock; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/App/Extensions/Localization/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct LocalizationConfig : ExtensionConfig 8 | { 9 | bool IsDefined() override; 10 | void LoadYAML(const YAML::Node& aNode) override; 11 | 12 | static bool ReadOptions(const YAML::Node& aNode, Core::Map>& aOptions, 13 | Red::CName& aFallback, Core::Vector& aIssues); 14 | 15 | Red::CName fallback; 16 | Core::Map> onscreens; 17 | Core::Map> subtitles; 18 | Core::Map> lipmaps; 19 | Core::Map> vomaps; 20 | std::string extend; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/App/Extensions/Localization/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/Localization/Config.hpp" 5 | 6 | namespace App 7 | { 8 | using TextResource = Red::localization::PersistenceOnScreenEntries; 9 | using TextEntry = Red::localization::PersistenceOnScreenEntry; 10 | using TextEntryList = Red::DynArray; 11 | using TextEntryMap = Core::Map; 12 | 13 | using SubtitleResource = Red::localization::PersistenceSubtitleMap; 14 | using SubtitleEntry = Red::localization::PersistenceSubtitleMapEntry; 15 | using SubtitleEntryList = Red::DynArray; 16 | 17 | class LocalizationExtension : public ConfigurableExtensionImpl 18 | { 19 | public: 20 | std::string_view GetName() override; 21 | bool Load() override; 22 | bool Unload() override; 23 | void Configure() override; 24 | 25 | private: 26 | void OnLoadTexts(Red::Handle& aOnScreens, Red::ResourcePath aPath); 27 | void OnLoadSubtitles(Red::Handle& aSubtitles, Red::ResourcePath aPath); 28 | void OnLoadVoiceOvers(void* aContext, uint64_t a2); 29 | void OnLoadLipsyncs(void* aContext, uint8_t a2); 30 | 31 | static bool MergeTextResource(const std::string& aPath, TextEntryList& aFinalList, TextEntryMap& aUsedKeyMap, 32 | uint32_t aOriginalCount, uint64_t aOriginalMaxKey, bool aFallback); 33 | static void MergeTextEntry(TextEntryList& aFinalList, TextEntry& aNewEntry, uint32_t aIndex, 34 | TextEntryMap& aUsedKeyMap, uint32_t aOriginalCount, uint64_t aOriginalMaxKey, 35 | bool aFallback, bool aExtraEntry = false); 36 | static TextEntry* FindSameTextEntry(TextEntry& aEntry, TextEntryList& aList, uint32_t aCount); 37 | static bool IsCommentEntry(TextEntry& aEntry); 38 | static bool MergeSubtitleResource(const std::string& aPath, SubtitleEntryList& aFinalList); 39 | static bool MergeLipsyncResource(const Red::Handle& aSource, 40 | Red::Handle& aTarget); 41 | 42 | static inline Red::ResourceToken* s_currentLipMap; 43 | static inline Core::Map s_paths; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/App/Extensions/Localization/Language.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace App::Language 4 | { 5 | enum Code : uint64_t 6 | { 7 | Arabic = (uint64_t) Red::CName("ar-ar"), 8 | Czech = (uint64_t) Red::CName("cz-cz"), 9 | German = (uint64_t) Red::CName("de-de"), 10 | English = (uint64_t) Red::CName("en-us"), 11 | Spanish = (uint64_t) Red::CName("es-es"), 12 | LatinAmericanSpanish = (uint64_t) Red::CName("es-mx"), 13 | French = (uint64_t) Red::CName("fr-fr"), 14 | Hungarian = (uint64_t) Red::CName("hu-hu"), 15 | Italian = (uint64_t) Red::CName("it-it"), 16 | Japanese = (uint64_t) Red::CName("jp-jp"), 17 | Korean = (uint64_t) Red::CName("kr-kr"), 18 | Polish = (uint64_t) Red::CName("pl-pl"), 19 | BrazilianPortuguese = (uint64_t) Red::CName("pt-br"), 20 | Russian = (uint64_t) Red::CName("ru-ru"), 21 | Thai = (uint64_t) Red::CName("th-th"), 22 | Turkish = (uint64_t) Red::CName("tr-tr"), 23 | Ukrainian = (uint64_t) Red::CName("ua-ua"), 24 | SimplifiedChinese = (uint64_t) Red::CName("zh-cn"), 25 | TraditionalChinese = (uint64_t) Red::CName("zh-tw"), 26 | }; 27 | 28 | bool IsKnown(Red::CName aLanguage); 29 | Red::CName ResolveFromTextResource(Red::ResourcePath aPath); 30 | Red::CName ResolveFromSubtitleResource(Red::ResourcePath aPath); 31 | Red::CName ResolveFromLipsyncResource(Red::ResourcePath aPath); 32 | } 33 | -------------------------------------------------------------------------------- /src/App/Extensions/PuppetState/Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.hpp" 2 | 3 | namespace 4 | { 5 | constexpr auto PlayerNodeKey = "player"; 6 | constexpr auto BodyTypesNodeKey = "bodyTypes"; 7 | } 8 | 9 | bool App::PuppetStateConfig::IsDefined() 10 | { 11 | return !bodyTypes.empty(); 12 | } 13 | 14 | void App::PuppetStateConfig::LoadYAML(const YAML::Node& aNode) 15 | { 16 | const auto& playerNode = aNode[PlayerNodeKey]; 17 | 18 | if (!playerNode.IsDefined()) 19 | return; 20 | 21 | bool malformed = false; 22 | 23 | const auto& bodyTypesNode = playerNode[BodyTypesNodeKey]; 24 | 25 | if (bodyTypesNode.IsDefined()) 26 | { 27 | switch (bodyTypesNode.Type()) 28 | { 29 | case YAML::NodeType::Sequence: 30 | { 31 | for (const auto& itemNode : bodyTypesNode) 32 | { 33 | if (itemNode.IsScalar()) 34 | bodyTypes.push_back(itemNode.Scalar()); 35 | else 36 | malformed = true; 37 | } 38 | break; 39 | } 40 | case YAML::NodeType::Scalar: 41 | { 42 | bodyTypes.push_back(bodyTypesNode.Scalar()); 43 | break; 44 | } 45 | default: 46 | { 47 | malformed = true; 48 | } 49 | } 50 | } 51 | 52 | if (malformed) 53 | issues.emplace_back("Bad format. Expected body type name or list of names."); 54 | } 55 | -------------------------------------------------------------------------------- /src/App/Extensions/PuppetState/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct PuppetStateConfig : ExtensionConfig 8 | { 9 | using ExtensionConfig::ExtensionConfig; 10 | 11 | bool IsDefined() override; 12 | void LoadYAML(const YAML::Node& aNode) override; 13 | 14 | Core::Vector bodyTypes; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/App/Extensions/PuppetState/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/PuppetState/Handler.hpp" 5 | #include "App/Extensions/PuppetState/System.hpp" 6 | #include "App/Extensions/PuppetState/Config.hpp" 7 | #include "Red/CharacterCustomization.hpp" 8 | 9 | namespace App 10 | { 11 | class PuppetStateExtension : public ConfigurableExtensionImpl 12 | { 13 | public: 14 | static constexpr auto BodyTypeSuffixID = Red::TweakDBID("itemsFactoryAppearanceSuffix.BodyType"); 15 | static constexpr auto ArmsStateSuffixID = Red::TweakDBID("itemsFactoryAppearanceSuffix.ArmsState"); 16 | static constexpr auto FeetStateSuffixID = Red::TweakDBID("itemsFactoryAppearanceSuffix.FeetState"); 17 | static constexpr auto LegsStateSuffixID = Red::TweakDBID("itemsFactoryAppearanceSuffix.LegsState"); 18 | static constexpr auto BaseBodyName = "BaseBody"; 19 | 20 | bool Load() override; 21 | bool Unload() override; 22 | void Configure() override; 23 | void OnTweakDBReady() override; 24 | 25 | std::string_view GetName() override; 26 | 27 | static const Core::Set& GetBodyTypes(); 28 | static const Core::Map& GetBodyTags(); 29 | 30 | static Red::CName GetBodyType(const Red::WeakHandle& aPuppet); 31 | static PuppetArmsState GetArmsState(const Red::WeakHandle& aPuppet); 32 | static PuppetFeetState GetFeetState(const Red::WeakHandle& aPuppet); 33 | 34 | private: 35 | static void OnAttachPuppet(Red::gameuiCharacterCustomizationGenitalsController* aComponent); 36 | static void OnDetachPuppet(Red::gameuiCharacterCustomizationHairstyleController* aComponent, uintptr_t); 37 | 38 | static void CreateSuffixRecord(Red::TweakDBID aSuffixID, Red::CName aSystemName, Red::CName aFunctionName); 39 | static void CreateSuffixAlias(Red::TweakDBID aSuffixID, Red::TweakDBID aAliasID); 40 | static void ActivateSuffixRecords(const Core::Vector& aSuffixIDs); 41 | 42 | inline static Red::CName s_baseBodyType; 43 | inline static Core::Set s_bodyTypes; 44 | inline static Core::Map s_bodyTags; 45 | inline static Core::Map> s_handlers; 46 | static inline Red::SharedSpinLock s_mutex; 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /src/App/Extensions/PuppetState/System.cpp: -------------------------------------------------------------------------------- 1 | #include "System.hpp" 2 | #include "Extension.hpp" 3 | 4 | Red::CString App::PuppetStateSystem::GetBodyTypeSuffix(Red::ItemID& aItemID, 5 | const Red::WeakHandle& aOwner, 6 | const Red::Handle&) 7 | { 8 | return PuppetStateExtension::GetBodyType(aOwner).ToString(); 9 | } 10 | 11 | Red::CString App::PuppetStateSystem::GetArmsStateSuffix(Red::ItemID& aItemID, 12 | const Red::WeakHandle& aOwner, 13 | const Red::Handle&) 14 | { 15 | constexpr auto BaseArmsStr = RTTI_ENUM_NAME_STR(PuppetArmsState::BaseArms); 16 | constexpr auto MantisBladesStr = RTTI_ENUM_NAME_STR(PuppetArmsState::MantisBlades); 17 | constexpr auto MonowireStr = RTTI_ENUM_NAME_STR(PuppetArmsState::Monowire); 18 | constexpr auto ProjectileLauncherStr = RTTI_ENUM_NAME_STR(PuppetArmsState::ProjectileLauncher); 19 | 20 | switch (PuppetStateExtension::GetArmsState(aOwner)) 21 | { 22 | case PuppetArmsState::BaseArms: return BaseArmsStr; 23 | case PuppetArmsState::MantisBlades: return MantisBladesStr; 24 | case PuppetArmsState::Monowire: return MonowireStr; 25 | case PuppetArmsState::ProjectileLauncher: return ProjectileLauncherStr; 26 | default: return ""; 27 | } 28 | } 29 | 30 | Red::CString App::PuppetStateSystem::GetFeetStateSuffix(Red::ItemID& aItemID, 31 | const Red::WeakHandle& aOwner, 32 | const Red::Handle&) 33 | { 34 | constexpr auto FlatStr = RTTI_ENUM_NAME_STR(PuppetFeetState::Flat); 35 | constexpr auto LiftedStr = RTTI_ENUM_NAME_STR(PuppetFeetState::Lifted); 36 | constexpr auto HighHeelsStr = RTTI_ENUM_NAME_STR(PuppetFeetState::HighHeels); 37 | constexpr auto FlatShoesStr = RTTI_ENUM_NAME_STR(PuppetFeetState::FlatShoes); 38 | 39 | switch (PuppetStateExtension::GetFeetState(aOwner)) 40 | { 41 | case PuppetFeetState::Flat: return FlatStr; 42 | case PuppetFeetState::Lifted: return LiftedStr; 43 | case PuppetFeetState::HighHeels: return HighHeelsStr; 44 | case PuppetFeetState::FlatShoes: return FlatShoesStr; 45 | default: return ""; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/App/Extensions/PuppetState/System.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace App 4 | { 5 | struct PuppetStateSystem : Red::ScriptableSystem 6 | { 7 | Red::CString GetBodyTypeSuffix(Red::ItemID& aItemID, const Red::WeakHandle& aOwner, 8 | const Red::Handle&); 9 | Red::CString GetArmsStateSuffix(Red::ItemID& aItemID, const Red::WeakHandle& aOwner, 10 | const Red::Handle&); 11 | Red::CString GetFeetStateSuffix(Red::ItemID& aItemID, const Red::WeakHandle& aOwner, 12 | const Red::Handle&); 13 | 14 | RTTI_IMPL_TYPEINFO(App::PuppetStateSystem); 15 | RTTI_FWD_CONSTRUCTOR(); 16 | }; 17 | } 18 | 19 | RTTI_DEFINE_CLASS(App::PuppetStateSystem, { 20 | RTTI_SCRIPT_METHOD(GetBodyTypeSuffix); 21 | RTTI_SCRIPT_METHOD(GetArmsStateSuffix); 22 | RTTI_SCRIPT_METHOD(GetFeetStateSuffix); 23 | }) 24 | -------------------------------------------------------------------------------- /src/App/Extensions/QuestPhase/Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.hpp" 2 | 3 | bool App::QuestPhaseConfig::IsDefined() 4 | { 5 | return !phases.empty(); 6 | } 7 | 8 | void App::QuestPhaseConfig::LoadYAML(const YAML::Node& aNode) 9 | { 10 | const auto& questNode = aNode["quest"]; 11 | 12 | if (!questNode.IsDefined()) 13 | return; 14 | 15 | const auto& phasesNode = questNode["phases"]; 16 | if (phasesNode.IsDefined() && phasesNode.IsSequence()) 17 | { 18 | for (const auto& phaseNode : phasesNode) 19 | { 20 | if (!phaseNode.IsMap()) 21 | { 22 | continue; 23 | } 24 | 25 | const auto& pathNode = phaseNode["path"]; 26 | const auto& parentNode = phaseNode["parent"]; 27 | 28 | if (!pathNode.IsDefined() || !pathNode.IsScalar() || !parentNode.IsDefined() || !parentNode.IsScalar()) 29 | { 30 | continue; 31 | } 32 | 33 | QuestPhaseMod phaseData{}; 34 | phaseData.mod = name; 35 | phaseData.phasePath = pathNode.Scalar(); 36 | phaseData.parentPaths.insert(parentNode.Scalar()); 37 | 38 | FillConnection(phaseNode["connection"], phaseData.input); 39 | FillConnection(phaseNode["input"], phaseData.input); 40 | FillConnection(phaseNode["output"], phaseData.output); 41 | 42 | phases.emplace_back(std::move(phaseData)); 43 | } 44 | } 45 | } 46 | 47 | bool App::QuestPhaseConfig::FillConnection(const YAML::Node& aNode, App::QuestPhaseConnection& aConnection) 48 | { 49 | if (aNode.IsDefined()) 50 | { 51 | if (aNode.IsMap()) 52 | { 53 | aConnection.nodePath = aNode["node"].as>(); 54 | aConnection.socketName = aNode["socket"].Scalar().data(); 55 | return true; 56 | } 57 | if (aNode.IsSequence()) 58 | { 59 | aConnection.nodePath = aNode.as>(); 60 | return true; 61 | } 62 | } 63 | 64 | return false; 65 | } 66 | -------------------------------------------------------------------------------- /src/App/Extensions/QuestPhase/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct QuestPhaseConnection 8 | { 9 | Core::Vector nodePath; 10 | Red::CName socketName; 11 | }; 12 | 13 | struct QuestPhaseMod 14 | { 15 | std::string mod; 16 | std::string phasePath; 17 | Core::Set parentPaths; 18 | QuestPhaseConnection input; 19 | QuestPhaseConnection output; 20 | }; 21 | 22 | struct QuestPhaseConfig : ExtensionConfig 23 | { 24 | using ExtensionConfig::ExtensionConfig; 25 | 26 | bool IsDefined() override; 27 | void LoadYAML(const YAML::Node& aNode) override; 28 | bool FillConnection(const YAML::Node& aNode, QuestPhaseConnection& aConnection); 29 | 30 | Core::Vector phases; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/App/Extensions/QuestPhase/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/QuestPhase/Config.hpp" 5 | #include "App/Shared/ResourcePathRegistry.hpp" 6 | #include "Red/QuestLoader.hpp" 7 | #include "Red/QuestPhase.hpp" 8 | #include "Red/QuestsSystem.hpp" 9 | 10 | namespace App 11 | { 12 | class QuestPhaseExtension : public ConfigurableExtensionImpl 13 | { 14 | public: 15 | std::string_view GetName() override; 16 | bool Load() override; 17 | bool Unload() override; 18 | void Configure() override; 19 | 20 | private: 21 | using ConnectionPoint = std::pair, Red::Handle>; 22 | 23 | static void OnPhasePreload(void* aLoader, Red::ResourcePath aPhasePath, 24 | Red::Handle& aPhaseResource); 25 | static void OnGameRestored(Red::QuestsSystem* aSystem); 26 | static void OnQuestStart(Red::questRootInstance* aInstance, Red::QuestContext* aContext, 27 | const Red::Handle& aResource); 28 | 29 | static bool PatchPhase(Red::Handle& aPhaseResource, const QuestPhaseMod& aPhaseMod); 30 | static ConnectionPoint FindConnectionPoint(const Red::Handle& aPhaseGraph, 31 | const Core::Vector& aNodePath, uint32_t aStep = 0); 32 | static Red::Handle ResolveSocket(Red::Handle& aNode, 33 | Red::questSocketType aSocketType, 34 | Red::CName aSocketName); 35 | static void AddConnection(Red::Handle& aOutNode, Red::CName aOutSocket, 36 | Red::Handle& aInNode, Red::CName aInSocket); 37 | static void RemoveConnection(Red::Handle& aOutNode, 38 | Red::Handle& aInNode); 39 | static Red::Handle CreatePhaseNode( 40 | const Red::Handle& aPhaseGraph, const QuestPhaseMod& aPhaseMod, uint16_t aParentID); 41 | static uint16_t GeneratePhaseNodeID(const char* aData, uint32_t aLength); 42 | 43 | inline static Core::Map> s_phases; 44 | inline static Core::Map> s_forced; 45 | inline static Core::SharedPtr s_resourcePathRegistry; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/App/Extensions/ResourceLink/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct ResourceLinkConfig : ExtensionConfig 8 | { 9 | using ExtensionConfig::ExtensionConfig; 10 | 11 | bool IsDefined() override; 12 | void LoadYAML(const YAML::Node& aNode) override; 13 | 14 | Core::Map> copies; 15 | Core::Map> links; 16 | Core::Map paths; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/App/Extensions/ResourceLink/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/ResourceLink/Config.hpp" 5 | #include "Red/ResourceDepot.hpp" 6 | #include "Red/ResourceLoader.hpp" 7 | 8 | namespace App 9 | { 10 | class ResourceLinkExtension : public ConfigurableExtensionImpl 11 | { 12 | public: 13 | std::string_view GetName() override; 14 | bool Load() override; 15 | bool Unload() override; 16 | void Configure() override; 17 | 18 | static Core::Set GetAliases(Red::ResourcePath aPath); 19 | 20 | private: 21 | static void OnLoaderResourceRequest(Red::ResourceLoader* aLoader, Red::SharedPtr>& aToken, 22 | Red::ResourceRequest& aRequest); 23 | static uintptr_t* OnDepotResourceRequest(Red::ResourceDepot* aDepot, const uintptr_t* aResourceHandle, 24 | Red::ResourcePath aPath, const int32_t* aArchiveHandle); 25 | static bool OnDepotResourceCheck(Red::ResourceDepot* aDepot, Red::ResourcePath aPath); 26 | 27 | inline static Core::Map s_mappings; 28 | inline static Core::Map s_copies; 29 | inline static Core::Map s_links; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/App/Extensions/ResourceMeta/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct ResourceFix 8 | { 9 | [[nodiscard]] inline bool DefinesNameMappings() const 10 | { 11 | return !names.empty(); 12 | } 13 | 14 | [[nodiscard]] inline Red::CName GetMappedName(Red::CName aName) const 15 | { 16 | const auto& it = names.find(aName); 17 | return it != names.end() ? it.value() : aName; 18 | } 19 | 20 | [[nodiscard]] inline bool DefinesPathMappings() const 21 | { 22 | return !paths.empty(); 23 | } 24 | 25 | [[nodiscard]] inline Red::ResourcePath GetMappedPath(Red::ResourcePath aPath) const 26 | { 27 | const auto& it = paths.find(aPath); 28 | return it != paths.end() ? it.value() : aPath; 29 | } 30 | 31 | [[nodiscard]] inline bool DefinesContext() const 32 | { 33 | return !context.empty(); 34 | } 35 | 36 | [[nodiscard]] inline const Core::Map& GetContext() const 37 | { 38 | return context; 39 | } 40 | 41 | void Merge(const ResourceFix& aOther) 42 | { 43 | for (const auto& [oldName, newName] : aOther.names) 44 | { 45 | names[oldName] = newName; 46 | } 47 | 48 | for (const auto& [oldPath, newPath] : aOther.paths) 49 | { 50 | paths[oldPath] = newPath; 51 | } 52 | 53 | for (const auto& [param, value] : aOther.context) 54 | { 55 | context[param] = value; 56 | } 57 | } 58 | 59 | Core::Map names; 60 | Core::Map paths; 61 | Core::Map context; 62 | }; 63 | 64 | struct ResourceMetaConfig : ExtensionConfig 65 | { 66 | using ExtensionConfig::ExtensionConfig; 67 | 68 | bool IsDefined() override; 69 | void LoadYAML(const YAML::Node& aNode) override; 70 | void LoadScopes(const YAML::Node& aNode); 71 | void LoadFixes(const YAML::Node& aNode); 72 | 73 | Core::Map> scopes; 74 | Core::Map fixes; 75 | Core::Map paths; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /src/App/Extensions/ResourceMeta/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/ResourceMeta/Config.hpp" 5 | 6 | namespace App 7 | { 8 | class ResourceMetaExtension : public ConfigurableExtensionImpl 9 | { 10 | public: 11 | static constexpr auto CustomizationApp = Red::ResourcePath("player_customization.app"); 12 | 13 | std::string_view GetName() override; 14 | void Configure() override; 15 | 16 | static bool InScope(Red::ResourcePath aScopePath, Red::ResourcePath aTargetPath); 17 | static const Core::Set& GetList(Red::ResourcePath aScopePath); 18 | 19 | static Core::Set ExpandList(const Core::Set& aList); 20 | static Core::Set ExpandList(const Core::Set& aList); 21 | static Core::Map ExpandList(const Core::Map& aList); 22 | 23 | static const ResourceFix& GetFix(Red::ResourcePath aTargetPath); 24 | 25 | private: 26 | inline static Core::Map> s_scopes; 27 | inline static Core::Map s_fixes; 28 | inline static Core::Map s_paths; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/App/Extensions/ResourcePatch/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct ResourcePatch 8 | { 9 | [[nodiscard]] bool Modifies(Red::CName aProp) const 10 | { 11 | return props.empty() || props.contains(aProp); 12 | } 13 | 14 | [[nodiscard]] bool Modifies(Red::CName aProp, bool aOverwrite) const 15 | { 16 | return (!aOverwrite && props.empty()) || props.contains(aProp); 17 | } 18 | 19 | Core::Set props; 20 | Core::Set includes; 21 | Core::Set excludes; 22 | }; 23 | 24 | struct ResourcePatchConfig : ExtensionConfig 25 | { 26 | using ExtensionConfig::ExtensionConfig; 27 | 28 | bool IsDefined() override; 29 | void LoadYAML(const YAML::Node& aNode) override; 30 | 31 | Core::Map patches; 32 | Core::Map paths; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/App/Extensions/Transmog/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "Red/AppearanceChanger.hpp" 5 | 6 | namespace App 7 | { 8 | class TransmogExtension : public Extension 9 | { 10 | public: 11 | bool Load() override; 12 | bool Unload() override; 13 | std::string_view GetName() override; 14 | 15 | private: 16 | static bool OnLoadTemplate(Red::ItemFactoryAppearanceChangeRequest* aRequest); 17 | static void* OnSelectAppearance(Red::CName* aOut, 18 | const Red::Handle& aItemRecord, 19 | const Red::ItemID& aItemID, 20 | const Red::Handle& aAppearanceResource, 21 | uint64_t a5, 22 | Red::CName aAppearanceName); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/App/Extensions/WorldStreaming/Config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | 5 | namespace App 6 | { 7 | struct WorldSubNodeMutation 8 | { 9 | int64_t subNodeIndex; 10 | 11 | bool modifyPosition; 12 | Red::Vector4 position; 13 | 14 | bool modifyOrientation; 15 | Red::Quaternion orientation; 16 | 17 | bool modifyScale; 18 | Red::Vector3 scale; 19 | }; 20 | 21 | struct WorldNodeMutation 22 | { 23 | int64_t nodeIndex; 24 | Red::CName nodeType; 25 | 26 | bool modifyPosition; 27 | Red::Vector4 position; 28 | 29 | bool modifyOrientation; 30 | Red::Quaternion orientation; 31 | 32 | bool modifyScale; 33 | Red::Vector3 scale; 34 | 35 | bool modifyResource; 36 | Red::ResourcePath resourcePath; 37 | 38 | bool modifyAppearance; 39 | Red::CName appearanceName; 40 | 41 | bool modifyRecordID; 42 | Red::TweakDBID recordID; 43 | 44 | bool modifyProxyNodes; 45 | int32_t nbNodesUnderProxyDiff; 46 | 47 | int64_t expectedSubNodes; 48 | Core::Vector subNodeMutations; 49 | }; 50 | 51 | struct WorldNodeDeletion 52 | { 53 | int64_t nodeIndex; 54 | Red::CName nodeType; 55 | int64_t expectedSubNodes; 56 | Core::Vector subNodeDeletions; 57 | }; 58 | 59 | struct WorldSectorMod 60 | { 61 | std::string mod; 62 | std::string path; 63 | int64_t expectedNodes; 64 | Core::Vector nodeDeletions; 65 | Core::Vector nodeMutations; 66 | bool autoUpdateNodesUnderProxies; 67 | }; 68 | 69 | struct WorldStreamingConfig : ExtensionConfig 70 | { 71 | using ExtensionConfig::ExtensionConfig; 72 | 73 | bool IsDefined() override; 74 | void LoadYAML(const YAML::Node& aNode) override; 75 | bool ParseResource(const YAML::Node& aNode, Red::ResourcePath& aValue, bool& aFlag); 76 | bool ParseName(const YAML::Node& aNode, Red::CName& aValue, bool& aFlag); 77 | bool ParseRecordID(const YAML::Node& aNode, Red::TweakDBID& aValue, bool& aFlag); 78 | bool ParseSubDeletions(const YAML::Node& aDeletionsNode, const YAML::Node& aCountNode, 79 | Core::Vector& aDeletions, int64_t& aExpectedCount); 80 | bool ParseSubMutations(const YAML::Node& aMutationsNode, const YAML::Node& aCountNode, 81 | Core::Vector& aMutations, int64_t& aExpectedCount); 82 | 83 | Core::Vector blocks; 84 | Core::Vector sectors; 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /src/App/Extensions/WorldStreaming/Extension.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "App/Extensions/ExtensionBase.hpp" 4 | #include "App/Extensions/WorldStreaming/Config.hpp" 5 | #include "Red/StreamingSector.hpp" 6 | #include "Red/StreamingWorld.hpp" 7 | 8 | namespace App 9 | { 10 | using StreamingBlockRef = Red::ResourceReference; 11 | 12 | class WorldStreamingExtension : public ConfigurableExtensionImpl 13 | { 14 | public: 15 | std::string_view GetName() override; 16 | bool Load() override; 17 | bool Unload() override; 18 | void Configure() override; 19 | 20 | private: 21 | void OnWorldSerialize(Red::world::StreamingWorld* aWorld, Red::BaseStream* aStream); 22 | static void OnSectorPostLoad(Red::world::StreamingSector* aSector, uint64_t); 23 | static void OnRegisterSpots(Red::AIWorkspotManager* aManager, 24 | const Red::DynArray& aNewSpots); 25 | 26 | static bool PatchSector(Red::world::StreamingSector* aSector, const WorldSectorMod& aSectorMod); 27 | 28 | inline static Core::Map> s_sectors; 29 | inline static Red::SharedSpinLock s_sectorsLock; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/App/Facade.cpp: -------------------------------------------------------------------------------- 1 | #include "Facade.hpp" 2 | #include "App/Archives/ArchiveService.hpp" 3 | #include "App/Extensions/ExtensionService.hpp" 4 | #include "App/Extensions/Garment/Extension.hpp" 5 | #include "App/Extensions/PuppetState/Extension.hpp" 6 | #include "Core/Facades/Container.hpp" 7 | 8 | bool App::Facade::RegisterDir(Red::CString& aPath) 9 | { 10 | return Core::Resolve()->RegisterDirectory(aPath.c_str()); 11 | } 12 | 13 | bool App::Facade::RegisterArchive(Red::CString& aPath) 14 | { 15 | return Core::Resolve()->RegisterArchive(aPath.c_str()); 16 | } 17 | 18 | Red::CName App::Facade::GetBodyType(const Red::WeakHandle& aPuppet) 19 | { 20 | return PuppetStateExtension::GetBodyType(aPuppet); 21 | } 22 | 23 | void App::Facade::EnableGarmentOffsets() 24 | { 25 | GarmentExtension::EnableGarmentOffsets(); 26 | } 27 | 28 | void App::Facade::DisableGarmentOffsets() 29 | { 30 | GarmentExtension::DisableGarmentOffsets(); 31 | } 32 | 33 | void App::Facade::Reload() 34 | { 35 | Core::Resolve()->Configure(); 36 | } 37 | 38 | bool App::Facade::Require(Red::CString& aVersion) 39 | { 40 | const auto requirement = semver::from_string_noexcept(aVersion.c_str()); 41 | return requirement.has_value() && Project::Version >= requirement.value(); 42 | } 43 | 44 | Red::CString App::Facade::GetVersion() 45 | { 46 | return Project::Version.to_string().c_str(); 47 | } 48 | -------------------------------------------------------------------------------- /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 RegisterArchive(Red::CString& aPath); 12 | static Red::CName GetBodyType(const Red::WeakHandle& aPuppet); 13 | static void EnableGarmentOffsets(); 14 | static void DisableGarmentOffsets(); 15 | static void Reload(); 16 | static bool Require(Red::CString& aVersion); 17 | static Red::CString GetVersion(); 18 | 19 | RTTI_IMPL_TYPEINFO(Facade); 20 | }; 21 | } 22 | 23 | RTTI_DEFINE_CLASS(App::Facade, App::Project::Name, { 24 | RTTI_ABSTRACT(); 25 | RTTI_METHOD(RegisterDir); 26 | RTTI_METHOD(RegisterArchive); 27 | RTTI_METHOD(GetBodyType); 28 | RTTI_METHOD(EnableGarmentOffsets); 29 | RTTI_METHOD(DisableGarmentOffsets); 30 | RTTI_METHOD(Reload); 31 | RTTI_METHOD(Require); 32 | RTTI_METHOD(GetVersion, "Version"); 33 | }) 34 | -------------------------------------------------------------------------------- /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/Patches/EntitySpawnerPatch.cpp: -------------------------------------------------------------------------------- 1 | #include "EntitySpawnerPatch.hpp" 2 | #include "Red/TweakDB.hpp" 3 | 4 | namespace 5 | { 6 | constexpr auto DefaultAppearanceName = Red::CName("default"); 7 | } 8 | 9 | void App::EntitySpawnerPatch::OnBootstrap() 10 | { 11 | HookBefore(&OnSpawnEntity).OrThrow(); 12 | } 13 | 14 | void App::EntitySpawnerPatch::OnSpawnEntity(void* aSpawner, void* aOut, Red::EntitySpawnerRequest* aRequest, 15 | Red::ResourcePath aTemplate) 16 | { 17 | if (aRequest->recordID && (!aRequest->appearanceName || aRequest->appearanceName == DefaultAppearanceName)) 18 | { 19 | auto recordAppearanceName = Red::GetFlatValue({aRequest->recordID, ".appearanceName"}); 20 | if (recordAppearanceName) 21 | { 22 | aRequest->appearanceName = recordAppearanceName; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/App/Patches/EntitySpawnerPatch.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/EntitySpawner.hpp" 7 | 8 | namespace App 9 | { 10 | class EntitySpawnerPatch 11 | : public Core::Feature 12 | , public Core::HookingAgent 13 | , public Core::LoggingAgent 14 | { 15 | protected: 16 | void OnBootstrap() override; 17 | 18 | static void OnSpawnEntity(void* aSpawner, void* aOut, Red::EntitySpawnerRequest* aRequest, 19 | Red::ResourcePath aTemplate); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/App/Patches/WorldWidgetLimitPatch.cpp: -------------------------------------------------------------------------------- 1 | #include "WorldWidgetLimitPatch.hpp" 2 | 3 | void App::WorldWidgetLimitPatch::OnBootstrap() 4 | { 5 | auto& code = Core::RawPtr::Ref(); 6 | uint8_t op[] = {0x83, 0xFB, 0x0A, 0x73, 0x16}; 7 | bool success = false; 8 | 9 | DWORD oldProtect = 0; 10 | VirtualProtect(code, sizeof(code), PAGE_EXECUTE_WRITECOPY, &oldProtect); 11 | 12 | auto it = std::search(std::begin(code), std::end(code), std::begin(op), std::end(op)); 13 | if (it != std::end(code)) 14 | { 15 | *(it + 3) = 0x90; 16 | *(it + 4) = 0x90; 17 | success = true; 18 | } 19 | 20 | VirtualProtect(code, sizeof(code), oldProtect, nullptr); 21 | 22 | if (success) 23 | { 24 | LogInfo("WorldWidgetComponent limit patch is successfully applied."); 25 | } 26 | else 27 | { 28 | LogWarning("WorldWidgetComponent limit patch is not applied, expected pattern was not found."); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/App/Patches/WorldWidgetLimitPatch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Foundation/Feature.hpp" 4 | #include "Core/Logging/LoggingAgent.hpp" 5 | 6 | namespace App 7 | { 8 | class WorldWidgetLimitPatch 9 | : public Core::Feature 10 | , public Core::LoggingAgent 11 | { 12 | protected: 13 | void OnBootstrap() override; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/App/Shared/ResourcePathRegistry.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/ResourcePath.hpp" 7 | 8 | namespace App 9 | { 10 | class ResourcePathRegistry 11 | : public Core::Feature 12 | , public Core::LoggingAgent 13 | , public Core::HookingAgent 14 | { 15 | public: 16 | ResourcePathRegistry(const std::filesystem::path& aPreloadPath = {}); 17 | 18 | [[nodiscard]] std::string ResolvePath(Red::ResourcePath aPath); 19 | [[nodiscard]] std::string ResolvePathOrHash(Red::ResourcePath aPath); 20 | 21 | Red::ResourcePath RegisterPath(const std::string& aPathStr); 22 | void RegisterPath(Red::ResourcePath aPath, const std::string& aPathStr); 23 | 24 | protected: 25 | struct SharedInstance 26 | { 27 | Red::SharedSpinLock m_lock; 28 | Core::Map m_map; 29 | bool m_preloaded{false}; 30 | bool m_initialized{false}; 31 | }; 32 | 33 | void OnBootstrap() override; 34 | static void OnCreatePath(Red::ResourcePath* aPath, Red::StringView* aPathStr); 35 | 36 | inline static SharedInstance* s_instance; 37 | inline static std::filesystem::path s_preloadPath; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/App/Utils/Num.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 | template 76 | requires std::is_integral_v 77 | inline std::string ToHex(T aValue) 78 | { 79 | constexpr auto max = 2 * sizeof(uint64_t); 80 | constexpr auto len = 2 * sizeof(aValue); 81 | static char buffer[max + 1]; 82 | snprintf(buffer, max + 1, "%016X", aValue); 83 | return {buffer + (max - len), len}; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/App/Utils/Registers.asm: -------------------------------------------------------------------------------- 1 | .code 2 | 3 | get_r8 proc 4 | mov rax, r8 5 | ret 6 | get_r8 endp 7 | 8 | set_r8 proc 9 | mov r8, rcx 10 | ret 11 | set_r8 endp 12 | 13 | get_r9 proc 14 | mov rax, r9 15 | ret 16 | get_r9 endp 17 | 18 | set_r9 proc 19 | mov r9, rcx 20 | ret 21 | set_r9 endp 22 | 23 | end 24 | -------------------------------------------------------------------------------- /src/App/Utils/Registers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" 4 | { 5 | uint64_t get_r8(); 6 | uint64_t get_r9(); 7 | void set_r8(uint64_t); 8 | void set_r9(uint64_t); 9 | } 10 | -------------------------------------------------------------------------------- /src/App/Utils/Str.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace App::Str 4 | { 5 | inline std::wstring Widen(const std::string& aStr) 6 | { 7 | if (aStr.empty()) 8 | return L""; 9 | 10 | auto length = MultiByteToWideChar(CP_UTF8, 0, aStr.data(), static_cast(aStr.size()), nullptr, 0); 11 | 12 | if (length <= 0) 13 | return L""; 14 | 15 | std::wstring result(length, 0); 16 | length = MultiByteToWideChar(CP_UTF8, 0, aStr.data(), static_cast(aStr.size()), result.data(), 17 | static_cast(result.size())); 18 | 19 | if (length <= 0) 20 | return L""; 21 | 22 | return result; 23 | } 24 | 25 | inline std::string SnakeCase(const std::string& aStr) 26 | { 27 | std::string result; 28 | bool split = false; 29 | 30 | for (char c : aStr) 31 | { 32 | if (isupper(c)) 33 | { 34 | if (split) 35 | { 36 | result += '_'; 37 | split = false; 38 | } 39 | 40 | result += static_cast(std::tolower(c)); 41 | } 42 | else 43 | { 44 | result += c; 45 | split = (c != '_'); 46 | } 47 | } 48 | 49 | return result; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Red/AnimatedComponent.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::AnimatedComponent 4 | { 5 | constexpr auto InitializeAnimations = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::AnimatedComponent_InitializeAnimations, 7 | /* type = */ void (*)(Red::entAnimatedComponent* aComponent)>(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Red/AppearanceResource.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/EntityTemplate.hpp" 4 | 5 | namespace Red 6 | { 7 | using AppearanceResource = appearance::AppearanceResource; 8 | using AppearanceDefinition = appearance::AppearanceDefinition; 9 | } 10 | 11 | namespace Raw::AppearanceDefinition 12 | { 13 | using Mutex = Core::OffsetPtr<0xE6, Red::SharedSpinLock>; 14 | using CompilationFlag = Core::OffsetPtr<0xE7, bool>; 15 | using CompilationJob = Core::OffsetPtr<0xE8, Red::JobHandle>; 16 | 17 | constexpr auto ExtractPartComponents = Core::RawFunc< 18 | /* addr = */ Red::AddressLib::AppearanceDefinition_ExtractPartComponents, 19 | /* type = */ void* (*)(Red::DynArray>& aOut, 20 | const Red::SharedPtr>& aPartToken)>(); 21 | } 22 | 23 | namespace Raw::AppearanceResource 24 | { 25 | using Mutex = Core::OffsetPtr<0xF0, Red::SharedSpinLock>; 26 | 27 | constexpr auto OnLoad = Core::RawFunc< 28 | /* addr = */ Red::AddressLib::AppearanceResource_OnLoad, 29 | /* type = */ void (*)(Red::AppearanceResource* aResource)>(); 30 | 31 | constexpr auto FindAppearance = Core::RawFunc< 32 | /* addr = */ Red::AddressLib::AppearanceResource_FindAppearanceDefinition, 33 | /* type = */ uintptr_t (*)(Red::AppearanceResource* aResource, 34 | Red::Handle* aDefinition, 35 | Red::CName aName, 36 | uint32_t, 37 | uint8_t)>(); 38 | } 39 | -------------------------------------------------------------------------------- /src/Red/AttachmentSlots.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::AttachmentSlots 4 | { 5 | constexpr auto InitializeSlots = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::AttachmentSlots_InitializeSlots, 7 | /* type = */ void (*)(Red::game::AttachmentSlots*, Red::DynArray&)>(); 8 | 9 | constexpr auto IsSlotEmpty = Core::RawFunc< 10 | /* addr = */ Red::AddressLib::AttachmentSlots_IsSlotEmpty, 11 | /* type = */ bool (*)(Red::game::AttachmentSlots*, Red::TweakDBID)>(); 12 | 13 | constexpr auto IsSlotSpawning = Core::RawFunc< 14 | /* addr = */ Red::AddressLib::AttachmentSlots_IsSlotSpawning, 15 | /* type = */ bool (*)(Red::game::AttachmentSlots*, Red::TweakDBID)>(); 16 | } 17 | -------------------------------------------------------------------------------- /src/Red/Buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | struct BufferReader 6 | { 7 | virtual ~BufferReader() = 0; 8 | virtual uint32_t GetSize() = 0; 9 | virtual void sub_10(uint64_t a1) = 0; 10 | virtual void sub_18(uint64_t a1) = 0; 11 | virtual uint8_t GetType() = 0; 12 | 13 | inline static void Clone(void*& aDst, void* aSrc) 14 | { 15 | constexpr auto MakeBufferReaderType0 = Core::RawFunc< 16 | /* addr = */ Red::AddressLib::BufferReader_MakeType0, 17 | /* type = */ void (*)(void** aOut, uintptr_t a2)>(); 18 | 19 | constexpr auto MakeBufferReaderType1 = Core::RawFunc< 20 | /* addr = */ Red::AddressLib::BufferReader_MakeType1, 21 | /* type = */ void (*)(void** aOut, uintptr_t a2)>(); 22 | 23 | switch (reinterpret_cast(aSrc)->GetType()) 24 | { 25 | case 0: 26 | { 27 | MakeBufferReaderType0(&aDst, reinterpret_cast(aSrc) + 8); 28 | break; 29 | } 30 | case 1: 31 | { 32 | MakeBufferReaderType1(&aDst, reinterpret_cast(aSrc) + 8); 33 | break; 34 | } 35 | } 36 | } 37 | }; 38 | } 39 | 40 | namespace Red 41 | { 42 | inline void CopyBuffer(DataBuffer& aDst, const DataBuffer& aSrc) 43 | { 44 | if (aSrc.buffer.data) 45 | { 46 | aDst.buffer.Initialize(aSrc.buffer.GetAllocator(), aSrc.buffer.size, aSrc.buffer.alignment); 47 | std::memcpy(aDst.buffer.data, aSrc.buffer.data, aSrc.buffer.size); 48 | } 49 | } 50 | 51 | inline void CopyBuffer(DeferredDataBuffer& aDst, const DeferredDataBuffer& aSrc) 52 | { 53 | if (aSrc.unk30) 54 | { 55 | Red::BufferReader::Clone(aDst.unk30, aSrc.unk30); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Red/Common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | template 6 | struct Range 7 | { 8 | constexpr operator bool() const noexcept 9 | { 10 | return beginPtr != endPtr; 11 | } 12 | 13 | [[nodiscard]] inline T* begin() const 14 | { 15 | return beginPtr; 16 | } 17 | 18 | [[nodiscard]] inline T* end() const 19 | { 20 | return endPtr; 21 | } 22 | 23 | [[nodiscard]] auto GetSize() const 24 | { 25 | return endPtr - beginPtr; 26 | } 27 | 28 | [[nodiscard]] bool IsEmpty() const 29 | { 30 | return !beginPtr; 31 | } 32 | 33 | T* beginPtr{}; // 00 34 | T* endPtr{}; // 08 35 | }; 36 | RED4EXT_ASSERT_SIZE(Range, 0x10); 37 | RED4EXT_ASSERT_OFFSET(Range, beginPtr, 0x0); 38 | RED4EXT_ASSERT_OFFSET(Range, endPtr, 0x8); 39 | } 40 | -------------------------------------------------------------------------------- /src/Red/CommunitySystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::AISpotPersistentDataArray 4 | { 5 | constexpr auto Reserve = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::AISpotPersistentDataArray_Reserve, 7 | /* type = */ void (*)(Red::SortedArray* aArray, uint32_t aCapacity)>(); 8 | } 9 | 10 | namespace Raw::AIWorkspotManager 11 | { 12 | using Spots = Core::OffsetPtr<0x48, Red::SortedArray>; 13 | 14 | constexpr auto RegisterSpots = Core::RawFunc< 15 | /* addr = */ Red::AddressLib::AIWorkspotManager_RegisterSpots, 16 | /* type = */ void (*)(Red::AIWorkspotManager* aManager, const Red::DynArray& aSpots)>(); 17 | } 18 | -------------------------------------------------------------------------------- /src/Red/EntitySpawner.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | struct EntitySpawnerRequest 6 | { 7 | uint8_t unk00[0xC0]; // 00 8 | CName appearanceName; // C0 9 | uint8_t unkC8[0xE0 - 0xC8]; // C8 10 | TweakDBID recordID; // E0 11 | }; 12 | RED4EXT_ASSERT_OFFSET(EntitySpawnerRequest, appearanceName, 0xC0); 13 | RED4EXT_ASSERT_OFFSET(EntitySpawnerRequest, recordID, 0xE0); 14 | } 15 | 16 | namespace Raw::EntitySpawner 17 | { 18 | constexpr auto SpawnFromTemplate = Core::RawFunc< 19 | /* addr = */ Red::AddressLib::EntitySpawner_SpawnFromTemplate, 20 | /* type = */ void* (*)(void* aSpawner, void* aOut, Red::EntitySpawnerRequest* aRequest, 21 | Red::ResourcePath aTemplate)>(); 22 | } 23 | -------------------------------------------------------------------------------- /src/Red/EntityTemplate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | using EntityTemplate = ent::EntityTemplate; 6 | using TemplateAppearance = ent::TemplateAppearance; 7 | } 8 | 9 | namespace Raw::EntityTemplate 10 | { 11 | constexpr auto PostLoad = Core::RawFunc< 12 | /* addr = */ Red::AddressLib::EntityTemplate_OnLoad, 13 | /* type = */ void (*)(Red::EntityTemplate* aResource, void* a2)>(); 14 | 15 | constexpr auto FindAppearance = Core::RawFunc< 16 | /* addr = */ Red::AddressLib::EntityTemplate_FindAppearance, 17 | /* type = */ Red::TemplateAppearance* (*)(Red::EntityTemplate* aResource, Red::CName aName)>(); 18 | } 19 | -------------------------------------------------------------------------------- /src/Red/FactoryIndex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::FactoryIndex 4 | { 5 | constexpr auto LoadFactoryAsync = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::FactoryIndex_LoadFactoryAsync, 7 | /* type = */ void (*)(uintptr_t aIndex, Red::ResourcePath aPath, uintptr_t aContext)>(); 8 | 9 | constexpr auto ResolveResource = Core::RawFunc< 10 | /* addr = */ Red::AddressLib::FactoryIndex_ResolveResource, 11 | /* type = */ void (*)(uintptr_t aIndex, Red::ResourcePath& aPath, Red::CName aName)>(); 12 | } 13 | -------------------------------------------------------------------------------- /src/Red/GameApplication.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::GameApplication 4 | { 5 | constexpr auto InitResourceDepot = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::GameApplication_InitResourceDepot, 7 | /* type = */ void (*)(uintptr_t aApp, uintptr_t aParams)>(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Red/GameEngine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::CBaseEngine 4 | { 5 | constexpr auto InitEngine = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::CBaseEngine_InitEngine, 7 | /* type = */ bool (*)(Red::CBaseEngine& aEngine, Red::CGameOptions& aOptions)>(); 8 | 9 | constexpr auto LoadGatheredResources = Core::RawFunc< 10 | /* addr = */ Red::AddressLib::CBaseEngine_LoadGatheredResources, 11 | /* type = */ bool (*)(Red::CBaseEngine& aEngine)>(); 12 | } 13 | -------------------------------------------------------------------------------- /src/Red/ImpostorComponent.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::ImpostorComponent 4 | { 5 | constexpr auto OnAttach = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::ImpostorComponent_OnAttach, 7 | /* type = */ void (*)(Red::gameImpostorComponent* aComponent, uintptr_t a2)>(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Red/ItemObject.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::ItemObject 4 | { 5 | using ItemID = Core::OffsetPtr<0x288, Red::ItemID>; 6 | 7 | constexpr auto GetAppearanceName = Core::RawVFunc< 8 | /* offset = */ 0x280, 9 | /* type = */ Red::CName* (Red::ItemObject::*)(Red::CName& aOut)>(); 10 | } 11 | -------------------------------------------------------------------------------- /src/Red/JobHandle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::JobHandle 4 | { 5 | constexpr auto Wait = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::JobHandle_Wait, 7 | /* type = */ bool (*)(Red::JobHandle& aJob)>(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Red/JournalManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | using JournalEntryHash = uint32_t; 6 | } 7 | 8 | namespace Raw::JournalManager 9 | { 10 | constexpr auto LoadJournal = Core::RawFunc< 11 | /* addr = */ Red::AddressLib::JournalManager_LoadJournal, 12 | /* type = */ void (*)(Red::IScriptable* aManager, Red::JobHandle& aJobHandle)>(); 13 | 14 | constexpr auto GetTrackedQuest = Core::RawVFunc< 15 | /* addr = */ 0x1F8, 16 | /* type = */ void (Red::gameIJournalManager::*)(Red::Handle& aEntry)>(); 17 | 18 | constexpr auto GetTrackedPointOfInterest = Core::RawVFunc< 19 | /* addr = */ 0x208, 20 | /* type = */ void (Red::gameIJournalManager::*)(Red::Handle& aEntry)>(); 21 | 22 | constexpr auto GetEntryByHash = Core::RawVFunc< 23 | /* addr = */ 0x220, 24 | /* type = */ void* (Red::gameIJournalManager::*)(Red::Handle& aOut, 25 | Red::JournalEntryHash aEntryHash)>(); 26 | 27 | constexpr auto GetEntryHash = Core::RawVFunc< 28 | /* addr = */ 0x230, 29 | /* type = */ Red::JournalEntryHash (Red::gameIJournalManager::*)(Red::Handle& aEntry)>(); 30 | 31 | // constexpr auto ChangeEntryState = Core::RawVFunc< 32 | // /* addr = */ 0x268, 33 | // /* type = */ bool (Red::gameIJournalManager::*)(Red::Handle& aEntry, 34 | // Red::gameJournalEntryState aEntryState, 35 | // Red::gameJournalNotifyOption aNotifyOption, 36 | // uint32_t a4)>(); 37 | 38 | constexpr auto TrackQuest = Core::RawFunc< 39 | /* addr = */ Red::AddressLib::JournalManager_TrackQuest, 40 | /* type = */ void (Red::gameIJournalManager::*)(Red::Handle& aEntry)>(); 41 | 42 | constexpr auto TrackQuestByPath = Core::RawVFunc< 43 | /* addr = */ 0x298, 44 | /* type = */ void (Red::gameIJournalManager::*)(void* aPath)>(); 45 | 46 | constexpr auto TrackPointOfInterest = Core::RawVFunc< 47 | /* addr = */ 0x2A0, 48 | /* type = */ void (Red::gameIJournalManager::*)(Red::Handle& aEntry)>(); 49 | 50 | // constexpr auto GetEntryState = Core::RawVFunc< 51 | // /* addr = */ 0x2B0, 52 | // /* type = */ Red::gameJournalEntryState (Red::gameIJournalManager::*)(Red::Handle& aEntry)>(); 53 | // 54 | // constexpr auto GetEntryTimestamp = Core::RawVFunc< 55 | // /* addr = */ 0x2C8, 56 | // /* type = */ uint32_t (Red::gameIJournalManager::*)(Red::Handle& aEntry)>(); 57 | } 58 | -------------------------------------------------------------------------------- /src/Red/JournalTree.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::JournalTree 4 | { 5 | constexpr auto ProcessJournalIndex = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::JournalTree_ProcessJournalIndex, 7 | /* type = */ void (*)(uintptr_t a1, Red::JobGroup& aJobGroup)>(); 8 | } 9 | 10 | namespace Raw::JournalRootFolderEntry 11 | { 12 | constexpr auto Initialize = Core::RawFunc< 13 | /* addr = */ Red::AddressLib::JournalRootFolderEntry_Initialize, 14 | /* type = */ void (*)(Red::game::JournalRootFolderEntry* aRoot, 15 | uintptr_t a2, uintptr_t a3, Red::JobQueue& aJobQueue)>(); 16 | } 17 | -------------------------------------------------------------------------------- /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 | 9 | namespace Raw::Localization 10 | { 11 | using VoiceOverTokens = Core::OffsetPtr<0x8, Red::HashMap>>>>; 12 | using LipMapToken = Core::OffsetPtr<0x188, Red::SharedPtr>>; 13 | 14 | constexpr auto LoadTexts = Core::RawFunc< 15 | /* addr = */ Red::AddressLib::Localization_LoadOnScreens, 16 | /* type = */ uint64_t (*)(Red::Handle& aOut, 17 | Red::ResourcePath aPath)>(); 18 | 19 | constexpr auto LoadSubtitles = Core::RawFunc< 20 | /* addr = */ Red::AddressLib::Localization_LoadSubtitles, 21 | /* type = */ uint64_t (*)(Red::Handle& aOut, 22 | Red::ResourcePath aPath)>(); 23 | 24 | constexpr auto LoadVoiceOvers = Core::RawFunc< 25 | /* addr = */ Red::AddressLib::Localization_LoadVoiceOvers, 26 | /* type = */ void (*)(void* aContext, uint64_t a2)>(); 27 | 28 | constexpr auto LoadLipsyncs = Core::RawFunc< 29 | /* addr = */ Red::AddressLib::Localization_LoadLipsyncs, 30 | /* type = */ void (*)(void* aContext, uint8_t a2)>(); 31 | } 32 | -------------------------------------------------------------------------------- /src/Red/MappinSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::MappinSystem 4 | { 5 | using CookedMappinResource = Core::OffsetPtr<0x58, Red::Handle>; 6 | using CookedPoiResource = Core::OffsetPtr<0x68, Red::Handle>; 7 | 8 | constexpr auto GetMappinData = Core::RawFunc< 9 | /* addr = */ Red::AddressLib::MappinSystem_GetMappinData, 10 | /* type = */ void* (*)(void* aSystem, uint32_t aHash)>(); 11 | 12 | constexpr auto GetPoiData = Core::RawFunc< 13 | /* addr = */ Red::AddressLib::MappinSystem_GetPoiData, 14 | /* type = */ void* (*)(void* aSystem, uint32_t aHash)>(); 15 | 16 | constexpr auto OnStreamingWorldLoaded = Core::RawFunc< 17 | /* addr = */ Red::AddressLib::MappinSystem_OnStreamingWorldLoaded, 18 | /* type = */ void (*)(void* aSystem, Red::worldRuntimeScene*)>(); 19 | } 20 | -------------------------------------------------------------------------------- /src/Red/Mesh.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | struct MeshMaterialsToken 6 | { 7 | JobHandle job; 8 | SharedPtr>> materials; 9 | }; 10 | } 11 | 12 | namespace Raw::CMesh 13 | { 14 | using MaterialLock = Core::OffsetPtr<0x218, Red::SharedSpinLock>; 15 | 16 | constexpr auto PostLoad = Core::RawFunc< 17 | /* addr = */ Red::AddressLib::CMesh_PostLoad, 18 | /* type = */ void (*)(Red::CMesh* aResource, Red::PostLoadParams* a2)>(); 19 | 20 | constexpr auto GetAppearance = Core::RawFunc< 21 | /* addr = */ Red::AddressLib::CMesh_GetAppearance, 22 | /* type = */ Red::Handle& (*)(Red::CMesh* aMesh, Red::CName aAppearance)>(); 23 | 24 | constexpr auto FindAppearance = Core::RawFunc< 25 | /* addr = */ Red::AddressLib::CMesh_FindAppearance, 26 | /* type = */ Red::Handle& (*)(Red::CMesh* aMesh, Red::CName aAppearance)>(); 27 | 28 | constexpr auto LoadMaterialsAsync = Core::RawFunc< 29 | /* addr = */ Red::AddressLib::CMesh_LoadMaterialsAsync, 30 | /* type = */ void* (*)(Red::CMesh* aMesh, 31 | Red::MeshMaterialsToken& aOut, 32 | const Red::DynArray& aMaterialNames, 33 | uint8_t a4)>(); 34 | 35 | constexpr auto AddStubAppearance = Core::RawFunc< 36 | /* addr = */ Red::AddressLib::CMesh_AddStubAppearance, 37 | /* type = */ void (*)(Red::CMesh* aMesh)>(); 38 | 39 | constexpr auto ShouldPreloadAppearances = Core::RawFunc< 40 | /* addr = */ Red::AddressLib::CMesh_ShouldPreloadAppearances, 41 | /* type = */ bool (*)(Red::CMesh* aMesh)>(); 42 | } 43 | 44 | namespace Raw::MeshMaterialBuffer 45 | { 46 | constexpr auto LoadMaterialAsync = Core::RawFunc< 47 | /* addr = */ Red::AddressLib::MeshMaterialBuffer_LoadMaterialAsync, 48 | /* type = */ void* (*)(Red::meshMeshMaterialBuffer* aBuffer, 49 | Red::SharedPtr>& aOut, 50 | const Red::Handle& aMesh, 51 | uint16_t aMaterialIndex, 52 | uint64_t a5, 53 | uint8_t a6)>(); 54 | } 55 | 56 | namespace Raw::MeshAppearance 57 | { 58 | using Owner = Core::OffsetPtr<0x50, Red::CMesh*>; 59 | 60 | constexpr auto LoadMaterialSetupAsync = Core::RawFunc< 61 | /* addr = */ Red::AddressLib::MeshAppearance_LoadMaterialSetupAsync, 62 | /* type = */ void (*)(Red::mesh::MeshAppearance& aAppearance, Red::Handle& aOut, 63 | uint8_t a3)>(); 64 | } 65 | 66 | namespace Raw::MeshComponent 67 | { 68 | constexpr auto LoadResource = Core::RawVFunc< 69 | /* offset = */ 0x260, 70 | /* type = */ uint64_t(Red::IComponent::*)(Red::JobQueue& aQueue)>(); 71 | 72 | constexpr auto RefreshAppearance = Core::RawVFunc< 73 | /* offset = */ 0x280, 74 | /* type = */ void(Red::IComponent::*)()>(); 75 | } 76 | -------------------------------------------------------------------------------- /src/Red/MorphTarget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::MorphTargetMesh 4 | { 5 | constexpr auto PostLoad = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::MorphTargetMesh_PostLoad, 7 | /* type = */ void (*)(Red::MorphTargetMesh* aResource, Red::PostLoadParams* a2)>(); 8 | } 9 | 10 | namespace Raw::MorphTargetManager 11 | { 12 | constexpr auto ApplyMorphTarget = Core::RawFunc< 13 | /* addr = */ Red::AddressLib::MorphTargetManager_ApplyMorphTarget, 14 | /* type = */ void (*)(Red::IComponent* aManager, Red::CName aTarget, Red::CName aRegion, float aValue, bool a5)>(); 15 | } 16 | -------------------------------------------------------------------------------- /src/Red/PersistencySystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::PersistencySystem 4 | { 5 | constexpr auto SetPersistentStateData = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::PersistencySystem_SetPersistentStateData, 7 | /* type = */ void (*)(uint64_t a1, Red::DataBuffer& aData, uint64_t a3, uint32_t a4)>(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Red/QuestLoader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::QuestLoader 4 | { 5 | constexpr auto ProcessPhaseResource = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::QuestLoader_ProcessPhaseResource, 7 | /* type = */ void (*)(void* aLoader, Red::ResourcePath aPhasePath, 8 | Red::Handle& aPhaseResource)>(); 9 | } 10 | -------------------------------------------------------------------------------- /src/Red/QuestPhase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::QuestSocketDefinition 4 | { 5 | using OwnerNode = Core::OffsetPtr<0x48, Red::WeakHandle>; 6 | } 7 | -------------------------------------------------------------------------------- /src/Red/ResourceDepot.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::ResourceDepot 4 | { 5 | constexpr auto InitializeArchives = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::ResourceDepot_InitializeArchives, 7 | /* type = */ void (*)(Red::ResourceDepot* aDepot)>{}; 8 | 9 | constexpr auto LoadArchives = Core::RawFunc< 10 | /* addr = */ Red::AddressLib::ResourceDepot_LoadArchives, 11 | /* type = */ void (*)(Red::ResourceDepot* aDepot, 12 | Red::ArchiveGroup& aGroup, 13 | const Red::DynArray& aArchivePaths, 14 | Red::DynArray& aLoadedResourcePaths, 15 | bool aMemoryResident)>{}; 16 | 17 | constexpr auto RequestResource = Core::RawFunc< 18 | /* addr = */ Red::AddressLib::ResourceDepot_RequestResource, 19 | /* type = */ uintptr_t* (*)(Red::ResourceDepot* aDepot, 20 | const uintptr_t* aOutResourceHandle, 21 | Red::ResourcePath aPath, 22 | const int32_t* aArchiveHandle)>{}; 23 | 24 | constexpr auto CheckResource = Core::RawFunc< 25 | /* addr = */ Red::AddressLib::ResourceDepot_CheckResource, 26 | /* type = */ bool (*)(Red::ResourceDepot* aDepot, Red::ResourcePath aPath)>{}; 27 | } 28 | -------------------------------------------------------------------------------- /src/Red/ResourceLoader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | struct ResourceRequest 6 | { 7 | ResourcePath path; // 00 8 | uint64_t unk08{}; // 08 9 | uint64_t unk10{}; // 10 10 | int32_t unk18{-1}; // 18 11 | int32_t unk1C{}; // 1C 12 | uint64_t unk20{}; // 20 13 | }; 14 | RED4EXT_ASSERT_SIZE(ResourceRequest, 0x28); 15 | RED4EXT_ASSERT_OFFSET(ResourceRequest, path, 0x0); 16 | } 17 | 18 | namespace Raw::ResourceLoader 19 | { 20 | constexpr auto LoadAsync = Core::RawFunc< 21 | /* addr = */ Red::AddressLib::ResourceLoader_RequestResource, 22 | /* type = */ void (*)(Red::ResourceLoader* aLoader, 23 | Red::SharedPtr>& aToken, 24 | Red::ResourceRequest& aRequest)>(); 25 | 26 | constexpr auto OnUpdate = Core::RawFunc< 27 | /* addr = */ Red::AddressLib::ResourceLoader_OnUpdate, 28 | /* type = */ void (*)(Red::ResourceLoader* aLoader)>(); 29 | } 30 | -------------------------------------------------------------------------------- /src/Red/ResourcePath.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::ResourcePath 4 | { 5 | constexpr auto Create = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::ResourcePath_Create, 7 | /* type = */ Red::ResourcePath* (*)(Red::ResourcePath* aOut, Red::StringView* aPathStr)>(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Red/Serialization.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | struct ResourceSerializerRequest 6 | { 7 | uint64_t unk00; // 00 8 | uint64_t unk08; // 08 9 | ResourceLoader* loader; // 10 10 | uint64_t unk18; // 18 11 | uint64_t unk20; // 20 12 | ResourcePath path; // 28 13 | ResourcePath unk30; // 30 - Same as path 14 | uint8_t unk38; // 38 15 | uint64_t unk40; // 40 16 | uint64_t unk48; // 48 17 | uint64_t unk50; // 50 18 | uint8_t flags; // 58 19 | }; 20 | RED4EXT_ASSERT_SIZE(ResourceSerializerRequest, 0x60); 21 | RED4EXT_ASSERT_OFFSET(ResourceSerializerRequest, path, 0x28); 22 | RED4EXT_ASSERT_OFFSET(ResourceSerializerRequest, flags, 0x58); 23 | 24 | struct ResourceSerializerContext 25 | { 26 | uint8_t unk00[0x1D0]; // 00 27 | ResourceSerializerRequest request; // 1D0 28 | DynArray> serializables; // 230 29 | }; 30 | RED4EXT_ASSERT_OFFSET(ResourceSerializerContext, request, 0x1D0); 31 | RED4EXT_ASSERT_OFFSET(ResourceSerializerContext, serializables, 0x230); 32 | } 33 | 34 | namespace Raw::ResourceSerializer 35 | { 36 | constexpr auto Load = Core::RawFunc< 37 | /* addr = */ Red::AddressLib::ResourceSerializer_Load, 38 | /* type = */ void (*)(void* aSerializer, uint64_t a2, Red::ResourceSerializerRequest& aRequest, 39 | uint64_t a4, uint64_t a5)>(); 40 | 41 | constexpr auto Deserialize = Core::RawFunc< 42 | /* addr = */ Red::AddressLib::ResourceSerializer_Deserialize, 43 | /* type = */ void (*)(void* aSerializer, uint64_t a2, uint64_t a3, 44 | Red::JobHandle& aJob, Red::ResourceSerializerRequest& aRequest, uint64_t a6, 45 | Red::DynArray>& aResults, uint64_t a8)>(); 46 | 47 | constexpr auto PostLoad = Core::RawFunc< 48 | /* addr = */ Red::AddressLib::ResourceSerializer_PostLoad, 49 | /* type = */ void (*)(void* aSerializer, Red::ResourceSerializerRequest& aRequest, Red::JobQueue& aJobQueue)>(); 50 | 51 | constexpr auto OnDependenciesReady = Core::RawFunc< 52 | /* addr = */ Red::AddressLib::ResourceSerializer_OnDependenciesReady, 53 | /* type = */ void (*)(Red::ResourceSerializerContext* aContext)>(); 54 | 55 | constexpr auto OnResourceReady = Core::RawFunc< 56 | /* addr = */ Red::AddressLib::ResourceSerializer_OnResourceReady, 57 | /* type = */ void (*)(Red::ResourceSerializerContext* aContext)>(); 58 | } 59 | -------------------------------------------------------------------------------- /src/Red/SharedStorage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Red 4 | { 5 | template 6 | inline T* AcquireSharedInstance(Args&&... args) 7 | { 8 | constexpr CName SharedName = FNV1a64("!", N); 9 | 10 | auto rtti = Red::CRTTISystem::Get(); 11 | if (!rtti) 12 | return nullptr; 13 | 14 | auto* mutex = const_cast(reinterpret_cast(&rtti->typesLock)); 15 | auto* storage = reinterpret_cast*>(&rtti->unkF8); 16 | 17 | std::lock_guard _(*mutex); 18 | 19 | auto* entry = storage->Get(SharedName); 20 | if (entry) 21 | return *entry; 22 | 23 | static const auto s_instance = std::make_unique(std::forward(args)...); 24 | storage->Insert(SharedName, s_instance.get()); 25 | 26 | return s_instance.get(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Red/StreamingWorld.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::StreamingWorld 4 | { 5 | constexpr auto Serialize = Core::RawFunc< 6 | /* addr = */ Red::AddressLib::StreamingWorld_Serialize, 7 | /* type = */ void (*)(Red::worldStreamingWorld* aWorld, Red::BaseStream* aStream)>(); 8 | } 9 | 10 | namespace Raw::RuntimeSystemWorldStreaming 11 | { 12 | using StreamingWorld = Core::OffsetPtr<0x270, Red::Handle>; 13 | } 14 | -------------------------------------------------------------------------------- /src/Red/TPPRepresentationComponent.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Raw::TPPRepresentationComponent 4 | { 5 | using SlotListener = Core::OffsetPtr<0x148, Red::Handle>; 6 | 7 | constexpr auto OnAttach = Core::RawFunc< 8 | /* addr = */ Red::AddressLib::TPPRepresentationComponent_OnAttach, 9 | /* type = */ void (*)(Red::game::TPPRepresentationComponent* aComponent, uintptr_t a2)>(); 10 | 11 | constexpr auto RegisterAffectedItem = Core::RawFunc< 12 | /* addr = */ Red::AddressLib::TPPRepresentationComponent_RegisterAffectedItem, 13 | /* type = */ void (*)(Red::game::TPPRepresentationComponent* aComponent, 14 | Red::TweakDBID aItemID, 15 | const Red::Handle& aItemObject)>(); 16 | 17 | constexpr auto IsAffectedSlot = Core::RawFunc< 18 | /* addr = */ Red::AddressLib::TPPRepresentationComponent_IsAffectedSlot, 19 | /* type = */ bool (*)(Red::TweakDBID aSlotID)>(); 20 | } 21 | -------------------------------------------------------------------------------- /src/Red/TagList.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // namespace Red 4 | // { 5 | // using TagList = red::TagList; 6 | // } 7 | // 8 | // namespace Raw::TagList 9 | // { 10 | // constexpr auto Merge = Core::RawFunc< 11 | // /* addr = */ Red::AddressLib::TagList_MergeWith, 12 | // /* type = */ uint64_t (*)(Red::TagList& aDst, const Red::TagList& aSrc)>(); 13 | // } 14 | -------------------------------------------------------------------------------- /src/Red/Threads.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // namespace Raw 4 | // { 5 | // constexpr auto IsMainThread = Core::RawFunc< 6 | // /* addr = */ Red::AddressLib::IsMainThread, 7 | // /* type = */ bool (*)()>(); 8 | // } 9 | -------------------------------------------------------------------------------- /src/Red/TransactionSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // namespace Raw::TransactionSystem 4 | // { 5 | // constexpr auto IsSlotSpawning = Core::RawFunc< 6 | // /* addr = */ Red::AddressLib::TransactionSystem_IsSlotSpawning, 7 | // /* type = */ bool (*)(Red::IScriptable* aSystem, Red::IScriptable* aOwner, Red::TweakDBID aSlotID)>(); 8 | // } 9 | -------------------------------------------------------------------------------- /src/Red/VisualTagsPreset.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Red/TagList.hpp" 4 | 5 | namespace Red 6 | { 7 | using AppearanceNameVisualTagsPreset = game::AppearanceNameVisualTagsPreset; 8 | } 9 | 10 | namespace Raw::AppearanceNameVisualTagsPreset 11 | { 12 | constexpr auto GetVisualTags = Core::RawFunc< 13 | /* addr = */ Red::AddressLib::AppearanceNameVisualTagsPreset_GetVisualTags, 14 | /* type = */ void (*)(Red::AppearanceNameVisualTagsPreset* aPreset, // FIXME: might not be a preset anymore 15 | Red::ResourcePath aEntityPath, 16 | Red::CName aAppearanceName, 17 | Red::TagList& aOutTags)>(); 18 | } 19 | -------------------------------------------------------------------------------- /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/rtti.cpp: -------------------------------------------------------------------------------- 1 | #include "App/Facade.hpp" 2 | -------------------------------------------------------------------------------- /support/hints/ArchiveXL.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psiberx/cp2077-archive-xl/30d36f116cb1b6d246b56cbb002696e56f9d8f3d/support/hints/ArchiveXL.toml -------------------------------------------------------------------------------- /support/templates/factory.csv.json: -------------------------------------------------------------------------------- 1 | 2 | "Header": { 3 | "WKitJsonVersion": "0.0.3", 4 | "GameVersion": 1600, 5 | "DataType": "CR2W" 6 | }, 7 | "Data": { 8 | "Version": 195, 9 | "BuildVersion": 0, 10 | "RootChunk": { 11 | "$type": "C2dArray", 12 | "compiledData": [ 13 | [ 14 | "mymod_item", 15 | "mymod\\items\\clothing\\item.ent", 16 | "true" 17 | ] 18 | ], 19 | "compiledHeaders": [ 20 | "name", 21 | "path", 22 | "preload" 23 | ], 24 | "cookingPlatform": "PLATFORM_PC" 25 | }, 26 | "EmbeddedFiles": [] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /support/templates/localization.json: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "WKitJsonVersion": "0.0.3", 4 | "GameVersion": 1600, 5 | "DataType": "CR2W" 6 | }, 7 | "Data": { 8 | "Version": 195, 9 | "BuildVersion": 0, 10 | "RootChunk": { 11 | "$type": "JsonResource", 12 | "cookingPlatform": "PLATFORM_PC", 13 | "root": { 14 | "HandleId": "0", 15 | "Data": { 16 | "$type": "localizationPersistenceOnScreenEntries", 17 | "entries": [ 18 | { 19 | "$type": "localizationPersistenceOnScreenEntry", 20 | "secondaryKey": "MyMod-Item-Name", 21 | "femaleVariant": "Samurai Mask & Aviators" 22 | } 23 | ] 24 | } 25 | } 26 | }, 27 | "EmbeddedFiles": [] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /support/templates/mod.archive.xl: -------------------------------------------------------------------------------- 1 | factories: 2 | - mymod\factories\clothing.csv 3 | - mymod\factories\weapons.csv 4 | localization: 5 | onscreens: 6 | en-us: mymod\localization\en-us.json 7 | de-de: mymod\localization\de-de.json 8 | -------------------------------------------------------------------------------- /tools/dist/package-mod.ps1: -------------------------------------------------------------------------------- 1 | param ($ReleaseBin, $ProjectName = "ArchiveXL") 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-bundle.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} 10 | & $($PSScriptRoot + "\steps\compose-hints.ps1") -StageDir ${StageDir} 11 | & $($PSScriptRoot + "\steps\compose-licenses.ps1") -StageDir ${StageDir} -ProjectName ${ProjectName} 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-bundle.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir, $ReleaseBin, $ProjectName) 2 | 3 | $BundleDir = "${StageDir}/red4ext/plugins/${ProjectName}/Bundle" 4 | 5 | New-Item -ItemType directory -Force -Path ${BundleDir} | Out-Null 6 | Copy-Item -Path "bundle/packed/archive/pc/mod/*" -Destination ${BundleDir} 7 | Copy-Item -Path "bundle/source/resources/*" -Destination ${BundleDir} 8 | -------------------------------------------------------------------------------- /tools/dist/steps/compose-hints.ps1: -------------------------------------------------------------------------------- 1 | param ($StageDir) 2 | 3 | $HintsDir = "${StageDir}/r6/config/redsUserHints" 4 | 5 | New-Item -ItemType directory -Force -Path ${HintsDir} | Out-Null 6 | Copy-Item -Path "support/hints/*" -Destination ${HintsDir} 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 | -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | set_xmakever("2.5.9") 2 | 3 | set_project("ArchiveXL") 4 | set_version("1.23.1", {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("ArchiveXL") 35 | set_default(true) 36 | set_kind("shared") 37 | set_filename("ArchiveXL.dll") 38 | set_pcxxheader("src/pch.hpp") 39 | add_files("src/**.cpp", "src/**.rc", "src/**.asm", "lib/**.cpp") 40 | add_headerfiles("src/**.hpp", "lib/**.hpp") 41 | add_includedirs("src/", "lib/") 42 | add_deps("RED4ext.SDK", "nameof", "semver", "wil") 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", "ArchiveXL") 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("wil") 74 | set_default(false) 75 | set_kind("static") 76 | set_group("vendor") 77 | add_headerfiles("vendor/wil/include/**.h") 78 | add_includedirs("vendor/wil/include/", { public = true }) 79 | 80 | add_rules("plugin.vsxmake.autoupdate") 81 | --------------------------------------------------------------------------------