├── .gitattributes ├── .github └── workflows │ ├── build.yaml │ └── release.yaml ├── .gitignore ├── .gitmodules ├── .redscript-ide ├── .vscode ├── cmake-kits.json ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── cliff.toml ├── compile_commands.json ├── include └── ModSettings │ └── ModSettings.hpp ├── license.md ├── mod_settings.code-workspace ├── readme.md ├── requirements.md ├── src ├── archiveXL │ └── ModSettings.archive.xl ├── red4ext │ ├── .clang-format │ ├── Addresses.hpp │ ├── Hooks │ │ └── ReleaseScriptData.cpp │ ├── INIReader.h │ ├── IRuntimeVariable.cpp │ ├── IRuntimeVariable.hpp │ ├── Main.cpp │ ├── ModConfigVar.cpp │ ├── ModConfigVar.hpp │ ├── ModSettings.cpp │ ├── ModSettings.hpp │ ├── RuntimeVariable.cpp │ ├── RuntimeVariable.hpp │ ├── ScriptData.hpp │ ├── ScriptDefinitions │ │ ├── ScriptDefinitions.hpp │ │ ├── ScriptHost.hpp │ │ └── ScriptProperty.hpp │ ├── Scripting │ │ ├── Common.hpp │ │ ├── RTTIClass.hpp │ │ ├── RTTIEnum.hpp │ │ ├── RTTIExpansion.hpp │ │ ├── RTTIRegistrar.cpp │ │ └── RTTIRegistrar.hpp │ ├── Utils.cpp │ ├── Utils.hpp │ ├── Variable.cpp │ ├── Variable.hpp │ ├── stdafx.cpp │ └── stdafx.hpp ├── redscript │ ├── Module.reds.in │ └── mod_settings │ │ ├── ModSettings.reds │ │ ├── ModSettingsMainGameController.reds │ │ ├── ModSettingsNotificationListener.reds │ │ ├── _SettingsCategoryController.reds │ │ ├── _SettingsSelectorControllers.reds │ │ ├── _deathMenu.reds │ │ ├── _pauseMenu.reds │ │ ├── _pauseScenario.reds │ │ ├── _preGameScenarios.reds │ │ └── _singleplayerMenu.reds └── wolvenkit │ ├── ModSettings.cpmodproj │ ├── install_log.xml │ ├── layout.xml │ ├── packed │ └── archive │ │ └── pc │ │ └── mod │ │ └── ModSettings.archive │ └── source │ └── archive │ └── base │ ├── gameplay │ └── gui │ │ └── fullscreen │ │ ├── main_menu │ │ ├── pregame_menu.inkmenu │ │ └── pregame_menu_old.inkmenu │ │ ├── menu.inkmenu │ │ ├── menu_old.inkmenu │ │ └── settings │ │ └── mod_settings_main.inkwidget │ └── localization │ └── en-us │ └── onscreens │ └── mod_settings.json └── tools └── ModStngs.1sc /.gitattributes: -------------------------------------------------------------------------------- 1 | *.reds linguist-language=Swift 2 | # Auto detect text files and perform LF normalization 3 | * text=auto 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build Workflow 2 | on: [ push, pull_request ] 3 | 4 | jobs: 5 | build: 6 | name: Build (${{ matrix.config }}) 7 | runs-on: windows-latest 8 | permissions: 9 | contents: write 10 | 11 | strategy: 12 | matrix: 13 | config: [ Debug, RelWithDebInfo ] 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: true 19 | - uses: ilammy/msvc-dev-cmd@v1.12.1 20 | with: 21 | arch: amd64 22 | 23 | - name: Configure CMake 24 | run: cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DCMAKE_CI_BUILD=ON -DGITHUB_ENV="$env:GITHUB_ENV" -G Ninja 25 | 26 | - name: Build 27 | id: build 28 | run: cmake --build ${{ github.workspace }}/build --config ${{ matrix.config }} 29 | 30 | - name: Get short SHA 31 | run: echo "SHORT_SHA=$("${{ github.sha }}".SubString(0, 7))" >> $env:GITHUB_ENV 32 | 33 | - name: Create environment variables 34 | run: | 35 | $config = "${{ matrix.config }}" 36 | if ($config -eq "RelWithDebInfo") { 37 | $config = "release" 38 | } 39 | else { 40 | $config = $config.ToLower() 41 | } 42 | 43 | echo "PRETTY_CONFIG=${config}" | Out-File -FilePath $env:GITHUB_ENV -Encoding UTF8 -Append 44 | 45 | - name: Upload game_dir with zip file name 46 | uses: actions/upload-artifact@v3 47 | with: 48 | name: ${{ env.MOD_ZIP_FILENAME }}_${{ env.PRETTY_CONFIG }}_${{ env.SHORT_SHA }} 49 | path: ${{ github.workspace }}/game_dir/** 50 | 51 | - name: Upload game_dir_debug with zip file name 52 | uses: actions/upload-artifact@v3 53 | with: 54 | name: ${{ env.MOD_ZIP_FILENAME }}_${{ env.PRETTY_CONFIG }}_${{ env.SHORT_SHA }}_pdb 55 | path: ${{ github.workspace }}/game_dir_debug/** 56 | 57 | - name: Upload game_dir_requirements with zip file name 58 | uses: actions/upload-artifact@v3 59 | with: 60 | name: ${{ env.MOD_ZIP_FILENAME }}_${{ env.PRETTY_CONFIG }}_${{ env.SHORT_SHA }}_requirements 61 | path: ${{ github.workspace }}/game_dir_requirements/** 62 | 63 | - name: Upload game_dir_requirements_debug with zip file name 64 | uses: actions/upload-artifact@v3 65 | with: 66 | name: ${{ env.MOD_ZIP_FILENAME }}_${{ env.PRETTY_CONFIG }}_${{ env.SHORT_SHA }}_requirements_pdb 67 | path: ${{ github.workspace }}/game_dir_requirements_debug/** 68 | 69 | - name: 'Version Badge' 70 | if: always() 71 | run: | 72 | mkdir badge 73 | echo '{"cp_version":{"label":"Cyberpunk 2077","status":"${{ env.CYBERPUNK_2077_GAME_VERSION }}","color":"${{ steps.build.outcome == 'success' && '31b75d' || 'red' }}"}}' > badge/shields.json 74 | 75 | - name: Push shields branch 76 | if: github.ref == 'refs/heads/main' 77 | uses: s0/git-publish-subdir-action@develop 78 | env: 79 | REPO: self 80 | BRANCH: shields 81 | FOLDER: badge 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Workflow 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+*" 7 | 8 | jobs: 9 | release: 10 | name: Build & Release 11 | runs-on: windows-latest 12 | env: 13 | GIT_CLIFF_VERSION: 0.10.0 14 | CHANGELOG_FILE: ${{ github.workspace }}-CHANGES.md 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | submodules: true 20 | - uses: ilammy/msvc-dev-cmd@v1.12.1 21 | with: 22 | arch: amd64 23 | 24 | - name: Configure CMake 25 | run: cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CI_BUILD=ON -DGITHUB_ENV="$env:GITHUB_ENV" -G Ninja 26 | 27 | - name: Build 28 | run: cmake --build ${{ github.workspace }}/build --config RelWithDebInfo 29 | 30 | - name: Install git-cliff 31 | uses: Alex079/setup-custom-tool@v1 32 | with: 33 | archiveUrl: https://github.com/orhun/git-cliff/releases/download/v${{ env.GIT_CLIFF_VERSION }}/git-cliff-${{ env.GIT_CLIFF_VERSION }}-x86_64-pc-windows-msvc.zip 34 | archiveGlob: '*' 35 | toolName: git-cliff 36 | toolVersion: ${{ env.GIT_CLIFF_VERSION }} 37 | 38 | - name: Generate a changelog 39 | run: | 40 | git-cliff --current --strip header 41 | git-cliff --current --strip header -o ${{ env.CHANGELOG_FILE }} 42 | cat requirements.md >> ${{ env.CHANGELOG_FILE }} 43 | 44 | - name: Zipping game_dir 45 | run: tar -cvf "${{ github.workspace }}/${{ env.MOD_SLUG }}_${{ github.ref_name }}.zip" --format=zip * 46 | working-directory: game_dir 47 | 48 | - name: Zipping game_dir_debug 49 | run: tar -cvf "${{ github.workspace }}/${{ env.MOD_SLUG }}_${{ github.ref_name }}_pdb.zip" --format=zip * 50 | working-directory: game_dir_debug 51 | 52 | - name: Check file existence 53 | id: requirements_folder_exists 54 | uses: andstor/file-existence-action@v1 55 | with: 56 | files: "game_dir_requirements" 57 | 58 | - name: Check file existence 59 | id: requirements_debug_folder_exists 60 | uses: andstor/file-existence-action@v1 61 | with: 62 | files: "game_dir_requirements_debug" 63 | 64 | - name: Zipping game_dir_requirements 65 | if: steps.requirements_folder_exists.outputs.files_exists == 'true' 66 | run: tar -cvf "${{ github.workspace }}/${{ env.MOD_SLUG }}_${{ github.ref_name }}_requirements.zip" --format=zip * 67 | working-directory: game_dir_requirements 68 | 69 | - name: Zipping game_dir_requirements_debug 70 | if: steps.requirements_debug_folder_exists.outputs.files_exists == 'true' 71 | run: tar -cvf "${{ github.workspace }}/${{ env.MOD_SLUG }}_${{ github.ref_name }}_requirements_pdb.zip" --format=zip * 72 | working-directory: game_dir_requirements_debug 73 | 74 | - name: Release 75 | uses: softprops/action-gh-release@v1 76 | with: 77 | name: ${{ github.ref_name }}${{ env.CYBERPUNK_2077_GAME_VERSION_STR }} 78 | body_path: ${{ env.CHANGELOG_FILE }} 79 | append_body: true 80 | files: | 81 | ${{ env.MOD_SLUG }}_${{ github.ref_name }}.zip 82 | ${{ env.MOD_SLUG }}_${{ github.ref_name }}_requirements.zip 83 | ${{ env.MOD_SLUG }}_${{ github.ref_name }}_pdb.zip 84 | ${{ env.MOD_SLUG }}_${{ github.ref_name }}_requirements_pdb.zip 85 | prerelease: ${{ contains(github.ref_name, '-') || contains(github.ref_name, '_') }} 86 | env: 87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | build/ 20 | 21 | # Build results 22 | # [Dd]ebug/ 23 | # [Dd]ebugPublic/ 24 | # [Rr]elease/ 25 | # [Rr]eleases/ 26 | # x64/ 27 | # x86/ 28 | # [Ww][Ii][Nn]32/ 29 | # [Aa][Rr][Mm]/ 30 | # [Aa][Rr][Mm]64/ 31 | # bld/ 32 | # [Bb]in/ 33 | # [Oo]bj/ 34 | # [Ll]og/ 35 | # [Ll]ogs/ 36 | make/ 37 | *.zip 38 | resources/custom_game_controller.zip 39 | resources/install_log.xml 40 | game_dir/ 41 | game_dir_requirements/ 42 | game_dir_debug/ 43 | game_dir_requirements_debug/ 44 | 45 | Cyberpunk2077.exe* 46 | REDEngineErrorReporter.exe* 47 | 48 | # Visual Studio 2015/2017 cache/options directory 49 | .vs/ 50 | # Uncomment if you have tasks that create the project's static files in wwwroot 51 | #wwwroot/ 52 | 53 | # Visual Studio 2017 auto generated files 54 | Generated\ Files/ 55 | 56 | # MSTest test Results 57 | [Tt]est[Rr]esult*/ 58 | [Bb]uild[Ll]og.* 59 | 60 | # NUnit 61 | *.VisualState.xml 62 | TestResult.xml 63 | nunit-*.xml 64 | 65 | # Build Results of an ATL Project 66 | [Dd]ebugPS/ 67 | [Rr]eleasePS/ 68 | dlldata.c 69 | 70 | # Benchmark Results 71 | BenchmarkDotNet.Artifacts/ 72 | 73 | # .NET Core 74 | project.lock.json 75 | project.fragment.lock.json 76 | artifacts/ 77 | 78 | # ASP.NET Scaffolding 79 | ScaffoldingReadMe.txt 80 | 81 | # StyleCop 82 | StyleCopReport.xml 83 | 84 | # Files built by Visual Studio 85 | *_i.c 86 | *_p.c 87 | *_h.h 88 | *.ilk 89 | *.meta 90 | *.obj 91 | *.iobj 92 | *.pch 93 | *.pdb 94 | *.ipdb 95 | *.pgc 96 | *.pgd 97 | *.rsp 98 | *.sbr 99 | *.tlb 100 | *.tli 101 | *.tlh 102 | *.tmp 103 | *.tmp_proj 104 | *_wpftmp.csproj 105 | *.log 106 | *.vspscc 107 | *.vssscc 108 | .builds 109 | *.pidb 110 | *.svclog 111 | *.scc 112 | 113 | # Chutzpah Test files 114 | _Chutzpah* 115 | 116 | # Visual C++ cache files 117 | ipch/ 118 | *.aps 119 | *.ncb 120 | *.opendb 121 | *.opensdf 122 | *.sdf 123 | *.cachefile 124 | *.VC.db 125 | *.VC.VC.opendb 126 | 127 | # Visual Studio profiler 128 | *.psess 129 | *.vsp 130 | *.vspx 131 | *.sap 132 | 133 | # Visual Studio Trace Files 134 | *.e2e 135 | 136 | # TFS 2012 Local Workspace 137 | $tf/ 138 | 139 | # Guidance Automation Toolkit 140 | *.gpState 141 | 142 | # ReSharper is a .NET coding add-in 143 | _ReSharper*/ 144 | *.[Rr]e[Ss]harper 145 | *.DotSettings.user 146 | 147 | # TeamCity is a build add-in 148 | _TeamCity* 149 | 150 | # DotCover is a Code Coverage Tool 151 | *.dotCover 152 | 153 | # AxoCover is a Code Coverage Tool 154 | .axoCover/* 155 | !.axoCover/settings.json 156 | 157 | # Coverlet is a free, cross platform Code Coverage Tool 158 | coverage*.json 159 | coverage*.xml 160 | coverage*.info 161 | 162 | # Visual Studio code coverage results 163 | *.coverage 164 | *.coveragexml 165 | 166 | # NCrunch 167 | _NCrunch_* 168 | .*crunch*.local.xml 169 | nCrunchTemp_* 170 | 171 | # MightyMoose 172 | *.mm.* 173 | AutoTest.Net/ 174 | 175 | # Web workbench (sass) 176 | .sass-cache/ 177 | 178 | # Installshield output folder 179 | [Ee]xpress/ 180 | 181 | # DocProject is a documentation generator add-in 182 | DocProject/buildhelp/ 183 | DocProject/Help/*.HxT 184 | DocProject/Help/*.HxC 185 | DocProject/Help/*.hhc 186 | DocProject/Help/*.hhk 187 | DocProject/Help/*.hhp 188 | DocProject/Help/Html2 189 | DocProject/Help/html 190 | 191 | # Click-Once directory 192 | publish/ 193 | 194 | # Publish Web Output 195 | *.[Pp]ublish.xml 196 | *.azurePubxml 197 | # Note: Comment the next line if you want to checkin your web deploy settings, 198 | # but database connection strings (with potential passwords) will be unencrypted 199 | *.pubxml 200 | *.publishproj 201 | 202 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 203 | # checkin your Azure Web App publish settings, but sensitive information contained 204 | # in these scripts will be unencrypted 205 | PublishScripts/ 206 | 207 | # NuGet Packages 208 | *.nupkg 209 | # NuGet Symbol Packages 210 | *.snupkg 211 | # The packages folder can be ignored because of Package Restore 212 | **/[Pp]ackages/* 213 | # except build/, which is used as an MSBuild target. 214 | !**/[Pp]ackages/build/ 215 | # Uncomment if necessary however generally it will be regenerated when needed 216 | #!**/[Pp]ackages/repositories.config 217 | # NuGet v3's project.json files produces more ignorable files 218 | *.nuget.props 219 | *.nuget.targets 220 | 221 | # Microsoft Azure Build Output 222 | csx/ 223 | *.build.csdef 224 | 225 | # Microsoft Azure Emulator 226 | ecf/ 227 | rcf/ 228 | 229 | # Windows Store app package directories and files 230 | AppPackages/ 231 | BundleArtifacts/ 232 | Package.StoreAssociation.xml 233 | _pkginfo.txt 234 | *.appx 235 | *.appxbundle 236 | *.appxupload 237 | 238 | # Visual Studio cache files 239 | # files ending in .cache can be ignored 240 | *.[Cc]ache 241 | # but keep track of directories ending in .cache 242 | !?*.[Cc]ache/ 243 | 244 | # Others 245 | ClientBin/ 246 | ~$* 247 | *~ 248 | *.dbmdl 249 | *.dbproj.schemaview 250 | *.jfm 251 | *.pfx 252 | *.publishsettings 253 | orleans.codegen.cs 254 | 255 | # Including strong name files can present a security risk 256 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 257 | #*.snk 258 | 259 | # Since there are multiple workflows, uncomment next line to ignore bower_components 260 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 261 | #bower_components/ 262 | 263 | # RIA/Silverlight projects 264 | Generated_Code/ 265 | 266 | # Backup & report files from converting an old project file 267 | # to a newer Visual Studio version. Backup files are not needed, 268 | # because we have git ;-) 269 | _UpgradeReport_Files/ 270 | Backup*/ 271 | UpgradeLog*.XML 272 | UpgradeLog*.htm 273 | ServiceFabricBackup/ 274 | *.rptproj.bak 275 | 276 | # SQL Server files 277 | *.mdf 278 | *.ldf 279 | *.ndf 280 | 281 | # Business Intelligence projects 282 | *.rdl.data 283 | *.bim.layout 284 | *.bim_*.settings 285 | *.rptproj.rsuser 286 | *- [Bb]ackup.rdl 287 | *- [Bb]ackup ([0-9]).rdl 288 | *- [Bb]ackup ([0-9][0-9]).rdl 289 | 290 | # Microsoft Fakes 291 | FakesAssemblies/ 292 | 293 | # GhostDoc plugin setting file 294 | *.GhostDoc.xml 295 | 296 | # Node.js Tools for Visual Studio 297 | .ntvs_analysis.dat 298 | node_modules/ 299 | 300 | # Visual Studio 6 build log 301 | *.plg 302 | 303 | # Visual Studio 6 workspace options file 304 | *.opt 305 | 306 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 307 | *.vbw 308 | 309 | # Visual Studio LightSwitch build output 310 | **/*.HTMLClient/GeneratedArtifacts 311 | **/*.DesktopClient/GeneratedArtifacts 312 | **/*.DesktopClient/ModelManifest.xml 313 | **/*.Server/GeneratedArtifacts 314 | **/*.Server/ModelManifest.xml 315 | _Pvt_Extensions 316 | 317 | # Paket dependency manager 318 | .paket/paket.exe 319 | paket-files/ 320 | 321 | # FAKE - F# Make 322 | .fake/ 323 | 324 | # CodeRush personal settings 325 | .cr/personal 326 | 327 | # Python Tools for Visual Studio (PTVS) 328 | __pycache__/ 329 | *.pyc 330 | 331 | # Cake - Uncomment if you are using it 332 | # tools/** 333 | # !tools/packages.config 334 | 335 | # Tabs Studio 336 | *.tss 337 | 338 | # Telerik's JustMock configuration file 339 | *.jmconfig 340 | 341 | # BizTalk build output 342 | *.btp.cs 343 | *.btm.cs 344 | *.odx.cs 345 | *.xsd.cs 346 | 347 | # OpenCover UI analysis results 348 | OpenCover/ 349 | 350 | # Azure Stream Analytics local run output 351 | ASALocalRun/ 352 | 353 | # MSBuild Binary and Structured Log 354 | *.binlog 355 | 356 | # NVidia Nsight GPU debugger configuration file 357 | *.nvuser 358 | 359 | # MFractors (Xamarin productivity tool) working folder 360 | .mfractor/ 361 | 362 | # Local History for Visual Studio 363 | .localhistory/ 364 | 365 | # BeatPulse healthcheck temp database 366 | healthchecksdb 367 | 368 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 369 | MigrationBackup/ 370 | 371 | # Ionide (cross platform F# VS Code tools) working folder 372 | .ionide/ 373 | 374 | # Fody - auto-generated XML schema 375 | FodyWeavers.xsd 376 | 377 | red4ext/deps/* 378 | *.zip 379 | build/mods/ModSettings/archives/ModSettings.archive 380 | build/mods/ModSettings/info.json 381 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/spdlog"] 2 | path = deps/spdlog 3 | url = https://github.com/gabime/spdlog.git 4 | [submodule "deps/detours"] 5 | path = deps/detours 6 | url = https://github.com/microsoft/Detours.git 7 | [submodule "deps/red4ext.sdk"] 8 | path = deps/red4ext.sdk 9 | url = https://github.com/jackhumbert/RED4ext.SDK.git 10 | [submodule "deps/cpcmake"] 11 | path = deps/cpcmake 12 | url = https://github.com/jackhumbert/cyberpunk_cmake.git 13 | [submodule "deps/archive_xl"] 14 | path = deps/archive_xl 15 | url = https://github.com/psiberx/cp2077-archive-xl.git 16 | [submodule "deps/red_lib"] 17 | path = deps/red_lib 18 | url = https://github.com/jackhumbert/cp2077-red-lib.git 19 | -------------------------------------------------------------------------------- /.redscript-ide: -------------------------------------------------------------------------------- 1 | redscript_dir = "src/redscript" 2 | script_cache_path = "build/dependencies.redscripts" -------------------------------------------------------------------------------- /.vscode/cmake-kits.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "VS 2022", 4 | "visualStudio": "88610c92", 5 | "visualStudioArchitecture": "x64", 6 | "isTrusted": true, 7 | "compilers": { 8 | "C": "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.38.33130/bin/Hostx64/x64/cl.exe", 9 | "CXX": "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.38.33130/bin/Hostx64/x64/cl.exe" 10 | }, 11 | "preferredGenerator": { 12 | "name": "Ninja" 13 | }, 14 | "environmentVariables": { 15 | "PATH": "C:/Users/Jack/Downloads/ninja-win/;${env:PATH}" 16 | } 17 | } 18 | ] -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "cppvsdbg", 9 | "request": "launch", 10 | "name": "Debug", 11 | "preLaunchTask": "Install Mod", 12 | "program": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Cyberpunk 2077\\bin\\x64\\Cyberpunk2077.exe", 13 | "cwd": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Cyberpunk 2077\\", 14 | "visualizerFile": "${workspaceFolder}/deps/cpcmake/.natvis", 15 | // "showDisplayString": true 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "redscript.buildPath": "build", 3 | "redscript.sourcePath": "src/redscript", 4 | "redscript.modFolderName": "flight_control", 5 | "files.exclude": { 6 | "**/.git": true, 7 | "**/.svn": true, 8 | "**/.hg": true, 9 | "**/CVS": true, 10 | "**/.DS_Store": true, 11 | "**/Thumbs.db": true, 12 | } 13 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build Mod", 8 | "command": "echo ${command:cmake.build}", 9 | "problemMatcher": [], 10 | "detail": "Compile .dll & packed redscript files" 11 | }, 12 | { 13 | "label": "Install Mod", 14 | "command": "echo ${command:cmake.install}", 15 | "problemMatcher": [], 16 | "detail": "Install files into the game's installation directory" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | 3 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 4 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/cpcmake") 5 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/red4ext.sdk/cmake") 6 | 7 | include(ConfigureVersionFromGit) 8 | configure_version_from_git() 9 | 10 | project(mod_settings VERSION ${GIT_VERSION_MAJOR}.${GIT_VERSION_MINOR}.${GIT_VERSION_PATCH} LANGUAGES NONE) 11 | 12 | include(CyberpunkMod) 13 | 14 | configure_mod( 15 | NAME "Mod Settings" 16 | SLUG ${PROJECT_NAME} 17 | PREFIX MS 18 | AUTHOR "Jack Humbert" 19 | URL "https://github.com/jackhumbert/mod_settings" 20 | LICENSE "Licensed under the MIT license. See the license.md in the root project for details." 21 | ) 22 | 23 | set(LOAD_ARCHIVES_FROM_RED4EXT ON) 24 | set(LOAD_REDSCRIPT_FROM_RED4EXT ON) 25 | 26 | find_program(ZOLTAN_CLANG_EXE NAMES zoltan-clang.exe PATHS "${MOD_TOOLS_DIR}" CACHE) 27 | find_program(REDSCRIPT_CLI_EXE NAMES redscript-cli.exe PATHS "${MOD_TOOLS_DIR}" CACHE) 28 | find_program(CYBERPUNK_2077_EXE NAMES Cyberpunk2077.exe PATHS "${CYBERPUNK_2077_GAME_DIR}/bin/x64" CACHE DOC "Cyberpunk2077.exe Executable File") 29 | 30 | find_package(RedLib) 31 | 32 | configure_red4ext(src/red4ext) 33 | configure_red4ext_addresses(Addresses.hpp) 34 | 35 | target_include_directories(${MOD_SLUG}.dll 36 | PUBLIC 37 | ${MOD_SOURCE_DIR}/include 38 | ) 39 | 40 | target_link_libraries(${MOD_SLUG}.dll 41 | PUBLIC 42 | ArchiveXL 43 | RedLib 44 | ) 45 | 46 | target_compile_definitions(${MOD_SLUG}.dll PRIVATE MOD_SETTINGS_DLLDIR_EX) 47 | 48 | configure_archives(src/wolvenkit/packed/archive/pc/mod/ModSettings.archive src/archiveXL/ModSettings.archive.xl) 49 | 50 | configure_redscript(src/redscript) 51 | 52 | configure_folder_file(readme.md) 53 | configure_folder_file(license.md) 54 | 55 | # configure_uninstall() 56 | configure_release(${MOD_SLUG}_${MOD_VERSION_STR}.zip) 57 | configure_install() -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | [changelog] 2 | header = """ 3 | # Changelog 4 | """ 5 | # template for the changelog body 6 | # https://tera.netlify.app/docs/#introduction 7 | body = """ 8 | {% if version %}\ 9 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 10 | {% else %}\ 11 | ## [unreleased] 12 | {% endif %}\ 13 | {% for group, commits in commits | group_by(attribute="group") %} 14 | ### {{ group | upper_first }} 15 | {% for commit in commits %}\ 16 | - {% if commit.breaking %}[**breaking**] {% endif %}\ 17 | {{ commit.message | upper_first }} ({{ commit.id }}) 18 | {% endfor %}\ 19 | {% endfor %} 20 | ### Contributors 21 | {% for author in commits | map(attribute="author.name") | unique %}\ 22 | - {{ author }} 23 | {% endfor %}\n 24 | """ 25 | trim = true 26 | 27 | [git] 28 | # parse the commits based on https://www.conventionalcommits.org 29 | conventional_commits = true 30 | commit_parsers = [ 31 | { message = "^feat", group = "Features" }, 32 | { message = "^fix", group = "Bug Fixes" }, 33 | { message = "^doc", group = "Documentation" }, 34 | { message = "^perf", group = "Performance" }, 35 | { message = "^refactor", group = "Refactor" }, 36 | { message = "^style", group = "Styling" }, 37 | { message = "^test", group = "Testing" }, 38 | { message = "^chore", group = "Miscellaneous", skip = true }, 39 | { message = "^ci", group = "CI", skip = true }, 40 | { body = ".*security", group = "Security" }, 41 | ] -------------------------------------------------------------------------------- /include/ModSettings/ModSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef MOD_SETTINGS_DLLDIR_EX 6 | #define MOD_SETTINGS_DLLDIR __declspec(dllexport) // export DLL information 7 | #else 8 | #define MOD_SETTINGS_DLLDIR __declspec(dllimport) // import DLL information 9 | #endif 10 | 11 | namespace ModSettings { 12 | 13 | using namespace RED4ext; 14 | 15 | struct ModVariable; 16 | 17 | struct ModSettingDependency { 18 | // void Resolve(std::string str, CName scriptClass); 19 | 20 | CName className; 21 | CName propertyName; 22 | CString value; 23 | ModVariable * variable; 24 | }; 25 | 26 | union ModVariableType { 27 | bool b; 28 | uint32_t u32; 29 | int32_t i32; 30 | float f32; 31 | }; 32 | 33 | typedef std::function runtime_class_callback_t; 34 | 35 | struct Variable { 36 | const char * modName; 37 | const char * className; 38 | const char * categoryName; 39 | const char * propertyName; 40 | CName type; 41 | const char * displayName; 42 | const char * description; 43 | uint32_t order; 44 | ModVariableType defaultValue; 45 | ModVariableType stepValue; 46 | ModVariableType minValue; 47 | ModVariableType maxValue; 48 | std::shared_ptr callback; 49 | ModSettingDependency dependency; 50 | }; 51 | 52 | RED4EXT_ASSERT_SIZE(Variable, 152); 53 | 54 | extern "C" MOD_SETTINGS_DLLDIR void AddVariable(Variable *variable); 55 | 56 | } // namespace ModSettings -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2021 Jack Humbert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /mod_settings.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "mod_settings", 5 | "path": "." 6 | } 7 | ], 8 | "settings": { 9 | "window.title": "Mod Settings", 10 | "files.associations": { 11 | "algorithm": "cpp", 12 | "array": "cpp", 13 | "atomic": "cpp", 14 | "bit": "cpp", 15 | "cctype": "cpp", 16 | "charconv": "cpp", 17 | "chrono": "cpp", 18 | "clocale": "cpp", 19 | "cmath": "cpp", 20 | "compare": "cpp", 21 | "concepts": "cpp", 22 | "condition_variable": "cpp", 23 | "cstddef": "cpp", 24 | "cstdint": "cpp", 25 | "cstdio": "cpp", 26 | "cstdlib": "cpp", 27 | "cstring": "cpp", 28 | "ctime": "cpp", 29 | "cwchar": "cpp", 30 | "deque": "cpp", 31 | "exception": "cpp", 32 | "filesystem": "cpp", 33 | "format": "cpp", 34 | "forward_list": "cpp", 35 | "fstream": "cpp", 36 | "functional": "cpp", 37 | "initializer_list": "cpp", 38 | "iomanip": "cpp", 39 | "ios": "cpp", 40 | "iosfwd": "cpp", 41 | "iostream": "cpp", 42 | "istream": "cpp", 43 | "iterator": "cpp", 44 | "limits": "cpp", 45 | "list": "cpp", 46 | "locale": "cpp", 47 | "map": "cpp", 48 | "memory": "cpp", 49 | "mutex": "cpp", 50 | "new": "cpp", 51 | "numeric": "cpp", 52 | "optional": "cpp", 53 | "ostream": "cpp", 54 | "ratio": "cpp", 55 | "regex": "cpp", 56 | "set": "cpp", 57 | "shared_mutex": "cpp", 58 | "sstream": "cpp", 59 | "stack": "cpp", 60 | "stdexcept": "cpp", 61 | "stop_token": "cpp", 62 | "streambuf": "cpp", 63 | "string": "cpp", 64 | "system_error": "cpp", 65 | "thread": "cpp", 66 | "tuple": "cpp", 67 | "type_traits": "cpp", 68 | "typeinfo": "cpp", 69 | "unordered_map": "cpp", 70 | "unordered_set": "cpp", 71 | "utility": "cpp", 72 | "variant": "cpp", 73 | "vector": "cpp", 74 | "xfacet": "cpp", 75 | "xhash": "cpp", 76 | "xiosbase": "cpp", 77 | "xlocale": "cpp", 78 | "xlocbuf": "cpp", 79 | "xlocinfo": "cpp", 80 | "xlocmes": "cpp", 81 | "xlocmon": "cpp", 82 | "xlocnum": "cpp", 83 | "xloctime": "cpp", 84 | "xmemory": "cpp", 85 | "xstddef": "cpp", 86 | "xstring": "cpp", 87 | "xtr1common": "cpp", 88 | "xtree": "cpp", 89 | "xutility": "cpp" 90 | }, 91 | "editor.tabSize": 2, 92 | "cmake.generator": "Ninja" 93 | } 94 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Mod Settings 2 | 3 | [![](https://byob.yarr.is/jackhumbert/mod_settings/cp_version)](https://github.com/jackhumbert/mod_settings/actions/workflows/build.yaml) 4 | 5 | ![Wt2Z4fgfQT](https://user-images.githubusercontent.com/141431/223482953-f7860657-62dd-4cc9-90ea-e099edfa1691.jpg) 6 | 7 | ## Installation 8 | 9 | [Get the latest release here](https://github.com/jackhumbert/mod_settings/releases) - `packed-v*.zip` in the release contains all of the requirements listed below at their most up-to-date versions (at the time of release). Simply extract it and copy the contents in your game's installation folder. 10 | 11 | If you want to install the mod outside of a release (not recommended), the `build/` folder in the repo contains all of the mod-specific files that you can drag into your game's installation folder. 12 | 13 | ## Configuration 14 | 15 | Configuration is done through redscript classes. [See one example here](https://github.com/jackhumbert/in_world_navigation/blob/main/src/redscript/in_world_navigation/InWorldNavigation.reds). Variable & class names are limited to 1024 characters. 16 | 17 | ## Advanced Configuration for Enums 18 | 19 | ```swift 20 | enum ModSetting { 21 | OptionA = 0, 22 | OptionB = 1, 23 | OptionC = 2 24 | } 25 | 26 | class ModSettings { 27 | @runtimeProperty("ModSettings.mod", "Mod") 28 | @runtimeProperty("ModSettings.displayName", "UI-ModSetting-Label") 29 | @runtimeProperty("ModSettings.displayValues.OptionA", "UI-ModSetting-OptionA") 30 | @runtimeProperty("ModSettings.displayValues.OptionB", "Fixed Option B") 31 | public let setting: ModSetting = ModSetting.OptionA; 32 | } 33 | ``` 34 | 35 | This example will produce the following results for enum values: 36 | 37 | | Enum Value | Display Value | 38 | | --- | --- | 39 | | `OptionA` | Localized text `GetLocalizedText("UI-ModSetting-OptionA")` | 40 | | `OptionB` | Fixed text `"Fixed Option B"` | 41 | | `OptionC` | Value name `"OptionC"` | 42 | 43 | 44 | ## Requirements 45 | 46 | * [RED4ext](https://github.com/WopsS/RED4ext) 47 | * [Redscript](https://github.com/jac3km4/redscript) 48 | 49 | _For the mod to work with REDmod deployments, the following mod is required:_ 50 | * [cybercmd](https://github.com/jac3km4/cybercmd) 51 | 52 | ## Development 53 | 54 | Running `tools/ModStngs.1sc` on `mod_settings_main.inkwidget` will toggle between using a custom class (`ModStngs` replacing `Settings` in `SettingsMainGameController` and `SettingsSelectorController*`) so the file can be opened & edited in Wolvenkit. If you keep the file open in Wolvenkit, you won't need to convert back, and only run the script after you've saved it in Wolvenkit, before packing. 55 | 56 | ## Bugs 57 | 58 | If you come across something that doesn't work quite right, or interferes with another mod, [search for or create an issue!](https://github.com/jackhumbert/mod_settings/issues) I have a lot of things on a private TODO list still, but can start to move things to Github issues. 59 | 60 | Special thanks to @psiberx for [Codeware Lib](https://github.com/psiberx/cp2077-codeware/), [InkPlayground Demo](https://github.com/psiberx/cp2077-playground), and Redscript & CET examples on Discord, @WopsS for [RED4ext](https://github.com/WopsS/RED4ext), @jac3km4 for [Redscript toolkit](https://github.com/jac3km4/redscript), @yamashi for [CET](https://github.com/yamashi/CyberEngineTweaks), @rfuzzo & team (especially @seberoth!) for [WolvenKit](https://github.com/WolvenKit/WolvenKit), and all of them for being helpful on Discord. 61 | -------------------------------------------------------------------------------- /requirements.md: -------------------------------------------------------------------------------- 1 | ## Requirements 2 | * RED4ext 1.19.0+ 3 | * ArchiveXL 1.10.0+ 4 | * Redscript 0.5.16+ 5 | -------------------------------------------------------------------------------- /src/archiveXL/ModSettings.archive.xl: -------------------------------------------------------------------------------- 1 | localization: 2 | onscreens: 3 | en-us: base\localization\en-us\onscreens\mod_settings.json -------------------------------------------------------------------------------- /src/red4ext/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # We'll use defaults from the LLVM style, but with 4 columns indentation. 3 | BasedOnStyle: LLVM 4 | IndentWidth: 2 5 | ColumnLimit: 120 -------------------------------------------------------------------------------- /src/red4ext/Addresses.hpp: -------------------------------------------------------------------------------- 1 | // This file has been generated by zoltan (https://github.com/jac3km4/zoltan) 2 | 3 | #define ZOLTAN_STRINGISE(N) #N 4 | #define ZOLTAN_EXPAND_THEN_STRINGISE(N) ZOLTAN_STRINGISE(N) 5 | #define __LINE_STR__ ZOLTAN_EXPAND_THEN_STRINGISE(__LINE__) 6 | 7 | -------------------------------------------------------------------------------- /src/red4ext/Hooks/ReleaseScriptData.cpp: -------------------------------------------------------------------------------- 1 | #include "Addresses.hpp" 2 | #include "ScriptData.hpp" 3 | #include "ModSettings.hpp" 4 | #include 5 | 6 | // @hash 3993832650 7 | // void *__fastcall ReleaseScriptData(ModSettings::ScriptData *scriptData); 8 | // RED4ext::UniversalRelocFunc ReleaseScriptData(3993832650); 9 | 10 | REGISTER_HOOK_HASH(void *, 3993832650, ReleaseScriptData, ModSettings::ScriptData *scriptData) { 11 | ModSettings::ModSettings::ProcessScriptData(scriptData); 12 | auto og = ReleaseScriptData_Original(scriptData); 13 | return og; 14 | } -------------------------------------------------------------------------------- /src/red4ext/INIReader.h: -------------------------------------------------------------------------------- 1 | // Read an INI file into easy-to-access name/value pairs. 2 | 3 | // inih and INIReader are released under the New BSD license (see LICENSE.txt). 4 | // Go to the project home page for more info: 5 | // 6 | // https://github.com/benhoyt/inih 7 | /* inih -- simple .INI file parser 8 | 9 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 10 | home page for more info: 11 | 12 | https://github.com/benhoyt/inih 13 | 14 | */ 15 | 16 | #ifndef __INI_H__ 17 | #define __INI_H__ 18 | 19 | /* Make this header file easier to include in C++ code */ 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | #include 25 | 26 | /* Typedef for prototype of handler function. */ 27 | typedef int (*ini_handler)(void* user, const char* section, 28 | const char* name, const char* value); 29 | 30 | /* Typedef for prototype of fgets-style reader function. */ 31 | typedef char* (*ini_reader)(char* str, int num, void* stream); 32 | 33 | /* Parse given INI-style file. May have [section]s, name=value pairs 34 | (whitespace stripped), and comments starting with ';' (semicolon). Section 35 | is "" if name=value pair parsed before any section heading. name:value 36 | pairs are also supported as a concession to Python's configparser. 37 | 38 | For each name=value pair parsed, call handler function with given user 39 | pointer as well as section, name, and value (data only valid for duration 40 | of handler call). Handler should return nonzero on success, zero on error. 41 | 42 | Returns 0 on success, line number of first error on parse error (doesn't 43 | stop on first error), -1 on file open error, or -2 on memory allocation 44 | error (only when INI_USE_STACK is zero). 45 | */ 46 | int ini_parse(const char* filename, ini_handler handler, void* user); 47 | 48 | /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't 49 | close the file when it's finished -- the caller must do that. */ 50 | int ini_parse_file(FILE* file, ini_handler handler, void* user); 51 | 52 | /* Same as ini_parse(), but takes an ini_reader function pointer instead of 53 | filename. Used for implementing custom or string-based I/O. */ 54 | int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 55 | void* user); 56 | 57 | /* Nonzero to allow multi-line value parsing, in the style of Python's 58 | configparser. If allowed, ini_parse() will call the handler with the same 59 | name for each subsequent line parsed. */ 60 | #ifndef INI_ALLOW_MULTILINE 61 | #define INI_ALLOW_MULTILINE 1 62 | #endif 63 | 64 | /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of 65 | the file. See http://code.google.com/p/inih/issues/detail?id=21 */ 66 | #ifndef INI_ALLOW_BOM 67 | #define INI_ALLOW_BOM 1 68 | #endif 69 | 70 | /* Nonzero to allow inline comments (with valid inline comment characters 71 | specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match 72 | Python 3.2+ configparser behaviour. */ 73 | #ifndef INI_ALLOW_INLINE_COMMENTS 74 | #define INI_ALLOW_INLINE_COMMENTS 1 75 | #endif 76 | #ifndef INI_INLINE_COMMENT_PREFIXES 77 | #define INI_INLINE_COMMENT_PREFIXES ";" 78 | #endif 79 | 80 | /* Nonzero to use stack, zero to use heap (malloc/free). */ 81 | #ifndef INI_USE_STACK 82 | #define INI_USE_STACK 1 83 | #endif 84 | 85 | /* Stop parsing on first error (default is to keep parsing). */ 86 | #ifndef INI_STOP_ON_FIRST_ERROR 87 | #define INI_STOP_ON_FIRST_ERROR 0 88 | #endif 89 | 90 | /* Maximum line length for any line in INI file. */ 91 | #ifndef INI_MAX_LINE 92 | #define INI_MAX_LINE 4096 93 | #endif 94 | 95 | #ifdef __cplusplus 96 | } 97 | #endif 98 | 99 | /* inih -- simple .INI file parser 100 | 101 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 102 | home page for more info: 103 | 104 | https://github.com/benhoyt/inih 105 | 106 | */ 107 | 108 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 109 | #define _CRT_SECURE_NO_WARNINGS 110 | #endif 111 | 112 | #include 113 | #include 114 | #include 115 | 116 | #if !INI_USE_STACK 117 | #include 118 | #endif 119 | 120 | #define MAX_SECTION 1024 121 | #define MAX_NAME 1024 122 | 123 | /* Strip whitespace chars off end of given string, in place. Return s. */ 124 | inline static char* rstrip(char* s) 125 | { 126 | char* p = s + strlen(s); 127 | while (p > s && isspace((unsigned char)(*--p))) 128 | *p = '\0'; 129 | return s; 130 | } 131 | 132 | /* Return pointer to first non-whitespace char in given string. */ 133 | inline static char* lskip(const char* s) 134 | { 135 | while (*s && isspace((unsigned char)(*s))) 136 | s++; 137 | return (char*)s; 138 | } 139 | 140 | /* Return pointer to first char (of chars) or inline comment in given string, 141 | or pointer to null at end of string if neither found. Inline comment must 142 | be prefixed by a whitespace character to register as a comment. */ 143 | inline static char* find_chars_or_comment(const char* s, const char* chars) 144 | { 145 | #if INI_ALLOW_INLINE_COMMENTS 146 | int was_space = 0; 147 | while (*s && (!chars || !strchr(chars, *s)) && 148 | !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { 149 | was_space = isspace((unsigned char)(*s)); 150 | s++; 151 | } 152 | #else 153 | while (*s && (!chars || !strchr(chars, *s))) { 154 | s++; 155 | } 156 | #endif 157 | return (char*)s; 158 | } 159 | 160 | /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ 161 | inline static char* strncpy0(char* dest, const char* src, size_t size) 162 | { 163 | strncpy(dest, src, size); 164 | dest[size - 1] = '\0'; 165 | return dest; 166 | } 167 | 168 | /* See documentation in header file. */ 169 | inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 170 | void* user) 171 | { 172 | /* Uses a fair bit of stack (use heap instead if you need to) */ 173 | #if INI_USE_STACK 174 | char line[INI_MAX_LINE]; 175 | #else 176 | char* line; 177 | #endif 178 | char section[MAX_SECTION] = ""; 179 | char prev_name[MAX_NAME] = ""; 180 | 181 | char* start; 182 | char* end; 183 | char* name; 184 | char* value; 185 | int lineno = 0; 186 | int error = 0; 187 | 188 | #if !INI_USE_STACK 189 | line = (char*)malloc(INI_MAX_LINE); 190 | if (!line) { 191 | return -2; 192 | } 193 | #endif 194 | 195 | /* Scan through stream line by line */ 196 | while (reader(line, INI_MAX_LINE, stream) != NULL) { 197 | lineno++; 198 | 199 | start = line; 200 | #if INI_ALLOW_BOM 201 | if (lineno == 1 && (unsigned char)start[0] == 0xEF && 202 | (unsigned char)start[1] == 0xBB && 203 | (unsigned char)start[2] == 0xBF) { 204 | start += 3; 205 | } 206 | #endif 207 | start = lskip(rstrip(start)); 208 | 209 | if (*start == ';' || *start == '#') { 210 | /* Per Python configparser, allow both ; and # comments at the 211 | start of a line */ 212 | } 213 | #if INI_ALLOW_MULTILINE 214 | else if (*prev_name && *start && start > line) { 215 | 216 | #if INI_ALLOW_INLINE_COMMENTS 217 | end = find_chars_or_comment(start, NULL); 218 | if (*end) 219 | *end = '\0'; 220 | rstrip(start); 221 | #endif 222 | 223 | /* Non-blank line with leading whitespace, treat as continuation 224 | of previous name's value (as per Python configparser). */ 225 | if (!handler(user, section, prev_name, start) && !error) 226 | error = lineno; 227 | } 228 | #endif 229 | else if (*start == '[') { 230 | /* A "[section]" line */ 231 | end = find_chars_or_comment(start + 1, "]"); 232 | if (*end == ']') { 233 | *end = '\0'; 234 | strncpy0(section, start + 1, sizeof(section)); 235 | *prev_name = '\0'; 236 | } 237 | else if (!error) { 238 | /* No ']' found on section line */ 239 | error = lineno; 240 | } 241 | } 242 | else if (*start) { 243 | /* Not a comment, must be a name[=:]value pair */ 244 | end = find_chars_or_comment(start, "=:"); 245 | if (*end == '=' || *end == ':') { 246 | *end = '\0'; 247 | name = rstrip(start); 248 | value = lskip(end + 1); 249 | #if INI_ALLOW_INLINE_COMMENTS 250 | end = find_chars_or_comment(value, NULL); 251 | if (*end) 252 | *end = '\0'; 253 | #endif 254 | rstrip(value); 255 | 256 | /* Valid name[=:]value pair found, call handler */ 257 | strncpy0(prev_name, name, sizeof(prev_name)); 258 | if (!handler(user, section, name, value) && !error) 259 | error = lineno; 260 | } 261 | else if (!error) { 262 | /* No '=' or ':' found on name[=:]value line */ 263 | error = lineno; 264 | } 265 | } 266 | 267 | #if INI_STOP_ON_FIRST_ERROR 268 | if (error) 269 | break; 270 | #endif 271 | } 272 | 273 | #if !INI_USE_STACK 274 | free(line); 275 | #endif 276 | 277 | return error; 278 | } 279 | 280 | /* See documentation in header file. */ 281 | inline int ini_parse_file(FILE* file, ini_handler handler, void* user) 282 | { 283 | return ini_parse_stream((ini_reader)fgets, file, handler, user); 284 | } 285 | 286 | /* See documentation in header file. */ 287 | inline int ini_parse(const char* filename, ini_handler handler, void* user) 288 | { 289 | FILE* file; 290 | int error; 291 | 292 | file = fopen(filename, "r"); 293 | if (!file) 294 | return -1; 295 | error = ini_parse_file(file, handler, user); 296 | fclose(file); 297 | return error; 298 | } 299 | 300 | #endif /* __INI_H__ */ 301 | 302 | 303 | #ifndef __INIREADER_H__ 304 | #define __INIREADER_H__ 305 | 306 | #include 307 | #include 308 | #include 309 | 310 | // Read an INI file into easy-to-access name/value pairs. (Note that I've gone 311 | // for simplicity here rather than speed, but it should be pretty decent.) 312 | class INIReader 313 | { 314 | public: 315 | // Empty Constructor 316 | INIReader() {}; 317 | 318 | // Construct INIReader and parse given filename. See ini.h for more info 319 | // about the parsing. 320 | explicit INIReader(const std::string& filename); 321 | 322 | // Construct INIReader and parse given file. See ini.h for more info 323 | // about the parsing. 324 | explicit INIReader(FILE *file); 325 | 326 | // Return the result of ini_parse(), i.e., 0 on success, line number of 327 | // first error on parse error, or -1 on file open error. 328 | int ParseError() const; 329 | 330 | // Return the list of sections found in ini file 331 | const std::set& Sections() const; 332 | 333 | // Get a string value from INI file, returning default_value if not found. 334 | std::string Get(const std::string& section, const std::string& name, 335 | const std::string& default_value) const; 336 | 337 | // Get an integer (long) value from INI file, returning default_value if 338 | // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). 339 | long GetInteger(const std::string& section, const std::string& name, long default_value) const; 340 | 341 | // Get a real (floating point double) value from INI file, returning 342 | // default_value if not found or not a valid floating point value 343 | // according to strtod(). 344 | double GetReal(const std::string& section, const std::string& name, double default_value) const; 345 | 346 | // Get a single precision floating point number value from INI file, returning 347 | // default_value if not found or not a valid floating point value 348 | // according to strtof(). 349 | float GetFloat(const std::string& section, const std::string& name, float default_value) const; 350 | 351 | // Get a boolean value from INI file, returning default_value if not found or if 352 | // not a valid true/false value. Valid true values are "true", "yes", "on", "1", 353 | // and valid false values are "false", "no", "off", "0" (not case sensitive). 354 | bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const; 355 | 356 | protected: 357 | int _error; 358 | std::map _values; 359 | std::set _sections; 360 | static std::string MakeKey(const std::string& section, const std::string& name); 361 | static int ValueHandler(void* user, const char* section, const char* name, 362 | const char* value); 363 | }; 364 | 365 | #endif // __INIREADER_H__ 366 | 367 | 368 | #ifndef __INIREADER__ 369 | #define __INIREADER__ 370 | 371 | #include 372 | #include 373 | #include 374 | 375 | inline INIReader::INIReader(const std::string& filename) 376 | { 377 | _error = ini_parse(filename.c_str(), ValueHandler, this); 378 | } 379 | 380 | inline INIReader::INIReader(FILE *file) 381 | { 382 | _error = ini_parse_file(file, ValueHandler, this); 383 | } 384 | 385 | inline int INIReader::ParseError() const 386 | { 387 | return _error; 388 | } 389 | 390 | inline const std::set& INIReader::Sections() const 391 | { 392 | return _sections; 393 | } 394 | 395 | inline std::string INIReader::Get(const std::string& section, const std::string& name, const std::string& default_value) const 396 | { 397 | std::string key = MakeKey(section, name); 398 | return _values.count(key) ? _values.at(key) : default_value; 399 | } 400 | 401 | inline long INIReader::GetInteger(const std::string& section, const std::string& name, long default_value) const 402 | { 403 | std::string valstr = Get(section, name, ""); 404 | const char* value = valstr.c_str(); 405 | char* end; 406 | // This parses "1234" (decimal) and also "0x4D2" (hex) 407 | long n = strtol(value, &end, 0); 408 | return end > value ? n : default_value; 409 | } 410 | 411 | inline double INIReader::GetReal(const std::string& section, const std::string& name, double default_value) const 412 | { 413 | std::string valstr = Get(section, name, ""); 414 | const char* value = valstr.c_str(); 415 | char* end; 416 | double n = strtod(value, &end); 417 | return end > value ? n : default_value; 418 | } 419 | 420 | inline float INIReader::GetFloat(const std::string& section, const std::string& name, float default_value) const 421 | { 422 | std::string valstr = Get(section, name, ""); 423 | const char* value = valstr.c_str(); 424 | char* end; 425 | float n = strtof(value, &end); 426 | return end > value ? n : default_value; 427 | } 428 | 429 | inline bool INIReader::GetBoolean(const std::string& section, const std::string& name, bool default_value) const 430 | { 431 | std::string valstr = Get(section, name, ""); 432 | // Convert to lower case to make string comparisons case-insensitive 433 | std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); 434 | if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") 435 | return true; 436 | else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") 437 | return false; 438 | else 439 | return default_value; 440 | } 441 | 442 | inline std::string INIReader::MakeKey(const std::string& section, const std::string& name) 443 | { 444 | std::string key = section + "=" + name; 445 | // Convert to lower case to make section/name lookups case-insensitive 446 | std::transform(key.begin(), key.end(), key.begin(), ::tolower); 447 | return key; 448 | } 449 | 450 | inline int INIReader::ValueHandler(void* user, const char* section, const char* name, 451 | const char* value) 452 | { 453 | INIReader* reader = (INIReader*)user; 454 | std::string key = MakeKey(section, name); 455 | if (reader->_values[key].size() > 0) 456 | reader->_values[key] += "\n"; 457 | reader->_values[key] += value; 458 | reader->_sections.insert(section); 459 | return 1; 460 | } 461 | 462 | #endif // __INIREADER__ 463 | -------------------------------------------------------------------------------- /src/red4ext/IRuntimeVariable.cpp: -------------------------------------------------------------------------------- 1 | #include "IRuntimeVariable.hpp" 2 | #include "ScriptDefinitions/ScriptProperty.hpp" 3 | 4 | namespace ModSettings { 5 | 6 | IRuntimeVariable::IRuntimeVariable() { 7 | bitfield.isInPreGame = true; 8 | bitfield.isInGame = true; 9 | bitfield.isVisible = true; 10 | bitfield.isInitialized = true; 11 | bitfield.isDisabled = false; 12 | bitfield.canBeRestoredToDefault = true; 13 | } 14 | 15 | IRuntimeVariable::IRuntimeVariable(ScriptProperty* prop) : IRuntimeVariable() { 16 | this->updatePolicy = user::EConfigVarUpdatePolicy::ConfirmationRequired; 17 | this->name = prop->name; 18 | char str[0x100]; 19 | std::sprintf(str, "/mods/%s/%s", prop->parent->name.ToString(), this->name.ToString()); 20 | this->groupPath = CNamePool::Add(str); 21 | 22 | // prop->ReadProperty("ModSettings.updatePolicy", &updatePolicy, user::EConfigVarUpdatePolicy::ConfirmationRequired); 23 | prop->ReadProperty("ModSettings.displayName", &this->displayName, this->name); 24 | prop->ReadProperty("ModSettings.description", &this->description); 25 | prop->ReadProperty("ModSettings.order", &this->order, (uint32_t)-1); 26 | 27 | } 28 | 29 | IRuntimeVariable::IRuntimeVariable(CName className, CName propertyName, CName displayName, CName description, uint32_t order) : IRuntimeVariable() { 30 | this->updatePolicy = user::EConfigVarUpdatePolicy::ConfirmationRequired; 31 | this->name = propertyName; 32 | this->displayName = displayName; 33 | char str[0x100]; 34 | std::sprintf(str, "/mods/%s/%s", className.ToString(), propertyName.ToString()); 35 | this->groupPath = CNamePool::Add(str); 36 | this->description = description; 37 | this->order = order; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/red4ext/IRuntimeVariable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "RED4ext/CName.hpp" 3 | #include 4 | #include 5 | 6 | namespace ModSettings { 7 | 8 | struct ScriptProperty; 9 | 10 | class IRuntimeVariable : public RED4ext::user::RuntimeSettingsVar { 11 | public: 12 | IRuntimeVariable(); 13 | IRuntimeVariable(ScriptProperty* prop); 14 | IRuntimeVariable(RED4ext::CName className, RED4ext::CName propertyName, RED4ext::CName displayName, RED4ext::CName description, uint32_t order); 15 | 16 | // custom virtuals 17 | virtual void __fastcall GetValueToWrite(char *value) = 0; 18 | virtual RED4ext::ScriptInstance *__fastcall GetInputValue() = 0; 19 | virtual RED4ext::ScriptInstance *__fastcall GetValuePtr() = 0; 20 | virtual void __fastcall UpdateDefault(void *value) = 0; 21 | }; 22 | 23 | } -------------------------------------------------------------------------------- /src/red4ext/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "RED4ext/RTTISystem.hpp" 2 | #include "Red/TypeInfo/Registrar.hpp" 3 | #include "Utils.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | #include "ModSettings.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace ModSettings { 16 | const RED4ext::Sdk *sdk; 17 | RED4ext::PluginHandle pluginHandle; 18 | } // namespace ModSettings 19 | 20 | BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { 21 | switch (fdwReason) { 22 | case DLL_PROCESS_ATTACH: 23 | ModSettings::ModSettings::GetInstance(); 24 | break; 25 | } 26 | return true; 27 | } 28 | 29 | RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::PluginHandle aHandle, RED4ext::EMainReason aReason, 30 | const RED4ext::Sdk *aSdk) { 31 | switch (aReason) { 32 | case RED4ext::EMainReason::Load: { 33 | ModSettings::sdk = aSdk; 34 | ModSettings::pluginHandle = aHandle; 35 | 36 | aSdk->logger->Info(aHandle, "Starting up Mod Settings " MOD_VERSION_STR); 37 | auto ptr = GetModuleHandle(nullptr); 38 | aSdk->logger->InfoF(aHandle, "Base address: %p", ptr); 39 | 40 | auto scriptsFolder = Utils::GetRootDir() / "r6" / "scripts" / "mod_settings"; 41 | if (std::filesystem::exists(scriptsFolder)) { 42 | aSdk->logger->Info(aHandle, "Deleting old scripts folder"); 43 | std::filesystem::remove_all(scriptsFolder); 44 | } 45 | auto archive = Utils::GetRootDir() / "archive" / "pc" / "mod" / "ModSettings.archive"; 46 | if (std::filesystem::exists(archive)) { 47 | aSdk->logger->Info(aHandle, "Deleting old archive"); 48 | std::filesystem::remove_all(archive); 49 | } 50 | auto archiveXL = Utils::GetRootDir() / "archive" / "pc" / "mod" / "ModSettings.archive.xl"; 51 | if (std::filesystem::exists(archiveXL)) { 52 | aSdk->logger->Info(aHandle, "Deleting old archive.xl"); 53 | std::filesystem::remove_all(archiveXL); 54 | } 55 | auto XL = Utils::GetRootDir() / "archive" / "pc" / "mod" / "ModSettings.xl"; 56 | if (std::filesystem::exists(XL)) { 57 | aSdk->logger->Info(aHandle, "Deleting old xl"); 58 | std::filesystem::remove_all(XL); 59 | } 60 | 61 | Red::TypeInfoRegistrar::RegisterDiscovered(); 62 | 63 | aSdk->scripts->Add(aHandle, L"packed.reds"); 64 | aSdk->scripts->Add(aHandle, L"module.reds"); 65 | ArchiveXL::RegisterArchive(aHandle, "ModSettings.archive"); 66 | ModModuleFactory::GetInstance().Load(aSdk, aHandle); 67 | // Engine::RTTIRegistrar::RegisterPending(); 68 | 69 | break; 70 | } 71 | case RED4ext::EMainReason::Unload: { 72 | aSdk->logger->Info(aHandle, "Shutting down"); 73 | ModModuleFactory::GetInstance().Unload(aSdk, aHandle); 74 | break; 75 | } 76 | } 77 | 78 | return true; 79 | } 80 | 81 | RED4EXT_C_EXPORT void RED4EXT_CALL Query(RED4ext::PluginInfo *aInfo) { 82 | aInfo->name = L"Mod Settings"; 83 | aInfo->author = L"Jack Humbert"; 84 | aInfo->version = RED4EXT_SEMVER(MOD_VERSION_MAJOR, MOD_VERSION_MINOR, MOD_VERSION_PATCH); 85 | // aInfo->runtime = RED4EXT_RUNTIME_LATEST; 86 | aInfo->runtime = RED4EXT_RUNTIME_INDEPENDENT; 87 | aInfo->sdk = RED4EXT_SDK_LATEST; 88 | } 89 | 90 | RED4EXT_C_EXPORT uint32_t RED4EXT_CALL Supports() { return RED4EXT_API_VERSION_LATEST; } 91 | -------------------------------------------------------------------------------- /src/red4ext/ModConfigVar.cpp: -------------------------------------------------------------------------------- 1 | #include "ModConfigVar.hpp" -------------------------------------------------------------------------------- /src/red4ext/ModConfigVar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "RED4ext/Scripting/Natives/userSettingsVar.hpp" 3 | #include "RuntimeVariable.hpp" 4 | #include "ScriptDefinitions/ScriptDefinitions.hpp" 5 | #include "Scripting/RTTIClass.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // namespace ModSettings { 12 | 13 | struct IModConfigVar : RED4ext::IScriptable { 14 | virtual void SetRuntime(ModSettings::IRuntimeVariable * runtime) = 0; 15 | }; 16 | 17 | template struct ModConfigVar : IModConfigVar { 18 | public: 19 | T GetValue() { return this->runtimeVar->valueInput; } 20 | void SetValue(T value) { this->runtimeVar->UpdateValue(&value); } 21 | T GetDefaultValue() { return this->runtimeVar->defaultValue; } 22 | 23 | inline virtual void SetRuntime(ModSettings::IRuntimeVariable * runtime) override { 24 | runtimeVar = reinterpret_cast*>(runtime); 25 | } 26 | 27 | ModSettings::RuntimeVariable *runtimeVar; // 40 28 | 29 | RTTI_IMPL_TYPEINFO(ModConfigVar); 30 | RTTI_IMPL_ALLOCATOR(); 31 | }; 32 | 33 | template struct ModConfigVarRange : IModConfigVar { 34 | T GetValue() { return this->runtimeVar->valueInput; } 35 | void SetValue(T value) { this->runtimeVar->UpdateValue(&value); } 36 | T GetDefaultValue() { return this->runtimeVar->defaultValue; } 37 | T GetMinValue() { return this->runtimeVar->minValue; } 38 | T GetMaxValue() { return this->runtimeVar->maxValue; } 39 | T GetStepValue() { return this->runtimeVar->stepValue; } 40 | 41 | inline virtual void SetRuntime(ModSettings::IRuntimeVariable * runtime) override { 42 | runtimeVar = reinterpret_cast*>(runtime); 43 | } 44 | 45 | ModSettings::RuntimeVariableRange *runtimeVar; // 40 46 | 47 | RTTI_IMPL_TYPEINFO(ModConfigVarRange); 48 | RTTI_IMPL_ALLOCATOR(); 49 | }; 50 | 51 | RTTI_DEFINE_CLASS(ModConfigVar, "ModConfigVarBool", { 52 | RTTI_PARENT(RED4ext::user::SettingsVar); 53 | RTTI_METHOD(GetValue); 54 | RTTI_METHOD(SetValue); 55 | RTTI_METHOD(GetDefaultValue); 56 | }); 57 | 58 | RTTI_DEFINE_CLASS(ModConfigVar, "ModConfigVarName", { 59 | RTTI_PARENT(RED4ext::user::SettingsVar); 60 | RTTI_METHOD(GetValue); 61 | RTTI_METHOD(SetValue); 62 | RTTI_METHOD(GetDefaultValue); 63 | }); 64 | 65 | RTTI_DEFINE_CLASS(ModConfigVarRange, "ModConfigVarFloat", { 66 | RTTI_PARENT(RED4ext::user::SettingsVar); 67 | RTTI_METHOD(GetValue); 68 | RTTI_METHOD(SetValue); 69 | RTTI_METHOD(GetDefaultValue); 70 | RTTI_METHOD(GetMinValue); 71 | RTTI_METHOD(GetMaxValue); 72 | RTTI_METHOD(GetStepValue); 73 | }); 74 | 75 | RTTI_DEFINE_CLASS(ModConfigVarRange, "ModConfigVarInt32", { 76 | RTTI_PARENT(RED4ext::user::SettingsVar); 77 | RTTI_METHOD(GetValue); 78 | RTTI_METHOD(SetValue); 79 | RTTI_METHOD(GetDefaultValue); 80 | RTTI_METHOD(GetMinValue); 81 | RTTI_METHOD(GetMaxValue); 82 | RTTI_METHOD(GetStepValue); 83 | }); 84 | 85 | struct ModConfigVarEnum : IModConfigVar { 86 | int32_t GetValueFor(int32_t index) { 87 | auto varEnum = reinterpret_cast(this->runtimeVar); 88 | if (varEnum->values.size > index) { 89 | return varEnum->values[index]; 90 | } else { 91 | return 0; 92 | } 93 | } 94 | 95 | int32_t GetValue() { return GetValueFor(reinterpret_cast(this->runtimeVar)->valueInput); } 96 | 97 | int32_t GetDefaultValue() { 98 | return GetValueFor(reinterpret_cast(this->runtimeVar)->defaultValue); 99 | } 100 | 101 | RED4ext::DynArray GetValues() { 102 | auto varEnum = reinterpret_cast(this->runtimeVar); 103 | auto values = RED4ext::DynArray(new RED4ext::Memory::DefaultAllocator()); 104 | for (const auto &value : varEnum->values) { 105 | values.EmplaceBack(value); 106 | } 107 | return values; 108 | } 109 | 110 | int32_t GetIndexFor(int32_t value) { 111 | auto varEnum = reinterpret_cast(this->runtimeVar); 112 | auto values = RED4ext::DynArray(new RED4ext::Memory::DefaultAllocator()); 113 | int32_t index = -1; 114 | for (int i = 0; i < varEnum->values.size; i++) { 115 | if (varEnum->values[i] == value) { 116 | index = i; 117 | } 118 | } 119 | return index; 120 | } 121 | 122 | int32_t GetIndex() { return reinterpret_cast(this->runtimeVar)->valueInput; } 123 | 124 | int32_t GetDefaultIndex() { return reinterpret_cast(this->runtimeVar)->defaultValue; } 125 | 126 | void SetIndex(int32_t index) { reinterpret_cast(this->runtimeVar)->UpdateValue(&index); } 127 | 128 | RED4ext::CName GetDisplayValue(int32_t index) { 129 | auto varEnum = reinterpret_cast(this->runtimeVar); 130 | if (varEnum->displayValues.size > index && index > -1) { 131 | return varEnum->displayValues[index]; 132 | } else { 133 | return "None"; 134 | } 135 | } 136 | 137 | inline virtual void SetRuntime(ModSettings::IRuntimeVariable * runtime) override { 138 | runtimeVar = reinterpret_cast(runtime); 139 | } 140 | 141 | ModSettings::RuntimeVariableEnum *runtimeVar; // 40 142 | 143 | RTTI_IMPL_TYPEINFO(ModConfigVarEnum); 144 | RTTI_IMPL_ALLOCATOR(); 145 | }; 146 | 147 | RTTI_DEFINE_CLASS(ModConfigVarEnum, { 148 | RTTI_PARENT(RED4ext::user::SettingsVar); 149 | RTTI_METHOD(GetValue); 150 | RTTI_METHOD(GetValueFor); 151 | RTTI_METHOD(GetDefaultValue); 152 | RTTI_METHOD(GetValues); 153 | RTTI_METHOD(GetIndexFor); 154 | RTTI_METHOD(GetDefaultIndex); 155 | RTTI_METHOD(SetIndex); 156 | RTTI_METHOD(GetIndex); 157 | RTTI_METHOD(GetDisplayValue); 158 | }); -------------------------------------------------------------------------------- /src/red4ext/ModSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "INIReader.h" 2 | #include "ModConfigVar.hpp" 3 | #include "Red/TypeInfo/Macros/Definition.hpp" 4 | #include "RuntimeVariable.hpp" 5 | #include "ScriptData.hpp" 6 | #include "ScriptDefinitions/ScriptDefinitions.hpp" 7 | #include "Scripting/RTTIRegistrar.hpp" 8 | #include "Utils.hpp" 9 | #include "stdafx.hpp" 10 | #include 11 | #include "ModSettings.hpp" 12 | #include "Variable.hpp" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | namespace ModSettings { 23 | 24 | INIReader reader; 25 | 26 | const std::filesystem::path configPath = Utils::GetRootDir() / "red4ext" / "plugins" / "mod_settings" / "user.ini"; 27 | 28 | ModSettings modSettings = ModSettings(); 29 | 30 | ModSettings::ModSettings() {} 31 | 32 | Handle ModSettings::GetInstance() { return Handle(&modSettings); } 33 | 34 | void ModSettings::ClearVariables() { 35 | modSettings.mods.clear(); 36 | } 37 | 38 | std::vector queuedVariables; 39 | 40 | void AddVariable(Variable* variable) { 41 | queuedVariables.emplace_back(variable); 42 | } 43 | 44 | void __fastcall ModSettings::ProcessScriptData(ScriptData *scriptData) { 45 | auto self = ModSettings::GetInstance(); 46 | if (scriptData) { 47 | ModSettings::ReadFromFile(); 48 | ModSettings::ClearVariables(); 49 | for (const auto &scriptClass : scriptData->classes) { 50 | for (const auto &prop : scriptClass->properties) { 51 | // filter out inherited props? 52 | // if (scriptClass->parent) { 53 | // auto found = false; 54 | // for (const auto &parentProp : scriptClass->parent->properties) { 55 | // found |= prop->name == parentProp->name; 56 | // } 57 | // if (found) 58 | // continue; 59 | // } 60 | if (prop->runtimeProperties.size) { 61 | if (prop->IsValid()) { 62 | auto modName = prop->ReadProperty("ModSettings.mod"); 63 | if (!self->mods.contains(modName)) { 64 | self->mods[modName] = Mod(modName); 65 | } 66 | auto &mod = self->mods[modName]; 67 | 68 | if (!mod.classes.contains(scriptClass->name)) { 69 | mod.classes[scriptClass->name] = { 70 | .name = scriptClass->name, 71 | .type = ToClass(scriptClass->name), 72 | .mod = &mod 73 | }; 74 | } 75 | auto &modClass = mod.classes[scriptClass->name]; 76 | 77 | auto categoryName = prop->ReadProperty("ModSettings.category"); 78 | if (!modClass.categories.contains(categoryName)) { 79 | modClass.categories[categoryName] = { 80 | .name = categoryName, 81 | .order = prop->ReadProperty("ModSettings.category.order"), 82 | .modClass = &modClass 83 | }; 84 | } 85 | auto &category = modClass.categories[categoryName]; 86 | 87 | ModVariable variable = { 88 | .name = prop->name, 89 | .type = CRTTISystem::Get()->GetType(prop->type->name), 90 | .configVarType = CRTTISystem::Get()->GetClass(ToConfigVar(prop->type->name)), 91 | .dependency = *prop->ReadDependency(scriptClass->name), 92 | .category = &category, 93 | .implicitOrder = (uint32_t)category.variables.size() 94 | }; 95 | 96 | if (variable.SetRuntimeVariable(prop)) { 97 | category.variables[prop->name] = variable; 98 | modClass.UpdateDefault(variable.name, variable.runtimeVar->GetValuePtr()); 99 | sdk->logger->InfoF(pluginHandle, "Loaded %s.%s", modClass.name.ToString(), variable.name.ToString()); 100 | } else { 101 | sdk->logger->ErrorF(pluginHandle, "%s.%s: Could not find runtime type for script type %s", modClass.name.ToString(), prop->name.ToString(), prop->type->name.ToString()); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | for (const auto &var : queuedVariables) { 108 | CNamePool::Add(var->modName); 109 | if (!self->mods.contains(var->modName)) { 110 | self->mods[var->modName] = Mod(var->modName); 111 | } 112 | auto &mod = self->mods[var->modName]; 113 | 114 | auto modClassName = CNamePool::Add(var->className); 115 | if (!mod.classes.contains(modClassName)) { 116 | mod.classes[modClassName] = { 117 | .name = modClassName, 118 | .mod = &mod 119 | }; 120 | } 121 | auto &modClass = mod.classes[modClassName]; 122 | 123 | auto categoryName = CNamePool::Add(var->categoryName); 124 | if (!modClass.categories.contains(categoryName)) { 125 | modClass.categories[categoryName] = { 126 | .name = categoryName, 127 | .modClass = &modClass 128 | }; 129 | } 130 | auto &category = modClass.categories[categoryName]; 131 | 132 | auto variableName = CNamePool::Add(var->propertyName); 133 | ModVariable variable = { 134 | .name = variableName, 135 | .type = CRTTISystem::Get()->GetType(var->type), 136 | .configVarType = CRTTISystem::Get()->GetClass(ToConfigVar(var->type)), 137 | .dependency = var->dependency, 138 | .category = &category, 139 | .implicitOrder = (uint32_t)category.variables.size() 140 | }; 141 | 142 | if (variable.CreateRuntimeVariable(*var)) { 143 | category.variables[variableName] = variable; 144 | modClass.RegisterCallback(var->callback); 145 | (*var->callback)(var->categoryName, var->propertyName, *(ModVariableType*)variable.runtimeVar->GetValuePtr()); 146 | sdk->logger->InfoF(pluginHandle, "Loaded '%s'.%s", var->modName, var->propertyName); 147 | } else { 148 | sdk->logger->ErrorF(pluginHandle, "Could not create runtime variable for {}", var->propertyName); 149 | } 150 | } 151 | // resolve dependencies 152 | for (auto &[_, mod] : self->mods) { 153 | for (auto &[_, modClass] : mod.classes) { 154 | for (auto &[_, category] : modClass.categories) { 155 | for (auto &[_, variable] : category.variables) { 156 | if (variable.dependency.propertyName) { 157 | if (mod.classes.contains(variable.dependency.className)) { 158 | auto &depClass = mod.classes[variable.dependency.className]; 159 | for (auto &[_, depCategory] : depClass.categories) { 160 | if (depCategory.variables.contains(variable.dependency.propertyName)) { 161 | variable.dependency.variable = &depCategory.variables[variable.dependency.propertyName]; 162 | } 163 | } 164 | } 165 | } 166 | } 167 | } 168 | } 169 | } 170 | } 171 | } 172 | 173 | // void ModSettings::AddVariable(ModSettingsVariable *variable) { 174 | // auto self = ModSettings::GetInstance(); 175 | 176 | // std::shared_lock _(self->variables_lock); 177 | // variable->UpdateValues(); 178 | // self->variables.EmplaceBack(variable); 179 | 180 | // auto modVars = self->variablesByMod.Get(variable->Mod()); 181 | // if (modVars) { 182 | // modVars->EmplaceBack(variable); 183 | // } else { 184 | // auto ra = DynArray(new Memory::DefaultAllocator); 185 | // ra.EmplaceBack(variable); 186 | // self->variablesByMod.Insert(variable->Mod(), ra); 187 | // self->mods.EmplaceBack(variable->Mod()); 188 | // } 189 | 190 | // auto classVars = self->variablesByClass.Get(variable->ClassName()); 191 | // if (classVars) { 192 | // classVars->EmplaceBack(variable); 193 | // } else { 194 | // auto ra = DynArray(new Memory::DefaultAllocator); 195 | // ra.EmplaceBack(variable); 196 | // self->variablesByClass.Insert(variable->ClassName(), ra); 197 | // self->classes.EmplaceBack(variable->ClassName()); 198 | // } 199 | 200 | // if (variable->Category() != "None") { 201 | // auto modCategories = self->categoriesByMod.Get(variable->Mod()); 202 | // if (modCategories) { 203 | // auto found = false; 204 | // for (const auto &category : *modCategories) { 205 | // found |= (variable->Category() == category); 206 | // } 207 | // if (!found) { 208 | // modCategories->EmplaceBack(variable->Category()); 209 | // } 210 | // } else { 211 | // auto ra = DynArray(new Memory::DefaultAllocator); 212 | // ra.EmplaceBack(variable->Category()); 213 | // self->categoriesByMod.Insert(variable->Mod(), ra); 214 | // } 215 | // } 216 | // } 217 | 218 | DynArray ModSettings::GetMods() { 219 | auto array = DynArray(new Memory::DefaultAllocator); 220 | for (auto const &[modName, mod] : modSettings.mods) { 221 | array.EmplaceBack(modName); 222 | } 223 | return array; 224 | } 225 | 226 | DynArray ModSettings::GetCategories(CName modName) { 227 | auto array = DynArray(new Memory::DefaultAllocator); 228 | std::vector> modCategories; 229 | for (auto const &[modClassName, modClass] : modSettings.mods[modName].classes) { 230 | for (auto itr = modClass.categories.begin(); itr != modClass.categories.end(); ++itr ) { 231 | modCategories.push_back(*itr); 232 | } 233 | } 234 | sort(modCategories.begin(), modCategories.end(), [=](std::pair& a, std::pair& b) { 235 | return a.second.order < b.second.order; 236 | }); 237 | for (auto const &[categoryName, category] : modCategories) { 238 | if (categoryName != "None" && !category.variables.empty()) { 239 | auto position = std::find(array.begin(), array.end(), categoryName); 240 | if (position == array.end()) 241 | array.EmplaceBack(categoryName); 242 | } 243 | } 244 | return array; 245 | } 246 | 247 | DynArray> ModSettings::GetVars(CName modName, CName categoryName) { 248 | auto array = DynArray>(new Memory::DefaultAllocator); 249 | if (modSettings.mods.contains(modName)) { 250 | std::vector variables; 251 | for (auto &[modClassName, modClass] : modSettings.mods[modName].classes) { 252 | if (modClass.categories.contains(categoryName)) { 253 | for (auto const &[variableName, variable] : modClass.categories[categoryName].variables) { 254 | if (variable.IsEnabled()) { 255 | variables.emplace_back(variable); 256 | } 257 | } 258 | } 259 | } 260 | std::sort(variables.begin(), variables.end()); 261 | for (auto const &variable : variables) { 262 | auto configVar = variable.ToConfigVar(); 263 | if (configVar) { 264 | array.EmplaceBack(Handle(configVar)); 265 | } 266 | } 267 | } 268 | return array; 269 | } 270 | 271 | void ModSettings::WriteToFile() { 272 | std::ofstream configFile(configPath); 273 | if (configFile.is_open()) { 274 | for (const auto &[modName, mod] : modSettings.mods) { 275 | for (const auto &[className, modClass] : mod.classes) { 276 | configFile << "[" << className.ToString() << "]\n"; 277 | for (const auto &[categoryName, category] : modClass.categories) { 278 | for (const auto &[variableName, variable] : category.variables) { 279 | configFile << variable; 280 | } 281 | } 282 | modClass.NotifyListeners(); 283 | configFile << "\n"; 284 | } 285 | } 286 | configFile.close(); 287 | sdk->logger->InfoF(pluginHandle, "User settings written to file: %s", configPath.string().c_str()); 288 | } else { 289 | sdk->logger->InfoF(pluginHandle, "Could not write to file: %s", configPath.string().c_str()); 290 | } 291 | } 292 | 293 | bool ModSettings::GetSettingString(CName className, CName propertyName, CString *value) { 294 | std::string defaultValue = ""; 295 | auto valueStr = reader.Get(className.ToString(), propertyName.ToString(), defaultValue); 296 | if (valueStr != defaultValue) { 297 | *value = CString(valueStr.c_str()); 298 | return true; 299 | } else { 300 | return false; 301 | } 302 | } 303 | 304 | void ModSettings::ReadValueFromFile(ScriptProperty *prop, ScriptInstance pointer) { 305 | CString settingFromFile; 306 | if (ModSettings::GetSettingString(prop->parent->name, prop->name, &settingFromFile)) { 307 | prop->FromString(pointer, settingFromFile); 308 | } 309 | } 310 | 311 | void ModSettings::ReadFromFile() { 312 | reader = INIReader(configPath.string()); 313 | 314 | if (reader.ParseError() != 0) { 315 | return; 316 | } 317 | } 318 | 319 | void ModSettings::AcceptChanges() { 320 | ModSettings::WriteToFile(); 321 | modSettings.changeMade = false; 322 | modSettings.NotifyListeners(); 323 | } 324 | 325 | void ModSettings::RestoreDefaults(CName modName) { 326 | if (modSettings.mods.contains(modName)) { 327 | auto mod = modSettings.mods[modName]; 328 | modSettings.changeMade = false; 329 | for (auto [modClassName, modClass] : mod.classes) { 330 | for (auto [categoryName, category] : modClass.categories) { 331 | for (auto [variableName, variable] : category.variables) { 332 | modSettings.changeMade |= variable.RestoreDefault(); 333 | } 334 | } 335 | } 336 | modSettings.NotifyListeners(); 337 | } 338 | } 339 | 340 | void ModSettings::RejectChanges() { 341 | for (auto [modName, mod] : modSettings.mods) { 342 | for (auto [modClassName, modClass] : mod.classes) { 343 | for (auto [categoryName, category] : modClass.categories) { 344 | for (auto [variableName, variable] : category.variables) { 345 | modSettings.changeMade |= variable.RestoreDefault(); 346 | } 347 | } 348 | } 349 | } 350 | modSettings.changeMade = false; 351 | modSettings.NotifyListeners(); 352 | } 353 | 354 | void ModSettings::NotifyListeners() { 355 | std::shared_lock _(listeners_lock); 356 | for (auto &[id, listener] : this->listeners) { 357 | if (listener) { 358 | auto instance = listener.Lock(); 359 | auto func = instance->GetType()->GetFunction("OnModSettingsChange"); 360 | if (func) 361 | RED4ext::ExecuteFunction(instance, func, nullptr); 362 | } else { 363 | // remove? 364 | } 365 | } 366 | } 367 | 368 | void ModSettings::RegisterListenerToModifications(Handle &listener) { 369 | if (listener) { 370 | modSettings.listeners[listener->unk28] = listener; 371 | } 372 | } 373 | 374 | void ModSettings::UnregisterListenerToModifications(Handle &listener) { 375 | if (listener) { 376 | modSettings.listeners.erase(listener->unk28); 377 | } 378 | } 379 | 380 | void ModSettings::RegisterListenerToClass(Handle &listener) { 381 | if (listener) { 382 | auto className = listener->GetType()->GetName(); 383 | for (auto &[modName, mod] : modSettings.mods) { 384 | if (mod.classes.contains(className)) { 385 | mod.classes[className].RegisterListener(listener); 386 | } 387 | } 388 | } 389 | } 390 | 391 | void ModSettings::UnregisterListenerToClass(Handle &listener) { 392 | if (listener) { 393 | auto className = listener->GetType()->GetName(); 394 | for (auto &[modName, mod] : modSettings.mods) { 395 | if (mod.classes.contains(className)) { 396 | mod.classes[className].UnregisterListener(listener); 397 | } 398 | } 399 | } 400 | } 401 | 402 | // friend struct Red ::TypeInfoBuilder()>; 403 | // friend class Red ::ClassDescriptor; 404 | 405 | CClass *ModSettings::GetNativeType() { return Red::GetClass(); } 406 | 407 | Memory::IAllocator *ModSettings::GetAllocator() { return Memory::RTTIAllocator::Get(); } 408 | 409 | } // namespace ModSettings 410 | 411 | RTTI_DEFINE_CLASS(ModSettings::ModSettings, "ModSettings", { 412 | RTTI_METHOD(GetInstance); 413 | RTTI_METHOD(GetMods); 414 | RTTI_METHOD(GetVars); 415 | RTTI_METHOD(GetCategories); 416 | RTTI_METHOD(AcceptChanges); 417 | RTTI_METHOD(RejectChanges); 418 | RTTI_METHOD(RestoreDefaults); 419 | RTTI_METHOD(RegisterListenerToClass); 420 | RTTI_METHOD(UnregisterListenerToClass); 421 | RTTI_METHOD(RegisterListenerToModifications); 422 | RTTI_METHOD(UnregisterListenerToModifications); 423 | RTTI_PROPERTY(changeMade); 424 | }); -------------------------------------------------------------------------------- /src/red4ext/ModSettings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Utils.hpp" 4 | #include "Variable.hpp" 5 | #include 6 | 7 | namespace ModSettings { 8 | 9 | extern const Sdk *sdk; 10 | extern PluginHandle pluginHandle; 11 | 12 | struct ScriptData; 13 | 14 | class ModSettings : public IScriptable { 15 | public: 16 | ModSettings(); 17 | 18 | virtual CClass *GetNativeType() override; 19 | virtual Memory::IAllocator *GetAllocator() override; 20 | virtual bool CanBeDestructed() override { 21 | return false; 22 | } 23 | 24 | static Handle GetInstance(); 25 | 26 | static void ProcessScriptData(ScriptData *scriptData); 27 | static bool GetSettingString(CName, CName, CString *); 28 | static void ReadValueFromFile(ScriptProperty *prop, ScriptInstance pointer); 29 | 30 | template 31 | static void ReadValueFromFile(CName className, CName propertyName, 32 | T *pointer) { 33 | auto self = ModSettings::GetInstance(); 34 | CString settingFromFile; 35 | if (ModSettings::GetSettingString(className, propertyName, 36 | &settingFromFile)) { 37 | RedTypeFromString(pointer, settingFromFile); 38 | } 39 | } 40 | 41 | static void ClearVariables(); 42 | // static void AddVariable(ModSettingsVariable *); 43 | 44 | static void WriteToFile(); 45 | static void ReadFromFile(); 46 | static void AcceptChanges(); 47 | static void RestoreDefaults(CName modName); 48 | static void RejectChanges(); 49 | static void RegisterListenerToModifications(Handle& listener); 50 | static void UnregisterListenerToModifications(Handle& listener); 51 | static void RegisterListenerToClass(Handle& listener); 52 | static void UnregisterListenerToClass(Handle& listener); 53 | 54 | static DynArray GetMods(); 55 | static DynArray GetCategories(CName modName); 56 | static DynArray> GetVars(CName modName, CName categoryName); 57 | 58 | void NotifyListeners(); 59 | 60 | bool changeMade = false; 61 | 62 | static_assert(Detail::IsAllocator, 63 | "Memory::RTTIAllocator" 64 | " is not a valid allocator type"); 65 | private: 66 | SharedMutex variables_lock; 67 | std::map> listeners; 68 | SharedMutex listeners_lock; 69 | std::map mods; 70 | }; 71 | 72 | } // namespace ModSettings -------------------------------------------------------------------------------- /src/red4ext/RuntimeVariable.cpp: -------------------------------------------------------------------------------- 1 | #include "RuntimeVariable.hpp" 2 | 3 | -------------------------------------------------------------------------------- /src/red4ext/RuntimeVariable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "IRuntimeVariable.hpp" 3 | #include "ScriptDefinitions/ScriptProperty.hpp" 4 | #include "ModSettings.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ModSettings { 10 | 11 | template struct RuntimeVariable : public IRuntimeVariable { 12 | RuntimeVariable(ScriptProperty *prop) : IRuntimeVariable(prop) { 13 | T value, defaultValue; 14 | prop->ReadDefaultValue(&defaultValue); 15 | UpdateDefault(&defaultValue); 16 | value = defaultValue; 17 | ModSettings::ReadValueFromFile(prop, &value); 18 | UpdateAll(&value); 19 | } 20 | 21 | RuntimeVariable(RED4ext::CName className, RED4ext::CName propertyName, RED4ext::CName displayName, 22 | RED4ext::CName description, uint32_t order, T defaultValue) 23 | : IRuntimeVariable(className, propertyName, displayName, description, order) { 24 | UpdateDefault(&defaultValue); 25 | T value = defaultValue; 26 | ModSettings::ReadValueFromFile(className, propertyName, &value); 27 | UpdateAll(&value); 28 | } 29 | 30 | // overrides 31 | 32 | virtual inline bool __fastcall WasModifiedSinceLastSave() override { 33 | return importPolicy != RED4ext::user::EConfigVarImportPolicy::Ignore && valueWrittenToFile != valueValidated; 34 | } 35 | 36 | virtual inline bool __fastcall HasChange() override { return valueInput != valueValidated; } 37 | 38 | virtual inline bool __fastcall IsDefault() override { 39 | T value; 40 | if (HasChange()) { 41 | value = valueInput; 42 | } else { 43 | value = valueValidated; 44 | } 45 | return value == defaultValue; 46 | } 47 | 48 | virtual inline bool __fastcall RestoreDefault(uint8_t a1) override { 49 | auto wasDefault = IsDefault(); 50 | if (wasDefault) 51 | return !wasDefault; 52 | unk44 = a1; 53 | if (((a1 - 2) & 0xFD) != 0) { 54 | if (((a1 - 1) & 0xFD) != 0) { 55 | if (!a1) { 56 | UpdateValue(&defaultValue); 57 | // UserSettings = GetUserSettings(); 58 | // AddSettingsDataToSettings(UserSettings, a1); 59 | } 60 | return !wasDefault; 61 | } 62 | UpdateImmediately: 63 | UpdateValue(&defaultValue); 64 | // v9 = GetUserSettings(); 65 | // sub_7FF62769C390(v9, a1); 66 | return !wasDefault; 67 | } 68 | switch (updatePolicy) { 69 | case RED4ext::user::EConfigVarUpdatePolicy::Disabled: 70 | return !wasDefault; 71 | case RED4ext::user::EConfigVarUpdatePolicy::Immediately: 72 | goto UpdateImmediately; 73 | case RED4ext::user::EConfigVarUpdatePolicy::ConfirmationRequired: 74 | UpdateValue(&defaultValue); 75 | // v8 = GetUserSettings(); 76 | // SettingsConfirmChange_0(v8, a1); 77 | break; 78 | case RED4ext::user::EConfigVarUpdatePolicy::RestartRequired: 79 | UpdateValue(&defaultValue); 80 | // v7 = GetUserSettings(); 81 | // SettingsRestartRequired_0(v7, a1); 82 | break; 83 | case RED4ext::user::EConfigVarUpdatePolicy::LoadLastCheckpointRequired: 84 | UpdateValue(&defaultValue); 85 | // v6 = GetUserSettings(); 86 | // SettingsLoadLastCheckpoint_0(v6, a1); 87 | break; 88 | default: 89 | // LogError_f("E:\\R6.Release\\dev\\src\\common\\redConfig\\include\\inGameConfigVar.hpp", 44, line, 90 | // "Unknown in-game config var update policy (%d)", (unsigned __int8)a1->updatePolicy); 91 | //__debugbreak(); 92 | break; 93 | } 94 | return !wasDefault; 95 | } 96 | 97 | virtual inline void __fastcall UpdateValue(void *value) override { 98 | valueInput = *(T *)value; 99 | auto ms = ModSettings::GetInstance(); 100 | ms->changeMade = true; 101 | ms->NotifyListeners(); 102 | } 103 | 104 | virtual inline void __fastcall ApplyChange() override { valueValidated = valueInput; } 105 | 106 | virtual inline void __fastcall RevertChange() override { valueInput = valueValidated; } 107 | 108 | virtual inline void __fastcall ChangeWasWritten() override { valueWrittenToFile = valueValidated; } 109 | 110 | virtual inline void __fastcall UpdateAll(void *value) override { 111 | valueWrittenToFile = *(T *)value; 112 | valueInput = *(T *)value; 113 | valueValidated = *(T *)value; 114 | } 115 | 116 | // custom virtuals 117 | 118 | virtual void __fastcall GetValueToWrite(char *value) override; 119 | 120 | virtual inline RED4ext::ScriptInstance *__fastcall GetInputValue() override { 121 | return (RED4ext::ScriptInstance *)&valueInput; 122 | } 123 | 124 | virtual inline RED4ext::ScriptInstance *__fastcall GetValuePtr() override { 125 | return (RED4ext::ScriptInstance *)&valueValidated; 126 | } 127 | 128 | virtual inline void __fastcall UpdateDefault(void *value) override { defaultValue = *(T *)value; } 129 | 130 | T valueValidated; 131 | T defaultValue; 132 | T valueInput; 133 | T valueWrittenToFile; 134 | }; 135 | 136 | template struct RuntimeVariableRange : RuntimeVariable { 137 | inline RuntimeVariableRange(ScriptProperty *prop) : RuntimeVariable(prop) { 138 | this->type = RED4ext::user::EConfigVarType::Int; 139 | prop->ReadProperty("ModSettings.step", &this->stepValue, (T)1); 140 | prop->ReadProperty("ModSettings.min", &this->minValue, (T)0); 141 | prop->ReadProperty("ModSettings.max", &this->maxValue, (T)10); 142 | } 143 | 144 | inline RuntimeVariableRange(CName className, CName propertyName, CName displayName, CName description, uint32_t order, 145 | T defaultValue, T stepValue, T minValue, T maxValue) 146 | : RuntimeVariable(className, propertyName, displayName, description, order, defaultValue) { 147 | this->type = RED4ext::user::EConfigVarType::Int; 148 | this->stepValue = stepValue; 149 | this->minValue = minValue; 150 | this->maxValue = maxValue; 151 | } 152 | 153 | T minValue; 154 | T maxValue; 155 | T stepValue; 156 | }; 157 | 158 | template struct RuntimeVariableList : public RuntimeVariable { 159 | inline RuntimeVariableList(ScriptProperty *prop) : RuntimeVariable(prop) {} 160 | inline RuntimeVariableList(CName className, CName propertyName, CName displayName, CName description, uint32_t order, 161 | uint32_t defaultValue) 162 | : RuntimeVariable(className, propertyName, displayName, description, order, defaultValue) { 163 | } 164 | T value; 165 | RED4ext::DynArray values; 166 | RED4ext::DynArray displayValues; 167 | }; 168 | 169 | struct RuntimeVariableBool : RuntimeVariable { 170 | inline RuntimeVariableBool(ScriptProperty *prop) : RuntimeVariable(prop) { 171 | this->type = RED4ext::user::EConfigVarType::Bool; 172 | } 173 | inline RuntimeVariableBool(CName className, CName propertyName, CName displayName, CName description, uint32_t order, 174 | bool defaultValue) 175 | : RuntimeVariable(className, propertyName, displayName, description, order, defaultValue) { 176 | this->type = RED4ext::user::EConfigVarType::Bool; 177 | } 178 | }; 179 | 180 | struct RuntimeVariableName : RuntimeVariable { 181 | inline RuntimeVariableName(ScriptProperty *prop) : RuntimeVariable(prop) { 182 | this->type = RED4ext::user::EConfigVarType::Name; 183 | } 184 | inline RuntimeVariableName(CName className, CName propertyName, CName displayName, CName description, uint32_t order, 185 | CName defaultValue) 186 | : RuntimeVariable(className, propertyName, displayName, description, order, defaultValue) { 187 | this->type = RED4ext::user::EConfigVarType::Name; 188 | } 189 | }; 190 | 191 | // i don't think these will matter here 192 | // RED4EXT_ASSERT_SIZE(RuntimeVariableBool, 0x4E); 193 | // RED4EXT_ASSERT_OFFSET(RuntimeVariable, valueValidated, 0x48); 194 | // RED4EXT_ASSERT_OFFSET(RuntimeVariable, defaultValue, 0x49); 195 | // RED4EXT_ASSERT_OFFSET(RuntimeVariable, valueInput, 0x4A); 196 | // RED4EXT_ASSERT_OFFSET(RuntimeVariable, valueWrittenToFile, 0x4B); 197 | // char (*__kaboom)[offsetof(RuntimeVariableBool, valueValidated)] = 1; 198 | 199 | template <> inline RuntimeVariableRange::RuntimeVariableRange(ScriptProperty *prop) : RuntimeVariable(prop) { 200 | this->type = RED4ext::user::EConfigVarType::Float; 201 | prop->ReadProperty("ModSettings.step", &this->stepValue, 0.05f); 202 | prop->ReadProperty("ModSettings.min", &this->minValue, 0.0f); 203 | prop->ReadProperty("ModSettings.max", &this->maxValue, 1.0f); 204 | } 205 | 206 | template <> inline RuntimeVariableRange::RuntimeVariableRange(CName className, CName propertyName, CName displayName, CName description, uint32_t order, 207 | float defaultValue, float stepValue, float minValue, float maxValue) 208 | : RuntimeVariable(className, propertyName, displayName, description, order, defaultValue) { 209 | this->type = RED4ext::user::EConfigVarType::Float; 210 | this->stepValue = stepValue; 211 | this->minValue = minValue; 212 | this->maxValue = maxValue; 213 | } 214 | 215 | template <> inline void __fastcall RuntimeVariable::GetValueToWrite(char *value) { 216 | sprintf(value, "%d", valueValidated); 217 | } 218 | 219 | template <> inline void __fastcall RuntimeVariable::GetValueToWrite(char *value) { 220 | sprintf(value, "%d", valueValidated); 221 | } 222 | 223 | template <> inline void __fastcall RuntimeVariable::GetValueToWrite(char *value) { 224 | sprintf(value, "%d", valueValidated); 225 | } 226 | 227 | template <> inline void __fastcall RuntimeVariable::GetValueToWrite(char *value) { 228 | sprintf(value, "%f", valueValidated); 229 | } 230 | 231 | struct RuntimeVariableEnum : public RuntimeVariableList { 232 | RuntimeVariableEnum(ScriptProperty *prop) : RuntimeVariableList(prop) { 233 | this->type = RED4ext::user::EConfigVarType::IntList; 234 | 235 | values = RED4ext::DynArray(new RED4ext::Memory::DefaultAllocator()); 236 | displayValues = RED4ext::DynArray(new RED4ext::Memory::DefaultAllocator()); 237 | 238 | auto e = RED4ext::CRTTISystem::Get()->GetEnumByScriptName(prop->type->name); 239 | if (e) { 240 | const auto displayValuesPrefix = RED4ext::FNV1a64("ModSettings.displayValues."); 241 | 242 | for (const auto &value : e->hashList) { 243 | const auto displayValueAttr = RED4ext::FNV1a64(value.ToString(), displayValuesPrefix); 244 | const auto displayValue = prop->runtimeProperties.Get(displayValueAttr); 245 | if (displayValue) { 246 | RED4ext::CNamePool::Add(displayValue->c_str()); 247 | displayValues.EmplaceBack(displayValue->c_str()); 248 | } else { 249 | displayValues.EmplaceBack(value); 250 | } 251 | } 252 | 253 | for (const auto &value : e->valueList) { 254 | values.EmplaceBack((int32_t)value); 255 | } 256 | 257 | bitfield.listHasDisplayValues = true; 258 | } 259 | } 260 | }; 261 | 262 | } // namespace ModSettings -------------------------------------------------------------------------------- /src/red4ext/ScriptData.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ScriptDefinitions/ScriptDefinitions.hpp" 3 | 4 | namespace ModSettings { 5 | 6 | struct ScriptData { 7 | RED4ext::HashMap unk00; 8 | RED4ext::HashMap unk30; 9 | RED4ext::HashMap unk60; 10 | RED4ext::DynArray files; 11 | RED4ext::DynArray functions; 12 | RED4ext::DynArray enums; 13 | RED4ext::DynArray unkC0; 14 | RED4ext::DynArray classes; 15 | RED4ext::DynArray types; 16 | RED4ext::DynArray strings; 17 | RED4ext::HashMap unk100; 18 | uint8_t unk60MUTX; 19 | void *unk138; 20 | }; 21 | 22 | } -------------------------------------------------------------------------------- /src/red4ext/ScriptDefinitions/ScriptDefinitions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "RED4ext/RTTITypes.hpp" 4 | #include 5 | 6 | namespace ModSettings { 7 | 8 | struct ScriptProperty; 9 | 10 | enum class EDefinitionType : char { 11 | Type = 0x0, 12 | Class = 0x1, 13 | Constant = 0x2, 14 | Enum = 0x3, 15 | Bitfield = 0x4, 16 | Function = 0x5, 17 | Parameter = 0x6, 18 | LocalVar = 0x7, 19 | Property = 0x8, 20 | SourceFile = 0x9, 21 | }; 22 | 23 | enum class EScriptType : long { 24 | Simple = 0x0, 25 | Cls = 0x1, 26 | Handle = 0x2, 27 | WeakHandle = 0x3, 28 | Array = 0x4, 29 | NativeArray = 0x5 30 | }; 31 | 32 | enum class EVisibility : char { 33 | Public = 0x0, 34 | Protected = 0x1, 35 | Private = 0x2, 36 | }; 37 | 38 | struct ScriptClass; 39 | 40 | struct ScriptDefinition { 41 | virtual RED4ext::Memory::IAllocator *dstr() = 0; 42 | virtual void sub_08() = 0; 43 | virtual EDefinitionType GetDefinitionType() = 0; 44 | virtual EVisibility GetVisibility() = 0; 45 | virtual ScriptClass GetParent() = 0; 46 | virtual void sub_28() = 0; 47 | virtual bool IsNative() = 0; 48 | 49 | RED4ext::CName name; 50 | uint64_t unk10; 51 | }; 52 | 53 | struct ScriptType : ScriptDefinition { 54 | RED4ext::CBaseRTTIType *rttiType; 55 | ScriptClass *innerType; 56 | uint32_t unk28; 57 | EScriptType type; 58 | }; 59 | 60 | struct ScriptClassFlags { 61 | uint32_t isNative : 1; 62 | uint32_t isAbstract : 1; 63 | uint32_t isFinal : 1; 64 | uint32_t isStruct : 1; 65 | uint32_t hasFunctions : 1; 66 | uint32_t hasFields : 1; 67 | uint32_t isImportOnly : 1; 68 | uint32_t isTestOnly : 1; 69 | uint32_t hasOverrides : 1; 70 | uint32_t unk200 : 1; 71 | uint32_t unk400 : 1; 72 | }; 73 | 74 | struct ScriptClass : ScriptDefinition { 75 | RED4ext::CClass *rttiType; 76 | ScriptClass *parent; 77 | RED4ext::DynArray properties; 78 | RED4ext::DynArray overrides; 79 | RED4ext::DynArray functions; 80 | __unaligned __declspec(align(1)) RED4ext::HashMap unk58; 81 | uint8_t visibility; 82 | uint8_t unk89; 83 | uint8_t unk8A; 84 | uint8_t unk8B; 85 | ScriptClassFlags flags; 86 | }; 87 | RED4EXT_ASSERT_SIZE(ScriptClass, 0x90); 88 | // char (*__kaboom)[offsetof(ScriptClass, rttiType)] = 1; 89 | 90 | } -------------------------------------------------------------------------------- /src/red4ext/ScriptDefinitions/ScriptHost.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace ModSettings { 4 | 5 | struct ScriptFile { 6 | RED4ext::CName name; 7 | RED4ext::CString filename; 8 | }; 9 | 10 | enum EBreakpointState : unsigned __int8 { 11 | Continue = 0x0, 12 | StepOver = 0x1, 13 | StepInto = 0x2, 14 | StepOut = 0x3, 15 | Pause = 0x4, 16 | }; 17 | 18 | struct ScriptHost { 19 | void *vft1; 20 | void *vft2; 21 | RED4ext::DynArray unk10; 22 | uint32_t unk20; 23 | uint16_t unk24; 24 | uint16_t unk26; 25 | uint64_t unk28; 26 | RED4ext::DynArray scriptFileIndexes; 27 | RED4ext::DynArray scriptFiles; 28 | uint64_t unk50; 29 | uint32_t breakpointThread; 30 | EBreakpointState breakpointState; 31 | uint8_t unk5D; 32 | uint8_t unk5E; 33 | uint8_t unk5F; 34 | uint64_t unk60; 35 | RED4ext::HashMap unk68; 36 | RED4ext::SharedMutex unk68MUTX; 37 | uint8_t unk99; 38 | uint8_t unk9A; 39 | uint8_t unk9B; 40 | uint8_t unk9C; 41 | uint8_t unk9D; 42 | uint8_t unk9E; 43 | uint8_t unk9F; 44 | void *psa; 45 | }; 46 | 47 | } -------------------------------------------------------------------------------- /src/red4ext/ScriptDefinitions/ScriptProperty.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Variable.hpp" 4 | #include 5 | #include 6 | #include "ScriptDefinitions/ScriptDefinitions.hpp" 7 | 8 | namespace ModSettings { 9 | 10 | struct ScriptPropertyFlags { 11 | uint16_t isNative : 1; 12 | uint16_t isEditable : 1; 13 | uint16_t isInline : 1; 14 | uint16_t isConst : 1; 15 | uint16_t isReplicated : 1; 16 | uint16_t hasHint : 1; 17 | uint16_t isInstanceEditable : 1; 18 | uint16_t hasDefault : 1; 19 | uint16_t isPersistent : 1; 20 | uint16_t isTestOnly : 1; 21 | uint16_t isMutable : 1; 22 | uint16_t isBrowsable : 1; 23 | }; 24 | 25 | inline void trim(std::string& s) { 26 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { 27 | return !std::isspace(ch); 28 | })); 29 | s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { 30 | return !std::isspace(ch); 31 | }).base(), s.end()); 32 | } 33 | 34 | struct ScriptProperty : ScriptDefinition { 35 | bool IsValid() const { 36 | return this->runtimeProperties.Get("ModSettings.mod"); 37 | } 38 | 39 | RED4ext::CBaseRTTIType * GetType() const { 40 | return RED4ext::CRTTISystem::Get()->GetType(this->type->name); 41 | } 42 | 43 | void FromString(RED4ext::ScriptInstance pointer, const RED4ext::CString& str) const { 44 | RED4ext::RawBuffer buffer; 45 | buffer.data = (void*)str.c_str(); 46 | buffer.size = str.Length(); 47 | this->GetType()->FromString(pointer, buffer); 48 | } 49 | 50 | // ReadProperty(CName) 51 | 52 | template T ReadProperty(const RED4ext::CName &name) const { 53 | auto str = this->runtimeProperties.Get(name); 54 | if (str) { 55 | return RED4ext::CNamePool::Add(str->c_str()); 56 | } else { 57 | return 0LLU; 58 | } 59 | } 60 | 61 | template <> std::string ReadProperty(const RED4ext::CName &name) const { 62 | auto str = this->runtimeProperties.Get(name); 63 | if (str) { 64 | return std::string(str->c_str()); 65 | } else { 66 | return std::string(); 67 | } 68 | } 69 | 70 | template <> uint32_t ReadProperty(const RED4ext::CName &name) const { 71 | auto str = this->runtimeProperties.Get(name); 72 | if (str) { 73 | uint32_t value; 74 | RED4ext::RawBuffer buffer; 75 | buffer.data = (void*)str->c_str(); 76 | buffer.size = str->length; 77 | RED4ext::CRTTISystem::Get()->GetType("Uint32")->FromString(&value, buffer); 78 | return value; 79 | } else { 80 | return 0; 81 | } 82 | } 83 | 84 | template <> int32_t ReadProperty(const RED4ext::CName &name) const { 85 | auto str = this->runtimeProperties.Get(name); 86 | if (str) { 87 | int32_t value; 88 | RED4ext::RawBuffer buffer; 89 | buffer.data = (void*)str->c_str(); 90 | buffer.size = str->length; 91 | RED4ext::CRTTISystem::Get()->GetType("Int32")->FromString(&value, buffer); 92 | return value; 93 | } else { 94 | return 0; 95 | } 96 | } 97 | 98 | ModSettingDependency* ReadDependency(const RED4ext::CName scriptClass) { 99 | auto dependency = (ModSettingDependency *)calloc(sizeof(ModSettingDependency), sizeof(char*)); 100 | auto str = this->runtimeProperties.Get("ModSettings.dependency"); 101 | if (str) { 102 | std::string depends(str->c_str()); 103 | trim(depends); 104 | if (!depends.empty()) { 105 | auto period = depends.find("."); 106 | auto equals = depends.find("="); 107 | if (equals != std::string::npos) { 108 | auto value = depends.substr(equals, depends.length()); 109 | trim(value); 110 | if (value.length()) { 111 | dependency->value = value; 112 | } 113 | } else { 114 | equals = depends.length(); 115 | dependency->value = "true"; 116 | } 117 | if (period != std::string::npos) { 118 | auto className = depends.substr(0, period); 119 | auto propertyName = depends.substr(period+1, equals); 120 | trim(className); 121 | trim(propertyName); 122 | dependency->propertyName = RED4ext::CNamePool::Add(propertyName.c_str()); 123 | dependency->className = RED4ext::CNamePool::Add(className.c_str()); 124 | } else { 125 | dependency->className = scriptClass; 126 | dependency->propertyName = RED4ext::CNamePool::Add(depends.c_str()); 127 | } 128 | } 129 | } 130 | return dependency; 131 | } 132 | 133 | // ReadProperty(CName, &) 134 | 135 | template void ReadProperty(const RED4ext::CName &name, T *pointer) const { 136 | auto str = this->runtimeProperties.Get(name); 137 | if (str && pointer) { 138 | this->FromString(pointer, *str); 139 | } 140 | } 141 | 142 | template <> void ReadProperty(const RED4ext::CName &name, uint32_t *pointer) const { 143 | auto str = this->runtimeProperties.Get(name); 144 | if (str && pointer) { 145 | RED4ext::RawBuffer buffer; 146 | buffer.data = (void*)str->c_str(); 147 | buffer.size = str->length; 148 | RED4ext::CRTTISystem::Get()->GetType("Uint32")->FromString(pointer, buffer); 149 | } 150 | } 151 | 152 | template <> void ReadProperty(const RED4ext::CName &name, int32_t *pointer) const { 153 | auto str = this->runtimeProperties.Get(name); 154 | if (str && pointer) { 155 | RED4ext::RawBuffer buffer; 156 | buffer.data = (void*)str->c_str(); 157 | buffer.size = str->length; 158 | RED4ext::CRTTISystem::Get()->GetType("Int32")->FromString(pointer, buffer); 159 | } 160 | } 161 | 162 | template <> void ReadProperty(const RED4ext::CName &name, float *pointer) const { 163 | auto str = this->runtimeProperties.Get(name); 164 | if (str && pointer) { 165 | RED4ext::RawBuffer buffer; 166 | buffer.data = (void*)str->c_str(); 167 | buffer.size = str->length; 168 | RED4ext::CRTTISystem::Get()->GetType("Float")->FromString(pointer, buffer); 169 | } 170 | } 171 | 172 | template <> void ReadProperty(const RED4ext::CName &name, RED4ext::CName *pointer) const { 173 | auto str = this->runtimeProperties.Get(name); 174 | if (str && pointer) { 175 | *pointer = RED4ext::CNamePool::Add(str->c_str()); 176 | } 177 | } 178 | 179 | template <> void ReadProperty(const RED4ext::CName &name, RED4ext::CString *pointer) const { 180 | auto str = this->runtimeProperties.Get(name); 181 | if (str && pointer) { 182 | *pointer = *str; 183 | } 184 | } 185 | 186 | template <> void ReadProperty(const RED4ext::CName &name, char ** pointer) const { 187 | auto str = this->runtimeProperties.Get(name); 188 | if (str && pointer) { 189 | std::strcpy(*pointer, str->c_str()); 190 | } 191 | } 192 | 193 | // ReadProperty(CName, &, fallback) 194 | 195 | template void ReadProperty(const RED4ext::CName &name, T *pointer, const T fallback) const { 196 | auto str = this->runtimeProperties.Get(name); 197 | if (str && pointer) { 198 | this->FromString(pointer, *str); 199 | } else if (pointer) { 200 | *pointer = fallback; 201 | } 202 | } 203 | 204 | template <> 205 | void ReadProperty(const RED4ext::CName &name, RED4ext::CName *pointer, const RED4ext::CName fallback) const { 206 | auto str = this->runtimeProperties.Get(name); 207 | if (str && pointer) { 208 | *pointer = RED4ext::CNamePool::Add(str->c_str()); 209 | } else if (pointer && fallback) { 210 | *pointer = fallback; 211 | } 212 | } 213 | 214 | void ReadDefaultValue(RED4ext::ScriptInstance pointer) const { 215 | if (this->defaultValues.size) { 216 | this->FromString(pointer, this->defaultValues[0]); 217 | } 218 | } 219 | 220 | RED4ext::CProperty *rttiProperty; 221 | ScriptDefinition *parent; 222 | ScriptPropertyFlags flags; 223 | uint16_t unk2A[3]; 224 | RED4ext::DynArray defaultClasss; 225 | RED4ext::DynArray defaultValues; 226 | uint64_t unk50; 227 | EVisibility unk58; 228 | uint8_t unk59[7]; 229 | RED4ext::HashMap runtimeProperties; 230 | ScriptType *type; 231 | }; 232 | RED4EXT_ASSERT_SIZE(ScriptProperty, 0x98); 233 | RED4EXT_ASSERT_OFFSET(ScriptProperty, runtimeProperties, 0x60); 234 | 235 | } -------------------------------------------------------------------------------- /src/red4ext/Scripting/Common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Engine 11 | { 12 | namespace detail 13 | { 14 | template 15 | class TypeDescriptor 16 | { 17 | public: 18 | inline static RED4ext::CBaseRTTIType* GetType() 19 | { 20 | return s_type; 21 | } 22 | 23 | protected: 24 | inline static RED4ext::CBaseRTTIType* s_type; 25 | }; 26 | 27 | template 28 | concept IsCompatible = std::is_same_v or std::is_base_of_v; 29 | 30 | template 31 | concept IsScripable = IsCompatible; 32 | 33 | template 34 | concept IsTypeInfoOrVoid = IsCompatible or std::is_same_v; 35 | 36 | template 37 | concept IsMemberCallCompatible = IsScripable and IsCompatible and sizeof(T) == sizeof(U); 38 | 39 | template 40 | concept HasTypeGetter = requires(T) 41 | { 42 | { T::GetRTTIType() } -> std::convertible_to; 43 | }; 44 | 45 | template 46 | concept HasTypeNameGetter = requires(T) 47 | { 48 | { T::GetRTTIName() } -> std::same_as; 49 | }; 50 | 51 | template 52 | concept HasTypeNameConst = requires(T) 53 | { 54 | { T::RTTIName } -> std::convertible_to; 55 | }; 56 | 57 | template 58 | concept HasTypeFlagsConst = requires(T) 59 | { 60 | { T::RTTIFlags } -> std::convertible_to; 61 | }; 62 | 63 | template 64 | concept HasGeneratedNameConst = requires(T) 65 | { 66 | { T::NAME } -> std::convertible_to; 67 | { T::ALIAS } -> std::convertible_to; 68 | }; 69 | 70 | template 71 | struct Specialization : public std::false_type {}; 72 | 73 | template class G, typename A> 74 | struct Specialization> : public std::true_type 75 | { 76 | using argument_type = A; 77 | }; 78 | 79 | template class G, typename A, typename... Args> 80 | struct Specialization> : public std::true_type 81 | { 82 | using argument_type = A; 83 | }; 84 | 85 | template 86 | concept IsSpecialization = Specialization::value; 87 | 88 | template 89 | struct RedSpecialization : public std::false_type {}; 90 | 91 | template 92 | struct RedSpecialization> : Specialization> 93 | { 94 | static constexpr auto prefix = "array:"; 95 | }; 96 | 97 | template 98 | struct RedSpecialization> : Specialization> 99 | { 100 | static constexpr auto prefix = "handle:"; 101 | }; 102 | 103 | template 104 | struct RedSpecialization> : Specialization> 105 | { 106 | static constexpr auto prefix = "whandle:"; 107 | }; 108 | 109 | template 110 | struct RedSpecialization> : Specialization> 111 | { 112 | static constexpr auto prefix = "script_ref:"; 113 | }; 114 | 115 | template 116 | concept IsRedSpecialization = RedSpecialization::value; 117 | 118 | template 119 | struct FunctionPtr : public std::false_type {}; 120 | 121 | template 122 | struct FunctionPtr : public std::true_type 123 | { 124 | using context_type = void; 125 | using return_type = R; 126 | using arguments_type = std::tuple>...>; 127 | template 128 | using argument_type = typename std::tuple_element::type; 129 | using qualified_arguments_type = std::tuple; 130 | template 131 | using qualified_argument_type = typename std::tuple_element::type; 132 | static constexpr size_t arity = sizeof...(Args); 133 | }; 134 | 135 | template 136 | struct FunctionPtr : FunctionPtr 137 | { 138 | using context_type = C; 139 | }; 140 | 141 | template 142 | concept IsFunctionPtr = FunctionPtr::value; 143 | 144 | template 145 | concept IsStaticFunctionPtr = FunctionPtr::value && std::is_void_v::context_type>; 146 | 147 | template 148 | concept IsMemberFunctionPtr = FunctionPtr::value && !std::is_void_v::context_type>; 149 | 150 | namespace TypeNameExtractor 151 | { 152 | template 153 | consteval const auto& GetSelfSignature() 154 | { 155 | #ifdef _MSC_VER 156 | return __FUNCSIG__; 157 | #else 158 | return __PRETTY_FUNCTION__; 159 | #endif 160 | } 161 | 162 | consteval std::pair GetAbsoluteBounds() 163 | { 164 | constexpr auto test = "bool"; 165 | constexpr auto len = std::char_traits::length(test); 166 | 167 | const auto& str = GetSelfSignature(); 168 | 169 | for (size_t i = 0; i < sizeof(str) - len; ++i) 170 | { 171 | if (std::char_traits::compare(str + i, test, len) == 0) 172 | return { i, sizeof(str) - len - i }; 173 | } 174 | 175 | return { 0, 0 }; 176 | } 177 | 178 | template 179 | consteval std::pair GetTypeNameBounds() 180 | { 181 | const auto bounds = GetAbsoluteBounds(); 182 | const auto& str = GetSelfSignature(); 183 | 184 | auto left = bounds.first; 185 | auto right = bounds.second; 186 | 187 | if (std::char_traits::compare(str + left, "struct ", 7) == 0) 188 | left += 7; 189 | else if (std::char_traits::compare(str + left, "class ", 6) == 0) 190 | left += 6; 191 | else if (std::char_traits::compare(str + left, "enum ", 5) == 0) 192 | left += 5; 193 | 194 | for (auto i = sizeof(str) - right - 1; i > left; --i) 195 | { 196 | if (str[i] == '>') 197 | break; 198 | 199 | if (str[i] == ':') 200 | { 201 | left = i + 1; 202 | break; 203 | } 204 | } 205 | 206 | return { left, right }; 207 | } 208 | 209 | template 210 | consteval auto GeFullTypeNameArray() 211 | { 212 | using U = std::remove_pointer_t>; 213 | 214 | constexpr auto bounds = GetAbsoluteBounds(); 215 | constexpr auto& str = GetSelfSignature(); 216 | constexpr auto len = sizeof(str) - bounds.first - bounds.second + 1; 217 | 218 | std::array name{}; 219 | 220 | for (auto i = 0; i < len - 1; ++i) 221 | name[i] = str[bounds.first + i]; 222 | 223 | return name; 224 | } 225 | 226 | template 227 | consteval auto GetShortTypeNameArray() 228 | { 229 | using U = std::remove_pointer_t>; 230 | 231 | constexpr auto bounds = GetTypeNameBounds(); 232 | constexpr auto& str = GetSelfSignature(); 233 | constexpr auto len = sizeof(str) - bounds.first - bounds.second + 1; 234 | 235 | std::array name{}; 236 | 237 | for (auto i = 0; i < len - 1; ++i) 238 | name[i] = str[bounds.first + i]; 239 | 240 | return name; 241 | } 242 | }; 243 | 244 | template 245 | const char* ExtractFullTypeName() 246 | { 247 | static auto name = TypeNameExtractor::GeFullTypeNameArray(); 248 | return name.data(); 249 | } 250 | 251 | template 252 | const char* ExtractShortTypeName() 253 | { 254 | static auto name = TypeNameExtractor::GetShortTypeNameArray(); 255 | return name.data(); 256 | } 257 | 258 | template 259 | struct TypeCase 260 | { 261 | using type = T; 262 | static constexpr auto name = N; 263 | }; 264 | 265 | template 266 | consteval bool TryTypeCase(RED4ext::CName& name) 267 | { 268 | if constexpr(std::is_same_v) 269 | { 270 | name = Case::name; 271 | return true; 272 | } 273 | 274 | return false; 275 | } 276 | 277 | template 278 | consteval RED4ext::CName MatchTypeCase() 279 | { 280 | RED4ext::CName name{}; 281 | (TryTypeCase(name) || ...); 282 | return name; 283 | } 284 | 285 | template 286 | consteval RED4ext::CName ResolveConstTypeName() 287 | { 288 | // if constexpr (HasTypeNameConst) 289 | // return RED4ext::FNV1a64(T::RTTIName, Prefix); 290 | 291 | if constexpr (HasGeneratedNameConst) 292 | return RED4ext::FNV1a64(T::NAME, Prefix); 293 | 294 | if constexpr (IsRedSpecialization) 295 | return ResolveConstTypeName::argument_type, 296 | RED4ext::FNV1a64(RedSpecialization::prefix, Prefix)>(); 297 | 298 | return MatchTypeCase< 299 | T, 300 | TypeCase, 301 | TypeCase, 302 | TypeCase, 303 | TypeCase, 304 | TypeCase, 305 | TypeCase, 306 | TypeCase, 307 | TypeCase, 308 | TypeCase, 309 | TypeCase, 310 | TypeCase, 311 | TypeCase, 312 | TypeCase, 313 | TypeCase, 314 | TypeCase, 315 | TypeCase, 316 | TypeCase 317 | >(); 318 | } 319 | 320 | template 321 | std::string BuildDynamicTypeName() 322 | { 323 | if constexpr (HasTypeNameGetter) 324 | return T::GetRTTIName().ToString(); 325 | 326 | if constexpr (IsRedSpecialization) 327 | return std::string(RedSpecialization::prefix) 328 | .append(BuildDynamicTypeName::argument_type>()); 329 | 330 | auto type = TypeDescriptor::GetType(); 331 | if (type) 332 | return type->GetName().ToString(); 333 | 334 | return {}; 335 | } 336 | 337 | template 338 | constexpr RED4ext::CName ResolveTypeName() 339 | { 340 | using U = std::remove_pointer_t>; 341 | 342 | constexpr auto name = ResolveConstTypeName(); 343 | 344 | if constexpr (!name.IsNone()) 345 | return name; 346 | 347 | if constexpr (HasTypeNameGetter) 348 | return U::GetRTTIName(); 349 | 350 | auto type = TypeDescriptor::GetType(); 351 | if (type) 352 | return type->GetName().ToString(); 353 | 354 | // To create a composite type (array, handle, etc.) it's enough to add 355 | // the full type name to the name pool. The RTTI system generates the 356 | // missing types dynamically if it can get the name as a string. 357 | return RED4ext::CNamePool::Add(BuildDynamicTypeName().c_str()); 358 | } 359 | 360 | template 361 | inline void ExtractArg(RED4ext::CStackFrame* aFrame, T* aArg) 362 | { 363 | RED4ext::GetParameter(aFrame, aArg); 364 | } 365 | 366 | template 367 | inline void ExtractArgs(RED4ext::CStackFrame* aFrame, std::tuple& aArgs, std::index_sequence) 368 | { 369 | (ExtractArg(aFrame, &std::get(aArgs)), ...); 370 | } 371 | 372 | template 373 | inline void ExtractArgs(RED4ext::CStackFrame* aFrame, std::tuple& aArgs) 374 | { 375 | ExtractArgs(aFrame, aArgs, std::make_index_sequence()); 376 | aFrame->code++; 377 | } 378 | } 379 | 380 | template 381 | requires detail::IsTypeInfoOrVoid 382 | using RTTIStaticFunction = void (*)(RED4ext::IScriptable*, RED4ext::CStackFrame*, R*, RT*); 383 | 384 | template 385 | requires detail::IsScripable and detail::IsTypeInfoOrVoid 386 | using RTTIMemberFunction = void (C::*)(RED4ext::CStackFrame*, R*, RT*); 387 | 388 | template 389 | using StaticFunction = R (*)(Args...); 390 | 391 | template 392 | using MemberFunction = R (C::*)(Args...); 393 | 394 | template 395 | requires detail::IsFunctionPtr 396 | inline RED4ext::ScriptingFunction_t WrapScriptableFunction() 397 | { 398 | using namespace detail; 399 | 400 | using F = decltype(AFunc); 401 | using C = typename FunctionPtr::context_type; 402 | using R = typename FunctionPtr::return_type; 403 | using Args = typename FunctionPtr::arguments_type; 404 | 405 | static const auto s_func = AFunc; 406 | 407 | auto f = [](RED4ext::IScriptable* aContext, RED4ext::CStackFrame* aFrame, 408 | R* aRet, RED4ext::CBaseRTTIType* aRetType) -> void 409 | { 410 | Args args; 411 | ExtractArgs(aFrame, args); 412 | 413 | if constexpr (std::is_void_v) 414 | { 415 | if constexpr (std::is_void_v) 416 | std::apply(s_func, args); 417 | else 418 | std::apply(s_func, std::tuple_cat(std::make_tuple(reinterpret_cast(aContext)), args)); 419 | } 420 | else 421 | { 422 | R ret; 423 | if constexpr (std::is_void_v) 424 | ret = std::apply(s_func, args); 425 | else 426 | ret = std::apply(s_func, std::tuple_cat(std::make_tuple(reinterpret_cast(aContext)), args)); 427 | 428 | if (aRet) { 429 | if (aRetType) { 430 | aRetType->Assign(aRet, &ret); 431 | } else { 432 | *aRet = ret; 433 | } 434 | } 435 | } 436 | }; 437 | 438 | return reinterpret_cast>(+f); 439 | } 440 | 441 | template 442 | void DescribeScriptableFunction(RED4ext::CBaseFunction* aScriptFunc, StaticFunction) 443 | { 444 | using namespace detail; 445 | 446 | (aScriptFunc->AddParam(ResolveTypeName(), "arg"), ...); 447 | 448 | if constexpr (!std::is_void_v) 449 | aScriptFunc->SetReturnType(ResolveTypeName()); 450 | } 451 | 452 | template 453 | void DescribeScriptableFunction(RED4ext::CBaseFunction* aScriptFunc, MemberFunction) 454 | { 455 | using namespace detail; 456 | 457 | (aScriptFunc->AddParam(ResolveTypeName(), "arg"), ...); 458 | 459 | if constexpr (!std::is_void_v) 460 | aScriptFunc->SetReturnType(ResolveTypeName()); 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /src/red4ext/Scripting/RTTIClass.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "RTTIRegistrar.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace Engine 10 | { 11 | template 12 | class RTTIClassDescriptor : public RED4ext::TTypedClass, public detail::TypeDescriptor 13 | { 14 | public: 15 | RTTIClassDescriptor() : RED4ext::TTypedClass(0ull) {} 16 | 17 | void SetName(const char* aName) 18 | { 19 | RED4ext::CClass::name = RED4ext::CNamePool::Add(aName); 20 | } 21 | 22 | void SetFlags(const RED4ext::CClass::Flags& aFlags) 23 | { 24 | RED4ext::CClass::flags = aFlags; 25 | } 26 | 27 | template 28 | RED4ext::CClassStaticFunction* AddFunction(RTTIStaticFunction aFunc, const char* aName, 29 | RED4ext::CBaseFunction::Flags aFlags = {}) 30 | { 31 | const auto* ptr = reinterpret_cast>(aFunc); 32 | auto* func = RED4ext::CClassStaticFunction::Create(this, aName, aName, ptr, aFlags); 33 | 34 | RED4ext::CClass::RegisterFunction(func); 35 | 36 | return func; 37 | } 38 | 39 | template 40 | requires std::is_same_v 41 | RED4ext::CClassStaticFunction* AddFunction(RTTIMemberFunction aFunc, const char* aName, 42 | RED4ext::CBaseFunction::Flags aFlags = {}) 43 | { 44 | const auto* ptr = reinterpret_cast>(aFunc); 45 | auto* func = RED4ext::CClassFunction::Create(this, aName, aName, ptr, aFlags); 46 | 47 | RED4ext::CClass::RegisterFunction(func); 48 | 49 | return func; 50 | } 51 | 52 | template 53 | requires detail::IsFunctionPtr 54 | RED4ext::CClassFunction* AddFunction(const char* aName, RED4ext::CBaseFunction::Flags aFlags = {}) 55 | { 56 | const auto* ptr = WrapScriptableFunction(); 57 | 58 | RED4ext::CClassFunction* func; 59 | if constexpr (detail::IsMemberFunctionPtr) 60 | func = RED4ext::CClassFunction::Create(this, aName, aName, ptr, aFlags); 61 | else 62 | func = RED4ext::CClassStaticFunction::Create(this, aName, aName, ptr, aFlags); 63 | 64 | DescribeScriptableFunction(func, AFunc); 65 | 66 | RED4ext::CClass::RegisterFunction(func); 67 | 68 | return func; 69 | } 70 | 71 | inline static void RegisterRTTI() 72 | { 73 | s_registrar.Register(); 74 | } 75 | 76 | inline static RED4ext::CClass* Get() 77 | { 78 | return s_class; 79 | } 80 | 81 | private: 82 | static void OnRegisterRTTI() 83 | { 84 | using FinalDescriptor = typename T::Descriptor; 85 | 86 | auto* desc = new FinalDescriptor(); 87 | 88 | // if constexpr (detail::HasTypeNameConst) 89 | // desc->SetName(T::RTTIName); 90 | // 91 | // if constexpr (detail::HasTypeFlagsConst) 92 | // desc->SetFlags(T::RTTIFlags); 93 | 94 | T::OnRegister(desc); 95 | 96 | if (desc->name.IsNone()) 97 | desc->SetName(detail::ExtractShortTypeName()); 98 | 99 | auto* rtti = RED4ext::CRTTISystem::Get(); 100 | rtti->RegisterType(desc); 101 | 102 | s_class = desc; 103 | detail::TypeDescriptor::s_type = desc; 104 | } 105 | 106 | static void OnPostRegisterRTTI() 107 | { 108 | using FinalDescriptor = typename T::Descriptor; 109 | using ParentType = typename T::Parent; 110 | 111 | auto* rtti = RED4ext::CRTTISystem::Get(); 112 | 113 | // Auto-fill parent 114 | if constexpr (detail::HasTypeGetter) 115 | s_class->parent = ParentType::GetRTTIType(); 116 | else if constexpr (detail::HasGeneratedNameConst) 117 | s_class->parent = rtti->GetClass(ParentType::NAME); 118 | else 119 | s_class->parent = rtti->GetClass("IScriptable"); 120 | 121 | auto* desc = reinterpret_cast(s_class); 122 | 123 | T::OnDescribe(desc, rtti); 124 | 125 | // Force native flag 126 | s_class->flags.isNative = true; 127 | } 128 | 129 | inline static RED4ext::CClass* s_class; 130 | inline static RTTIRegistrar s_registrar{ &OnRegisterRTTI, &OnPostRegisterRTTI }; // NOLINT(cert-err58-cpp) 131 | }; 132 | 133 | template 134 | requires detail::IsScripable

135 | class RTTIClass : public P 136 | { 137 | public: 138 | using Descriptor = RTTIClassDescriptor; 139 | using Parent = P; 140 | using Flags = RED4ext::CClass::Flags; 141 | 142 | RTTIClass() = default; 143 | 144 | RED4ext::CClass* GetNativeType() override 145 | { 146 | return Descriptor::Get(); 147 | } 148 | 149 | inline static RED4ext::Handle NewInstance(RED4ext::CClass* aDerived = nullptr) 150 | { 151 | // 1. Internal Handle 152 | // All IScriptable instances inherit WeakHandle .ref field from ISerializable. 153 | // This field is required for instance to be recognized by scripting engine. 154 | // Without this field initialized the scripted `this` will always be null. 155 | // When instantiated from scripts with `new Class()` the .ref is initialized 156 | // using the Handle constructed and returned by the `new` operator. 157 | // 2. Internal Class Pointer 158 | // Also IScriptable instances must have .unk30 set to the RTTI class instance. 159 | // This property is used by the engine when accessing class members. 160 | 161 | // TODO: Move to RED4ext::Handle? Or RED4ext::CClass::CreateHandle? 162 | 163 | // Resolve the type 164 | auto type = Descriptor::Get(); 165 | 166 | if (aDerived && aDerived->IsA(type)) 167 | type = aDerived; 168 | 169 | // Allocate and construct the instance 170 | auto instance = type->AllocInstance(); 171 | type->ConstructCls(instance); 172 | 173 | // Construct the handle 174 | auto scriptable = reinterpret_cast(instance); 175 | auto handle = RED4ext::Handle(scriptable); 176 | 177 | // Assign the handle to the instance 178 | scriptable->ref = RED4ext::WeakHandle(*reinterpret_cast*>(&handle)); 179 | 180 | // Assign the type to the instance 181 | scriptable->unk30 = type; 182 | 183 | return std::move(handle); 184 | } 185 | 186 | inline static RED4ext::CClass* GetRTTIType() 187 | { 188 | return Descriptor::Get(); 189 | } 190 | 191 | inline static RED4ext::CName GetRTTIName() 192 | { 193 | return Descriptor::Get()->GetName(); 194 | } 195 | 196 | inline static void RegisterRTTI() 197 | { 198 | Descriptor::RegisterRTTI(); 199 | } 200 | 201 | private: 202 | friend Descriptor; 203 | 204 | static void OnRegister(Descriptor* aType) {} 205 | static void OnDescribe(Descriptor* aType, RED4ext::CRTTISystem* aRtti) {} 206 | }; 207 | } 208 | -------------------------------------------------------------------------------- /src/red4ext/Scripting/RTTIEnum.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "RTTIRegistrar.hpp" 5 | 6 | #include 7 | 8 | namespace Engine 9 | { 10 | template 11 | requires std::is_enum_v 12 | class RTTIEnumSeq : RED4ext::CEnum, detail::TypeDescriptor 13 | { 14 | public: 15 | RTTIEnumSeq() : CEnum(0ull, sizeof(T), {}) 16 | { 17 | } 18 | 19 | void SetName(const char* aName) 20 | { 21 | RED4ext::CEnum::name = RED4ext::CNamePool::Add(aName); 22 | } 23 | 24 | void SetFlags(const RED4ext::CEnum::Flags& aFlags) 25 | { 26 | RED4ext::CEnum::flags = aFlags; 27 | } 28 | 29 | inline static void Register() 30 | { 31 | RED4ext::RTTIRegistrator::Add(&OnRegisterRTTI, &OnPostRegisterRTTI); 32 | } 33 | 34 | private: 35 | static void OnRegisterRTTI() 36 | { 37 | auto* type = new RTTIEnumSeq(); 38 | 39 | // T::OnRegister(); 40 | 41 | if (type->name.IsNone()) 42 | type->SetName(detail::ExtractShortTypeName()); 43 | 44 | auto* rtti = RED4ext::CRTTISystem::Get(); 45 | rtti->RegisterType(type); 46 | 47 | detail::TypeDescriptor::s_type = type; 48 | } 49 | 50 | static void OnPostRegisterRTTI() 51 | { 52 | } 53 | 54 | inline static RTTIRegistrar s_registrar{ &OnRegisterRTTI, &OnPostRegisterRTTI }; // NOLINT(cert-err58-cpp) 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/red4ext/Scripting/RTTIExpansion.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | #include "RTTIRegistrar.hpp" 5 | 6 | #include 7 | 8 | namespace Engine 9 | { 10 | template 11 | class RTTIClassExpander : public RED4ext::CClass 12 | { 13 | public: 14 | template 15 | RED4ext::CClassStaticFunction* AddFunction(RTTIStaticFunction aFunc, const char* aName, 16 | RED4ext::CBaseFunction::Flags aFlags = {}) 17 | { 18 | const auto* ptr = reinterpret_cast>(aFunc); 19 | auto* func = RED4ext::CClassStaticFunction::Create(this, aName, aName, ptr, aFlags); 20 | 21 | RED4ext::CClass::RegisterFunction(func); 22 | 23 | return func; 24 | } 25 | 26 | template 27 | requires detail::IsMemberCallCompatible 28 | RED4ext::CClassStaticFunction* AddFunction(RTTIMemberFunction aFunc, const char* aName, 29 | RED4ext::CBaseFunction::Flags aFlags = {}) 30 | { 31 | const auto* ptr = reinterpret_cast>(aFunc); 32 | auto* func = RED4ext::CClassFunction::Create(this, aName, aName, ptr, aFlags); 33 | 34 | RED4ext::CClass::RegisterFunction(func); 35 | 36 | return func; 37 | } 38 | 39 | template 40 | requires detail::IsFunctionPtr 41 | RED4ext::CClassFunction* AddFunction(const char* aName, RED4ext::CBaseFunction::Flags aFlags = {}) 42 | { 43 | const auto* ptr = WrapScriptableFunction(); 44 | 45 | RED4ext::CClassFunction* func; 46 | if constexpr (detail::IsMemberFunctionPtr) 47 | { 48 | static_assert( 49 | detail::IsScripable::context_type>, 50 | "Only IScriptable classes can have member methods."); 51 | 52 | func = RED4ext::CClassFunction::Create(this, aName, aName, ptr, aFlags); 53 | } 54 | else 55 | { 56 | func = RED4ext::CClassStaticFunction::Create(this, aName, aName, ptr, aFlags); 57 | } 58 | 59 | DescribeScriptableFunction(func, AFunc); 60 | 61 | RED4ext::CClass::RegisterFunction(func); 62 | 63 | return func; 64 | } 65 | 66 | inline static void RegisterRTTI() 67 | { 68 | s_registrar.Register(); 69 | } 70 | 71 | private: 72 | static void OnRegisterRTTI() {} 73 | 74 | static void OnPostRegisterRTTI() 75 | { 76 | using FinalDescriptor = typename T::Descriptor; 77 | 78 | auto* rtti = RED4ext::CRTTISystem::Get(); 79 | auto* type = rtti->GetClass(N); 80 | 81 | if (type) 82 | { 83 | T::OnExpand(reinterpret_cast(type), rtti); 84 | } 85 | } 86 | 87 | inline static RTTIRegistrar s_registrar{ &OnRegisterRTTI, &OnPostRegisterRTTI }; // NOLINT(cert-err58-cpp) 88 | }; 89 | 90 | template 91 | class RTTIExpansion : public B 92 | { 93 | public: 94 | using Descriptor = RTTIClassExpander; 95 | 96 | inline static void RegisterRTTI() 97 | { 98 | Descriptor::RegisterRTTI(); 99 | } 100 | 101 | private: 102 | friend Descriptor; 103 | 104 | static void OnExpand(Descriptor* aType, RED4ext::CRTTISystem* aRtti) {} 105 | }; 106 | } 107 | -------------------------------------------------------------------------------- /src/red4ext/Scripting/RTTIRegistrar.cpp: -------------------------------------------------------------------------------- 1 | #include "RTTIRegistrar.hpp" 2 | 3 | namespace 4 | { 5 | std::vector s_pending; 6 | } 7 | 8 | Engine::RTTIRegistrar::RTTIRegistrar(CallbackFunc aRegFunc, CallbackFunc aBuildFunc) 9 | : m_registered(false) 10 | , m_regFunc(aRegFunc) 11 | , m_buildFunc(aBuildFunc) 12 | { 13 | s_pending.push_back(this); 14 | } 15 | 16 | void Engine::RTTIRegistrar::Register() 17 | { 18 | if (!m_registered) 19 | { 20 | RED4ext::RTTIRegistrator::Add(m_regFunc, m_buildFunc); 21 | m_registered = true; 22 | } 23 | } 24 | 25 | void Engine::RTTIRegistrar::RegisterPending() 26 | { 27 | for (const auto& pending : s_pending) 28 | pending->Register(); 29 | 30 | s_pending.clear(); 31 | } 32 | -------------------------------------------------------------------------------- /src/red4ext/Scripting/RTTIRegistrar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.hpp" 4 | 5 | #include 6 | 7 | namespace Engine 8 | { 9 | /** 10 | * The registrar queues callbacks to register all at once on demand. 11 | * This serves two purposes: 12 | * - Auto discovery of used RTTI descriptors. 13 | * - Postpone until RTTI system is ready. 14 | */ 15 | class RTTIRegistrar 16 | { 17 | public: 18 | using CallbackFunc = RED4ext::RTTIRegistrator::CallbackFunc; 19 | 20 | RTTIRegistrar(CallbackFunc aRegFunc, CallbackFunc aBuildFunc); 21 | 22 | void Register(); 23 | static void RegisterPending(); 24 | 25 | private: 26 | bool m_registered; 27 | CallbackFunc m_regFunc; 28 | CallbackFunc m_buildFunc; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/red4ext/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.hpp" 2 | #include "Utils.hpp" 3 | 4 | //#include 5 | //#include 6 | // 7 | //void Utils::CreateLogger() 8 | //{ 9 | // auto rootDir = GetRootDir(); 10 | // auto red4extDir = rootDir / L"red4ext"; 11 | // auto logsDir = red4extDir / L"logs"; 12 | // auto logFile = logsDir / "mod_settings.log"; 13 | // 14 | // auto console = std::make_shared(); 15 | // auto file = std::make_shared(logFile.string(), true); 16 | // 17 | // spdlog::sinks_init_list sinks = {console, file}; 18 | // 19 | // auto logger = std::make_shared("", sinks); 20 | // spdlog::set_default_logger(logger); 21 | // 22 | // logger->flush_on(spdlog::level::trace); 23 | // spdlog::set_level(spdlog::level::trace); 24 | //} 25 | 26 | std::filesystem::path Utils::GetRootDir() 27 | { 28 | constexpr auto pathLength = MAX_PATH + 1; 29 | 30 | // Try to get the executable path until we can fit the length of the path. 31 | std::wstring filename; 32 | do 33 | { 34 | filename.resize(filename.size() + pathLength, '\0'); 35 | 36 | auto length = GetModuleFileName(nullptr, filename.data(), static_cast(filename.size())); 37 | if (length > 0) 38 | { 39 | // Resize it to the real, std::filesystem::path" will use the string's length instead of recounting it. 40 | filename.resize(length); 41 | } 42 | } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); 43 | 44 | auto rootDir = std::filesystem::path(filename) 45 | .parent_path() // Resolve to "x64" directory. 46 | .parent_path() // Resolve to "bin" directory. 47 | .parent_path(); // Resolve to game root directory. 48 | 49 | return rootDir; 50 | } 51 | 52 | std::wstring Utils::ToWString(const char* aText) 53 | { 54 | auto length = strlen(aText); 55 | 56 | std::wstring result(L"", length); 57 | mbstowcs(result.data(), aText, length); 58 | 59 | return result; 60 | } 61 | -------------------------------------------------------------------------------- /src/red4ext/Utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Utils 5 | { 6 | //void CreateLogger(); 7 | std::filesystem::path GetRootDir(); 8 | std::wstring ToWString(const char* aText); 9 | } 10 | 11 | #include "RED4ext/CName.hpp" 12 | #include 13 | 14 | template 15 | static void RedTypeFromString(T * pointer, const RED4ext::CString& str) { 16 | 17 | } 18 | 19 | template<> void RedTypeFromString(bool * pointer, const RED4ext::CString& str) { 20 | RED4ext::RawBuffer buffer; 21 | buffer.data = (void*)str.c_str(); 22 | buffer.size = str.length; 23 | RTTITYPE(Bool)->FromString(pointer, buffer); 24 | } 25 | 26 | template<> void RedTypeFromString(int8_t * pointer, const RED4ext::CString& str) { 27 | RED4ext::RawBuffer buffer; 28 | buffer.data = (void*)str.c_str(); 29 | buffer.size = str.length; 30 | RTTITYPE(Int8)->FromString(pointer, buffer); 31 | } 32 | 33 | template<> void RedTypeFromString(int16_t * pointer, const RED4ext::CString& str) { 34 | RED4ext::RawBuffer buffer; 35 | buffer.data = (void*)str.c_str(); 36 | buffer.size = str.length; 37 | RTTITYPE(Int16)->FromString(pointer, buffer); 38 | } 39 | 40 | template<> void RedTypeFromString(int32_t * pointer, const RED4ext::CString& str) { 41 | RED4ext::RawBuffer buffer; 42 | buffer.data = (void*)str.c_str(); 43 | buffer.size = str.length; 44 | RTTITYPE(Int32)->FromString(pointer, buffer); 45 | } 46 | 47 | template<> void RedTypeFromString(int64_t * pointer, const RED4ext::CString& str) { 48 | RED4ext::RawBuffer buffer; 49 | buffer.data = (void*)str.c_str(); 50 | buffer.size = str.length; 51 | RTTITYPE(Int64)->FromString(pointer, buffer); 52 | } 53 | 54 | template<> void RedTypeFromString(uint8_t * pointer, const RED4ext::CString& str) { 55 | RED4ext::RawBuffer buffer; 56 | buffer.data = (void*)str.c_str(); 57 | buffer.size = str.length; 58 | RTTITYPE(Uint8)->FromString(pointer, buffer); 59 | } 60 | 61 | template<> void RedTypeFromString(uint16_t * pointer, const RED4ext::CString& str) { 62 | RED4ext::RawBuffer buffer; 63 | buffer.data = (void*)str.c_str(); 64 | buffer.size = str.length; 65 | RTTITYPE(Uint16)->FromString(pointer, buffer); 66 | } 67 | 68 | template<> void RedTypeFromString(uint32_t * pointer, const RED4ext::CString& str) { 69 | RED4ext::RawBuffer buffer; 70 | buffer.data = (void*)str.c_str(); 71 | buffer.size = str.length; 72 | RTTITYPE(Uint32)->FromString(pointer, buffer); 73 | } 74 | 75 | template<> void RedTypeFromString(uint64_t * pointer, const RED4ext::CString& str) { 76 | RED4ext::RawBuffer buffer; 77 | buffer.data = (void*)str.c_str(); 78 | buffer.size = str.length; 79 | RTTITYPE(Uint64)->FromString(pointer, buffer); 80 | } 81 | 82 | template<> void RedTypeFromString(float * pointer, const RED4ext::CString& str) { 83 | RED4ext::RawBuffer buffer; 84 | buffer.data = (void*)str.c_str(); 85 | buffer.size = str.length; 86 | RTTITYPE(Float)->FromString(pointer, buffer); 87 | } 88 | 89 | template<> void RedTypeFromString(double * pointer, const RED4ext::CString& str) { 90 | RED4ext::RawBuffer buffer; 91 | buffer.data = (void*)str.c_str(); 92 | buffer.size = str.length; 93 | RTTITYPE(Double)->FromString(pointer, buffer); 94 | } 95 | 96 | template<> void RedTypeFromString(RED4ext::CName * pointer, const RED4ext::CString& str) { 97 | *pointer = RED4ext::CNamePool::Add(str.c_str()); 98 | } 99 | 100 | inline RED4ext::CBaseRTTIType* ToType(const RED4ext::CName name) { 101 | return RED4ext::CRTTISystem::Get()->GetType(name); 102 | } 103 | 104 | inline RED4ext::CClass* ToClass(const RED4ext::CName name) { 105 | return RED4ext::CRTTISystem::Get()->GetClass(name); 106 | } 107 | -------------------------------------------------------------------------------- /src/red4ext/Variable.cpp: -------------------------------------------------------------------------------- 1 | #include "RuntimeVariable.hpp" 2 | #include "RED4ext/CName.hpp" 3 | #include "RED4ext/CNamePool.hpp" 4 | #include "RED4ext/Scripting/IScriptable.hpp" 5 | #include "ScriptDefinitions/ScriptDefinitions.hpp" 6 | #include "ScriptDefinitions/ScriptProperty.hpp" 7 | #include "Variable.hpp" 8 | #include "ModConfigVar.hpp" 9 | #include 10 | 11 | namespace ModSettings { 12 | 13 | const CName ToConfigVar(CName typeName) noexcept { 14 | switch (typeName) { 15 | case CName("Bool"): 16 | return "ModConfigVarBool"; 17 | case CName("Int32"): 18 | return "ModConfigVarInt32"; 19 | case CName("Float"): 20 | return "ModConfigVarFloat"; 21 | case CName("CName"): 22 | return "ModConfigVarName"; 23 | default: 24 | return "ModConfigVarEnum"; 25 | } 26 | } 27 | 28 | // Variable 29 | 30 | ModVariable& ModCategory::AddVariable(ModVariable &variable) { 31 | this->variables[variable.name] = variable; 32 | variable.category = this; 33 | return this->variables[variable.name]; 34 | } 35 | 36 | ModVariable& ModClass::AddVariable(ModVariable &variable, ModCategory &category) { 37 | if (!this->categories.contains(category.name)) { 38 | category.modClass = this; 39 | this->categories[category.name] = category; 40 | } 41 | return this->categories[category.name].AddVariable(variable); 42 | } 43 | 44 | ModVariable& Mod::AddVariable(ModVariable &variable, ModCategory &category, ModClass &modClass) { 45 | if (!this->classes.contains(modClass.name)) { 46 | modClass.mod = this; 47 | this->classes[modClass.name] = modClass; 48 | } 49 | return this->classes[modClass.name].AddVariable(variable, category); 50 | } 51 | 52 | bool ModVariable::RestoreDefault() { 53 | this->runtimeVar->RestoreDefault(0); 54 | return this->runtimeVar->HasChange(); 55 | } 56 | 57 | void ModVariable::RejectChange() { 58 | if (this->runtimeVar->HasChange()) { 59 | this->runtimeVar->RevertChange(); 60 | } 61 | } 62 | 63 | bool ModVariable::IsEnabled() const { 64 | if (this->dependency.variable) { 65 | return this->dependency.variable->IsInputEqualToString(this->dependency.value); 66 | } 67 | return true; 68 | } 69 | 70 | bool ModVariable::IsInputEqualToString(const CString& other) const { 71 | CString str; 72 | this->type->ToString(this->runtimeVar->GetInputValue(), str); 73 | return str == other; 74 | } 75 | 76 | void ModVariable::Write(std::ofstream& stream) const { 77 | this->runtimeVar->ApplyChange(); 78 | if (this->runtimeVar->WasModifiedSinceLastSave()) { 79 | this->runtimeVar->ChangeWasWritten(); 80 | } 81 | auto str = RED4ext::CString(new RED4ext::Memory::DefaultAllocator()); 82 | this->type->ToString(this->runtimeVar->GetValuePtr(), str); 83 | 84 | stream << this->runtimeVar->name.ToString() << " = " << str.c_str() << "\n"; 85 | } 86 | 87 | bool ModVariable::SetRuntimeVariable(ScriptProperty * prop) { 88 | if (!this->type) { 89 | return false; 90 | } 91 | switch (this->type->GetName()) { 92 | case CName("Bool"): 93 | this->runtimeVar = new RuntimeVariableBool(prop); 94 | return true; 95 | case CName("Int32"): 96 | this->runtimeVar = new RuntimeVariableRange(prop); 97 | return true; 98 | case CName("Uint32"): 99 | this->runtimeVar = new RuntimeVariableRange(prop); 100 | return true; 101 | case CName("Float"): 102 | this->runtimeVar = new RuntimeVariableRange(prop); 103 | return true; 104 | default: 105 | if(this->type->GetType() == RED4ext::ERTTIType::Enum) { 106 | this->runtimeVar = new RuntimeVariableEnum(prop); 107 | return true; 108 | } else { 109 | this->runtimeVar = nullptr; 110 | return false; 111 | } 112 | } 113 | } 114 | 115 | bool ModVariable::CreateRuntimeVariable(const Variable &var) { 116 | switch (var.type) { 117 | case CName("Bool"): 118 | this->runtimeVar = new RuntimeVariableBool(var.className, var.propertyName, CNamePool::Add(var.displayName), CNamePool::Add(var.description), var.order, var.defaultValue.b); 119 | return true; 120 | case CName("Int32"): 121 | this->runtimeVar = new RuntimeVariableRange(var.className, var.propertyName, CNamePool::Add(var.displayName), CNamePool::Add(var.description), var.order, var.defaultValue.i32, var.stepValue.i32, var.minValue.i32, var.maxValue.i32); 122 | return true; 123 | case CName("Uint32"): 124 | this->runtimeVar = new RuntimeVariableRange(var.className, var.propertyName, CNamePool::Add(var.displayName), CNamePool::Add(var.description), var.order, var.defaultValue.u32, var.stepValue.u32, var.minValue.u32, var.maxValue.u32); 125 | return true; 126 | case CName("Float"): 127 | this->runtimeVar = new RuntimeVariableRange(var.className, var.propertyName, CNamePool::Add(var.displayName), CNamePool::Add(var.description), var.order, var.defaultValue.f32, var.stepValue.f32, var.minValue.f32, var.maxValue.f32); 128 | return true; 129 | default: 130 | // if(this->type->GetType() == RED4ext::ERTTIType::Enum) { 131 | // this->runtimeVar = new RuntimeVariableEnum(var.className, var.propertyName, var.displayName, var.description, var.order, *var.defaultValue); 132 | // return true; 133 | // } else { 134 | this->runtimeVar = nullptr; 135 | return false; 136 | // } 137 | } 138 | } 139 | 140 | IModConfigVar * ModVariable::ToConfigVar() const { 141 | if (this->runtimeVar) { 142 | auto configVar = this->configVarType->CreateInstance(); 143 | configVar->SetRuntime(this->runtimeVar); 144 | return configVar; 145 | } else { 146 | return nullptr; 147 | } 148 | } 149 | 150 | // Class 151 | 152 | // ModClass::ModClass(CName name) : ModClass() { 153 | // this->name = name; 154 | // this->type = ToClass(name); 155 | // } 156 | 157 | void ModClass::RegisterListener(Handle &listener) { 158 | for (auto it = this->listeners.begin(); it != this->listeners.end(); ++it) { 159 | if (!it->instance || !it->refCount || (int64_t)it->refCount == -1 || it->Expired()) { 160 | this->listeners.erase(it); 161 | } 162 | } 163 | if (listener && listener->ref && !listener->ref.Expired()) { 164 | this->listeners.emplace_back(listener); 165 | } 166 | } 167 | 168 | void ModClass::UnregisterListener(Handle &listener) { 169 | if (listener && listener->ref && !listener->ref.Expired()) { 170 | auto position = std::find(this->listeners.begin(), this->listeners.end(), WeakHandle(listener)); 171 | if (position != this->listeners.end()) 172 | this->listeners.erase(position); 173 | } 174 | } 175 | 176 | void ModClass::RegisterCallback(std::shared_ptr &callback) { 177 | if (callback) { 178 | this->callbacks.emplace_back(callback); 179 | } 180 | } 181 | 182 | void ModClass::UpdateDefault(CName propertyName, ScriptInstance* value) const { 183 | if (this->type) { 184 | for (auto i = 0; i < this->type->defaults.keys.size; i++) { 185 | if (this->type->defaults.keys[i] == propertyName) { 186 | // sdk->logger->InfoF(pluginHandle, "Loaded %s.%s", this->name.ToString(), propertyName.ToString()); 187 | auto propType = this->type->defaults.values[i]->GetType(); 188 | this->type->defaults.values[i]->Fill(propType, value); 189 | } 190 | } 191 | } 192 | } 193 | 194 | void ModClass::NotifyListeners() const { 195 | if (this->type) { 196 | for (const auto &[categoryName, category] : this->categories) { 197 | for (const auto &[variableName, variable] : category.variables) { 198 | auto valuePtr = variable.runtimeVar->GetValuePtr(); 199 | this->UpdateDefault(variableName, valuePtr); 200 | // std::shared_lock _(listeners_lock); 201 | for (auto &listener : this->listeners) { 202 | if (listener) { 203 | auto prop = this->type->propsByName.Get(variable.name); 204 | if (prop && *prop) { 205 | (*prop)->SetValue(listener.instance, valuePtr); 206 | } 207 | } 208 | } 209 | } 210 | } 211 | } 212 | // notify runtime listeners 213 | for (const auto &[categoryName, category] : this->categories) { 214 | for (const auto &[variableName, variable] : category.variables) { 215 | auto valuePtr = variable.runtimeVar->GetValuePtr(); 216 | for (auto &callback : this->callbacks) { 217 | if (callback) { 218 | (*callback)(categoryName, variableName, *(ModVariableType*)valuePtr); 219 | } 220 | } 221 | } 222 | } 223 | } 224 | 225 | // Mod 226 | 227 | Mod::Mod(CName name) : name(name) { 228 | // test 229 | // ModVariable var = { 230 | // .name = "My Prop", 231 | // .type = ToType("Bool"), 232 | // .configVarType = ToClass(ToConfigVar("Bool")) 233 | // }; 234 | // this->AddVariable(var, CName(), name); 235 | } 236 | 237 | // void ModSettingDependency::Resolve(std::string str, CName scriptClass) { 238 | // trim(str); 239 | // if (!str.empty()) { 240 | // auto period = str.find("."); 241 | // auto equals = str.find("="); 242 | // if (equals != std::string::npos) { 243 | // auto value = str.substr(equals, str.length()); 244 | // trim(value); 245 | // if (value.length()) { 246 | // this->value = value; 247 | // } 248 | // } else { 249 | // equals = str.length(); 250 | // this->value = "true"; 251 | // } 252 | // if (period != std::string::npos) { 253 | // auto className = str.substr(0, period); 254 | // auto propertyName = str.substr(period+1, equals); 255 | // trim(className); 256 | // trim(propertyName); 257 | // this->propertyName = RED4ext::CNamePool::Add(propertyName.c_str()); 258 | // this->className = RED4ext::CNamePool::Add(className.c_str()); 259 | // } else { 260 | // this->className = scriptClass; 261 | // this->propertyName = RED4ext::CNamePool::Add(str.c_str()); 262 | // } 263 | // } 264 | 265 | 266 | /* 267 | 268 | ModSettingsVariable::ModSettingsVariable() { 269 | this->listeners = RED4ext::DynArray>(new RED4ext::Memory::DefaultAllocator()); 270 | this->listeners.Reserve(1000); 271 | } 272 | 273 | // ModSettingsVariable::ModSettingsVariable(ScriptProperty* prop, const CName scriptClass) : ModSettingsVariable() { 274 | // prop->ReadProperty("ModSettings.mod", &this->mod); 275 | // prop->ReadProperty("ModSettings.category", &this->category); 276 | // this->type = RED4ext::CRTTISystem::Get()->GetType(prop->type->name); 277 | // this->configVarType = RED4ext::CRTTISystem::Get()->GetClass(ToConfigVar(prop->type->name)); 278 | // this->SetRuntimeSetting(prop); 279 | // auto depends = prop->ReadProperty("ModSettings.dependency"); 280 | // trim(depends); 281 | // if (!depends.empty()) { 282 | // auto period = depends.find("."); 283 | // auto equals = depends.find("="); 284 | // if (equals != std::string::npos) { 285 | // auto value = depends.substr(equals, depends.length()); 286 | // trim(value); 287 | // if (value.length()) { 288 | // this->value = value; 289 | // } 290 | // } else { 291 | // equals = depends.length(); 292 | // this->dependency.value = "true"; 293 | // } 294 | // if (period != std::string::npos) { 295 | // auto className = depends.substr(0, period); 296 | // auto propertyName = depends.substr(period+1, equals); 297 | // trim(className); 298 | // trim(propertyName); 299 | // this->dependency.propertyName = RED4ext::CNamePool::Add(propertyName.c_str()); 300 | // this->dependency.className = RED4ext::CNamePool::Add(className.c_str()); 301 | // } else { 302 | // this->dependency.className = scriptClass; 303 | // this->dependency.propertyName = RED4ext::CNamePool::Add(depends.c_str()); 304 | // } 305 | // } 306 | // } 307 | 308 | // ModSettingsVariable::ModSettingsVariable(const CName mod, const CName typeName, const CName className, const CName category) : 309 | // mod(mod), 310 | // type(RED4ext::CRTTISystem::Get()->GetType(typeName)), 311 | // configVarType(RED4ext::CRTTISystem::Get()->GetClass(ToConfigVar(typeName))), 312 | // className(className), 313 | // category(category) { 314 | // this->listeners = RED4ext::DynArray>(new RED4ext::Memory::DefaultAllocator()); 315 | // this->listeners.Reserve(1000); 316 | // } 317 | 318 | template <> constexpr const CName ModSettingsVariable::GetTypeCName() noexcept { 319 | return 0x3D2E9DD9E3C28D8C; 320 | } 321 | 322 | template <> constexpr const CName ModSettingsVariable::GetTypeCName() noexcept { 323 | return 0xF7BDD5A7C820889D; 324 | } 325 | 326 | template <> constexpr const CName ModSettingsVariable::GetTypeCName() noexcept { 327 | return 0xB9A127F5B4A621BF; 328 | } 329 | 330 | template <> constexpr const CName ModSettingsVariable::GetTypeCName() noexcept { 331 | return 0xA5E23DE2A2657AF9; 332 | } 333 | 334 | template <> constexpr const CName ModSettingsVariable::GetTypeCName() noexcept { 335 | return 0xB64F4A0ACCC8A8C5; 336 | } 337 | 338 | void ModSettingsVariable::UpdateValues() { 339 | auto classType = RED4ext::CRTTISystem::Get()->GetClass(className); 340 | if (classType) { 341 | auto valuePtr = runtimeVar->GetValuePtr(); 342 | for (auto i = 0; i < classType->defaults.keys.size; i++) { 343 | if (classType->defaults.keys[i] == runtimeVar->name) { 344 | sdk->logger->InfoF(pluginHandle, "Loaded %s.%s", className.ToString(), runtimeVar->name.ToString()); 345 | auto propType = classType->defaults.values[i]->GetType(); 346 | classType->defaults.values[i]->Fill(propType, valuePtr); 347 | } 348 | } 349 | // std::shared_lock _(listeners_lock); 350 | for (auto &listener : listeners) { 351 | if (listener) { 352 | auto prop = classType->propsByName.Get(runtimeVar->name); 353 | if (prop && *prop) { 354 | (*prop)->SetValue(listener.instance, valuePtr); 355 | } 356 | } 357 | } 358 | } 359 | } 360 | 361 | Handle ModSettingsVariable::CreateConfigVar() { 362 | auto configVar = this->configVarType->CreateInstance(); 363 | configVar->runtimeVar = this->runtimeVar; 364 | auto handle = Handle(configVar); 365 | handle.refCount->IncRef(); 366 | return handle; 367 | } 368 | 369 | void ModSettingsVariable::Write(std::ofstream& stream) { 370 | this->runtimeVar->ApplyChange(); 371 | if (this->runtimeVar->WasModifiedSinceLastSave()) { 372 | this->UpdateValues(); 373 | this->runtimeVar->ChangeWasWritten(); 374 | } 375 | auto str = RED4ext::CString(new RED4ext::Memory::DefaultAllocator()); 376 | this->type->ToString(this->runtimeVar->GetValuePtr(), str); 377 | 378 | stream << this->runtimeVar->name.ToString() << " = " << str.c_str() << "\n"; 379 | } 380 | 381 | bool ModSettingsVariable::RestoreDefault() { 382 | this->runtimeVar->RestoreDefault(0); 383 | return this->runtimeVar->HasChange(); 384 | } 385 | 386 | void ModSettingsVariable::RejectChange() { 387 | if (this->runtimeVar->HasChange()) { 388 | this->runtimeVar->RevertChange(); 389 | } 390 | } 391 | 392 | void ModSettingsVariable::RegisterListener(Handle handle) { 393 | // std::shared_lock _(this->listeners_lock); 394 | this->listeners.EmplaceBack(handle); 395 | } 396 | 397 | void ModSettingsVariable::UnregisterListener(Handle handle) { 398 | auto i = 0; 399 | for (const auto &listener : this->listeners) { 400 | // std::shared_lock _(this->listeners_lock); 401 | if (listener && listener.instance == handle.instance) { 402 | this->listeners.RemoveAt(i); 403 | break; 404 | } 405 | i++; 406 | } 407 | } 408 | 409 | bool ModVariable::SetRuntimeVariable(ScriptProperty * prop) { 410 | switch (this->type->GetName()) { 411 | case CName("Bool"): 412 | this->runtimeVar = new RuntimeVariableBool(prop); 413 | return true; 414 | case CName("Int32"): 415 | this->runtimeVar = new RuntimeVariableRange(prop); 416 | return true; 417 | case CName("Uint32"): 418 | this->runtimeVar = new RuntimeVariableRange(prop); 419 | return true; 420 | case CName("Float"): 421 | this->runtimeVar = new RuntimeVariableRange(prop); 422 | return true; 423 | default: 424 | if(this->type->GetType() == RED4ext::ERTTIType::Enum) { 425 | this->runtimeVar = new RuntimeVariableEnum(prop); 426 | return true; 427 | } else { 428 | this->runtimeVar = nullptr; 429 | return false; 430 | } 431 | } 432 | } 433 | */ 434 | 435 | } // namespace ModSettings -------------------------------------------------------------------------------- /src/red4ext/Variable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "IRuntimeVariable.hpp" 5 | #include "RED4ext/RTTITypes.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct IModConfigVar; 13 | 14 | namespace ModSettings { 15 | 16 | using namespace RED4ext; 17 | 18 | struct ScriptProperty; 19 | 20 | struct Mod; 21 | struct ModClass; 22 | struct ModCategory; 23 | struct ModVariable; 24 | 25 | const CName ToConfigVar(CName typeName) noexcept; 26 | 27 | struct ModVariable { 28 | uint32_t GetOrder() const { 29 | return runtimeVar->order != (uint32_t)-1 ? runtimeVar->order : implicitOrder; 30 | } 31 | 32 | bool operator< (const ModVariable &other) const { 33 | return GetOrder() < other.GetOrder(); 34 | } 35 | 36 | friend std::ofstream& operator<< (std::ofstream& stream, const ModVariable& mv) { 37 | mv.Write(stream); 38 | return stream; 39 | } 40 | 41 | void Write(std::ofstream& stream) const; 42 | bool SetRuntimeVariable(ScriptProperty * prop); 43 | bool CreateRuntimeVariable(const Variable &var); 44 | bool RestoreDefault(); 45 | void RejectChange(); 46 | bool IsEnabled() const; 47 | bool IsInputEqualToString(const CString& str) const; 48 | IModConfigVar * ToConfigVar() const; 49 | 50 | CName name = 0LLU; 51 | CBaseRTTIType *type = nullptr; 52 | CClass *configVarType = nullptr; 53 | IRuntimeVariable *runtimeVar = nullptr; 54 | ModSettingDependency dependency; 55 | ModCategory * category; 56 | uint32_t implicitOrder; 57 | }; 58 | 59 | // maybe shouldn't use this structure interally? 60 | struct ModCategory { 61 | // ModCategory() = default; 62 | // ModCategory(CName name); 63 | 64 | ModVariable& AddVariable(ModVariable &variable); 65 | 66 | constexpr operator CName() const noexcept { 67 | return this->name; 68 | } 69 | 70 | CName name; 71 | uint32_t order; 72 | std::map variables; 73 | ModClass * modClass; 74 | }; 75 | 76 | struct ModClass { 77 | // ModClass() = default; 78 | // ModClass(CName name); 79 | 80 | ModVariable& AddVariable(ModVariable &variable, ModCategory &category); 81 | void RegisterListener(Handle &listener); 82 | void UnregisterListener(Handle &listener); 83 | void RegisterCallback(std::shared_ptr &callback); 84 | void UpdateDefault(CName propertyName, ScriptInstance* value) const; 85 | void NotifyListeners() const; 86 | 87 | constexpr operator CName() const noexcept { 88 | return this->name; 89 | } 90 | 91 | CName name; 92 | uint32_t order; 93 | CClass* type; 94 | std::vector> listeners; 95 | std::vector> callbacks; 96 | std::map categories; 97 | Mod * mod; 98 | }; 99 | 100 | struct Mod { 101 | Mod() = default; 102 | Mod(CName name); 103 | 104 | ModVariable& AddVariable(ModVariable &variable, ModCategory &category, ModClass &modClass); 105 | 106 | CName name; 107 | std::map classes; 108 | }; 109 | /* 110 | class ModSettingsVariable { 111 | public: 112 | ModSettingsVariable(); 113 | ModSettingsVariable(ScriptProperty* prop, const CName scriptClass); 114 | 115 | void UpdateValues(); 116 | Handle CreateConfigVar(); 117 | void Write(std::ofstream& stream); 118 | 119 | inline const CName& Mod() const { return mod; } 120 | inline const CName& ClassName() const { return className; } 121 | inline const CName& Category() const { return category; } 122 | 123 | bool RestoreDefault(); 124 | void RejectChange(); 125 | 126 | void RegisterListener(Handle handle); 127 | void UnregisterListener(Handle handle); 128 | 129 | inline const bool IsValid() const { return this->runtimeVar != nullptr; } 130 | 131 | std::ofstream& operator<< (std::ofstream& stream) { 132 | this->Write(stream); 133 | return stream; 134 | } 135 | 136 | bool SetRuntimeVariable(ScriptProperty * prop); 137 | 138 | private: 139 | template 140 | constexpr const CName GetTypeCName() noexcept; 141 | 142 | CName mod = CName(); 143 | CName className = CName(); 144 | CName category = CName(); 145 | 146 | CBaseRTTIType *type; 147 | CClass *configVarType; 148 | IRuntimeVariable *runtimeVar = nullptr; 149 | DynArray> listeners; 150 | // std::mutex listeners_lock; 151 | ModSettingDependency dependency; 152 | }; 153 | */ 154 | } -------------------------------------------------------------------------------- /src/red4ext/stdafx.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.hpp" 2 | -------------------------------------------------------------------------------- /src/red4ext/stdafx.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | //#include 12 | 13 | #include 14 | //#include 15 | -------------------------------------------------------------------------------- /src/redscript/Module.reds.in: -------------------------------------------------------------------------------- 1 | module ModSettingsModule 2 | 3 | public func GetVersionString() -> String { 4 | return "v@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@"; 5 | } 6 | 7 | public func GetVersion() -> Int32 { 8 | return @PROJECT_VERSION_MAJOR@ * 10000 + @PROJECT_VERSION_MINOR@ * 100 + @PROJECT_VERSION_PATCH@; 9 | } -------------------------------------------------------------------------------- /src/redscript/mod_settings/ModSettings.reds: -------------------------------------------------------------------------------- 1 | public native class ModSettings extends IScriptable { 2 | public native static func GetInstance() -> ref; 3 | public native static func GetMods() -> array; 4 | public native static func GetCategories(mod: CName) -> array; 5 | public native static func GetVars(mod: CName, category: CName) -> array>; 6 | public native static func AcceptChanges() -> Void; 7 | public native static func RejectChanges() -> Void; 8 | public native static func RestoreDefaults(mod: CName) -> Void; 9 | public native static func RegisterListenerToClass(self: ref) -> Void; 10 | public native static func UnregisterListenerToClass(self: ref) -> Void; 11 | public native static func RegisterListenerToModifications(self: ref) -> Void; 12 | public native static func UnregisterListenerToModifications(self: ref) -> Void; 13 | 14 | public native let changeMade: Bool; 15 | 16 | public let isActive: Bool; 17 | } 18 | 19 | public native class ModConfigVarBool extends ConfigVar { 20 | public native func SetValue(value: Bool) -> Void; 21 | public native func GetValue() -> Bool; 22 | public native func GetDefaultValue() -> Bool; 23 | public func Toggle() -> Void { 24 | this.SetValue(!this.GetValue()); 25 | } 26 | } 27 | 28 | public native class ModConfigVarName extends ConfigVar { 29 | public native func SetValue(value: CName) -> Void; 30 | public native func GetValue() -> CName; 31 | public native func GetDefaultValue() -> CName; 32 | } 33 | 34 | public native class ModConfigVarFloat extends ConfigVar { 35 | public native func SetValue(value: Float) -> Void; 36 | public native func GetValue() -> Float; 37 | public native func GetDefaultValue() -> Float; 38 | public native func GetMinValue() -> Float; 39 | public native func GetMaxValue() -> Float; 40 | public native func GetStepValue() -> Float; 41 | } 42 | 43 | public native class ModConfigVarInt32 extends ConfigVar { 44 | public native func SetValue(value: Int32) -> Void; 45 | public native func GetValue() -> Int32; 46 | public native func GetDefaultValue() -> Int32; 47 | public native func GetMinValue() -> Int32; 48 | public native func GetMaxValue() -> Int32; 49 | public native func GetStepValue() -> Int32; 50 | } 51 | 52 | public native class ModConfigVarEnum extends ConfigVar { 53 | public native func GetValueFor(index: Int32) -> Int32; 54 | public native func GetValue() -> Int32; 55 | public native func GetDefaultValue() -> Int32; 56 | public native func GetValues() -> array; 57 | public native func GetIndexFor(value: Int32) -> Int32; 58 | public native func GetIndex() -> Int32; 59 | public native func GetDefaultIndex() -> Int32; 60 | public native func SetIndex(index: Int32) -> Void; 61 | public native func GetDisplayValue(index: Int32) -> CName; 62 | } 63 | 64 | // public class TestClassForCount { 65 | 66 | // @runtimeProperty("ModSettings.mod", "Automatic Mod With a really long name, just for fun") 67 | // public let automaticValue: Float = 0.0; 68 | 69 | // @runtimeProperty("ModSettings.mod", "Stone Mod") 70 | // public let stoneValue: Float = 0.0; 71 | 72 | // @runtimeProperty("ModSettings.mod", "Introduce Mod") 73 | // public let introduceValue: Float = 0.0; 74 | 75 | // @runtimeProperty("ModSettings.mod", "Gaudy Mod") 76 | // public let gaudyValue: Float = 0.0; 77 | 78 | // @runtimeProperty("ModSettings.mod", "Violent Mod") 79 | // public let violentValue: Float = 0.0; 80 | 81 | // @runtimeProperty("ModSettings.mod", "Farm Mod") 82 | // public let farmValue: Float = 0.0; 83 | 84 | // @runtimeProperty("ModSettings.mod", "Loutish Mod") 85 | // public let loutishValue: Float = 0.0; 86 | 87 | // @runtimeProperty("ModSettings.mod", "Toothsome Mod") 88 | // public let toothsomeValue: Float = 0.0; 89 | 90 | // @runtimeProperty("ModSettings.mod", "Check Mod") 91 | // public let checkValue: Float = 0.0; 92 | 93 | // @runtimeProperty("ModSettings.mod", "Lavish Mod") 94 | // public let lavishValue: Float = 0.0; 95 | 96 | // @runtimeProperty("ModSettings.mod", "Weary Mod") 97 | // public let wearyValue: Float = 0.0; 98 | 99 | // @runtimeProperty("ModSettings.mod", "Permit Mod") 100 | // public let permitValue: Float = 0.0; 101 | 102 | // @runtimeProperty("ModSettings.mod", "Division Mod") 103 | // public let divisionValue: Float = 0.0; 104 | 105 | // @runtimeProperty("ModSettings.mod", "Cake Mod") 106 | // public let cakeValue: Float = 0.0; 107 | 108 | // @runtimeProperty("ModSettings.mod", "Possess Mod") 109 | // public let possessValue: Float = 0.0; 110 | 111 | // @runtimeProperty("ModSettings.mod", "Pump Mod") 112 | // public let pumpValue: Float = 0.0; 113 | 114 | // @runtimeProperty("ModSettings.mod", "Stage Mod") 115 | // public let stageValue: Float = 0.0; 116 | 117 | // @runtimeProperty("ModSettings.mod", "Tub Mod") 118 | // public let tubValue: Float = 0.0; 119 | 120 | // @runtimeProperty("ModSettings.mod", "Love Mod") 121 | // public let loveValue: Float = 0.0; 122 | 123 | // @runtimeProperty("ModSettings.mod", "Known Mod") 124 | // public let knownValue: Float = 0.0; 125 | 126 | // @runtimeProperty("ModSettings.mod", "Far Mod") 127 | // public let farValue: Float = 0.0; 128 | 129 | // @runtimeProperty("ModSettings.mod", "Bell Mod") 130 | // public let bellValue: Float = 0.0; 131 | 132 | // @runtimeProperty("ModSettings.mod", "Waggish Mod") 133 | // public let waggishValue: Float = 0.0; 134 | 135 | // @runtimeProperty("ModSettings.mod", "Honorable Mod") 136 | // public let honorableValue: Float = 0.0; 137 | 138 | // @runtimeProperty("ModSettings.mod", "Vivacious Mod") 139 | // public let vivaciousValue: Float = 0.0; 140 | 141 | // @runtimeProperty("ModSettings.mod", "Offend Mod") 142 | // public let offendValue: Float = 0.0; 143 | 144 | // @runtimeProperty("ModSettings.mod", "Call Mod") 145 | // public let callValue: Float = 0.0; 146 | 147 | // @runtimeProperty("ModSettings.mod", "Good Mod") 148 | // public let goodValue: Float = 0.0; 149 | 150 | // @runtimeProperty("ModSettings.mod", "Tremendous Mod") 151 | // public let tremendousValue: Float = 0.0; 152 | 153 | // @runtimeProperty("ModSettings.mod", "Cycle Mod") 154 | // public let cycleValue: Float = 0.0; 155 | 156 | // @runtimeProperty("ModSettings.mod", "Provide Mod") 157 | // public let provideValue: Float = 0.0; 158 | 159 | // @runtimeProperty("ModSettings.mod", "Price Mod") 160 | // public let priceValue: Float = 0.0; 161 | 162 | // @runtimeProperty("ModSettings.mod", "Purpose Mod") 163 | // public let purposeValue: Float = 0.0; 164 | 165 | // @runtimeProperty("ModSettings.mod", "Cellar Mod") 166 | // public let cellarValue: Float = 0.0; 167 | 168 | // @runtimeProperty("ModSettings.mod", "Icky Mod") 169 | // public let ickyValue: Float = 0.0; 170 | 171 | // @runtimeProperty("ModSettings.mod", "Hobbies Mod") 172 | // public let hobbiesValue: Float = 0.0; 173 | 174 | // @runtimeProperty("ModSettings.mod", "Sweater Mod") 175 | // public let sweaterValue: Float = 0.0; 176 | 177 | // @runtimeProperty("ModSettings.mod", "Detect Mod") 178 | // public let detectValue: Float = 0.0; 179 | 180 | // @runtimeProperty("ModSettings.mod", "Knot Mod") 181 | // public let knotValue: Float = 0.0; 182 | 183 | // @runtimeProperty("ModSettings.mod", "Visit Mod") 184 | // public let visitValue: Float = 0.0; 185 | 186 | // @runtimeProperty("ModSettings.mod", "Double Mod") 187 | // public let doubleValue: Float = 0.0; 188 | 189 | // @runtimeProperty("ModSettings.mod", "Please Mod") 190 | // public let pleaseValue: Float = 0.0; 191 | 192 | // @runtimeProperty("ModSettings.mod", "Can Mod") 193 | // public let canValue: Float = 0.0; 194 | 195 | // @runtimeProperty("ModSettings.mod", "Classy Mod") 196 | // public let classyValue: Float = 0.0; 197 | 198 | // @runtimeProperty("ModSettings.mod", "Grieving Mod") 199 | // public let grievingValue: Float = 0.0; 200 | 201 | // @runtimeProperty("ModSettings.mod", "Gullible Mod") 202 | // public let gullibleValue: Float = 0.0; 203 | 204 | // @runtimeProperty("ModSettings.mod", "Inexpensive Mod") 205 | // public let inexpensiveValue: Float = 0.0; 206 | 207 | // @runtimeProperty("ModSettings.mod", "Reflect Mod") 208 | // public let reflectValue: Float = 0.0; 209 | 210 | // } -------------------------------------------------------------------------------- /src/redscript/mod_settings/ModSettingsNotificationListener.reds: -------------------------------------------------------------------------------- 1 | 2 | public class ModSettingsNotificationListener extends ConfigNotificationListener { 3 | 4 | private let m_ctrl: wref; 5 | 6 | public final func RegisterController(ctrl: ref) -> Void { 7 | this.m_ctrl = ctrl; 8 | } 9 | 10 | public func OnNotify(status: ConfigNotificationType) -> Void { 11 | // Log("SettingsNotificationListener::OnNotify"); 12 | this.m_ctrl.OnSettingsNotify(status); 13 | } 14 | } 15 | 16 | public class ModSettingsVarListener extends ConfigVarListener { 17 | 18 | private let m_ctrl: wref; 19 | 20 | public final func RegisterController(ctrl: ref) -> Void { 21 | this.m_ctrl = ctrl; 22 | } 23 | 24 | public func OnVarModified(groupPath: CName, varName: CName, varType: ConfigVarType, reason: ConfigChangeReason) -> Void { 25 | // Log("ModSettingsVarListener::OnVarModified"); 26 | this.m_ctrl.OnVarModified(groupPath, varName, varType, reason); 27 | } 28 | } -------------------------------------------------------------------------------- /src/redscript/mod_settings/_SettingsCategoryController.reds: -------------------------------------------------------------------------------- 1 | @wrapMethod(SettingsCategoryController) 2 | public final func Setup(label: CName) -> Void { 3 | if ModSettings.GetInstance().isActive { 4 | let labelString: String = GetLocalizedTextByKey(label); 5 | if StrLen(labelString) == 0 { 6 | labelString = ToString(label); 7 | }; 8 | inkTextRef.SetText(this.m_label, labelString); 9 | } else { 10 | wrappedMethod(label); 11 | } 12 | } -------------------------------------------------------------------------------- /src/redscript/mod_settings/_SettingsSelectorControllers.reds: -------------------------------------------------------------------------------- 1 | // @wrapMethod(SettingsSelectorControllerBool) 2 | // private func AcceptValue(forward: Bool) -> Void { 3 | // let ns = NativeSettings.GetInstance(); 4 | // if ns.fromMods { 5 | // let ot = ns.GetOptionTable(this) as ConfigVarBool; 6 | // inkWidgetRef.SetVisible(this.m_onState, ot.GetValue()); 7 | // inkWidgetRef.SetVisible(this.m_offState, !ot.GetValue()); 8 | // // data.callback(data.state) 9 | // } 10 | // wrappedMethod(forward); 11 | // } 12 | 13 | // @wrapMethod(SettingsSelectorControllerInt) 14 | // public func Refresh() -> Void { 15 | // let ns = NativeSettings.GetInstance(); 16 | // if ns.fromMods { 17 | // let ot = ns.GetOptionTable(this) as ConfigVarInt; 18 | // if ot.GetValue() != this.m_newValue { 19 | // ot.SetValue(this.m_newValue); 20 | // inkTextRef.SetText(this.m_ValueText, ToString(this.m_newValue)); 21 | // this.m_sliderController.ChangeValue(Cast(this.m_newValue)); 22 | // } 23 | // } else { 24 | // wrappedMethod(); 25 | // } 26 | // } 27 | 28 | // @wrapMethod(SettingsSelectorControllerInt) 29 | // public func ChangeValue(forward: Bool) -> Void { 30 | // let ns = NativeSettings.GetInstance(); 31 | // if ns.fromMods { 32 | // let ot = ns.GetOptionTable(this) as ConfigVarInt; 33 | // let step: Int32 = forward ? ot.GetStepValue() : -ot.GetStepValue(); 34 | // this.m_newValue = Clamp(this.m_newValue + step, ot.GetMinValue(), ot.GetMaxValue()); 35 | // this.Refresh(); 36 | // } else { 37 | // wrappedMethod(forward); 38 | // } 39 | // } 40 | 41 | // @addMethod(SettingsSelectorController) 42 | // public func SetupMod(entry: ref, isPreGame: Bool) -> Void { 43 | // this.m_SettingsEntry = entry; 44 | // this.m_IsPreGame = isPreGame; 45 | // this.m_varGroupPath = this.m_SettingsEntry.GetGroupPath(); 46 | // this.m_varName = this.m_SettingsEntry.GetName(); 47 | // this.BindSettings(entry); 48 | // } 49 | 50 | @wrapMethod(SettingsSelectorController) 51 | public func Refresh() -> Void { 52 | if ModSettings.GetInstance().isActive { 53 | let i: Int32; 54 | let languageProvider: ref; 55 | let modifiedSymbol: String; 56 | let text: String; 57 | let updatePolicy: ConfigVarUpdatePolicy; 58 | let wasModified: Bool; 59 | let size: Int32 = this.m_SettingsEntry.GetDisplayNameKeysSize(); 60 | if size > 0 { 61 | text = NameToString(this.m_SettingsEntry.GetDisplayName()); 62 | i = 0; 63 | while i < size { 64 | text = StrReplace(text, "%", GetLocalizedTextByKey(this.m_SettingsEntry.GetDisplayNameKey(i))); 65 | i += 1; 66 | }; 67 | } else { 68 | text = GetLocalizedTextByKey(this.m_SettingsEntry.GetDisplayName()); 69 | }; 70 | updatePolicy = this.m_SettingsEntry.GetUpdatePolicy(); 71 | if Equals(text, "") { 72 | text = NameToString(this.m_SettingsEntry.GetDisplayName()); 73 | }; 74 | if Equals(updatePolicy, ConfigVarUpdatePolicy.ConfirmationRequired) { 75 | modifiedSymbol = "*"; 76 | wasModified = this.m_SettingsEntry.HasRequestedValue(); 77 | } else { 78 | if Equals(updatePolicy, ConfigVarUpdatePolicy.RestartRequired) || Equals(updatePolicy, ConfigVarUpdatePolicy.LoadLastCheckpointRequired) { 79 | modifiedSymbol = "!"; 80 | wasModified = this.m_SettingsEntry.HasRequestedValue() || this.m_SettingsEntry.WasModifiedSinceLastSave(); 81 | } else { 82 | modifiedSymbol = ""; 83 | wasModified = false; 84 | }; 85 | }; 86 | languageProvider = inkWidgetRef.GetUserData(this.m_LabelText, n"inkLanguageOverrideProvider") as inkLanguageOverrideProvider; 87 | languageProvider.SetLanguage(scnDialogLineLanguage.Origin); 88 | inkTextRef.UpdateLanguageResources(this.m_LabelText, false); 89 | inkTextRef.SetText(this.m_LabelText, text); 90 | inkWidgetRef.SetVisible(this.m_ModifiedFlag, wasModified); 91 | inkTextRef.SetText(this.m_ModifiedFlag, modifiedSymbol); 92 | } else { 93 | wrappedMethod(); 94 | } 95 | } 96 | 97 | // @wrapMethod(SettingsSelectorControllerBool) 98 | // private func AcceptValue(forward: Bool) -> Void { 99 | // wrappedMethod(forward); 100 | // this.Refresh(); 101 | // } 102 | 103 | @wrapMethod(SettingsSelectorController) 104 | protected cb func OnLeft(e: ref) -> Bool { 105 | let og = wrappedMethod(e); 106 | if ModSettings.GetInstance().isActive { 107 | this.Refresh(); 108 | } 109 | return og; 110 | } 111 | 112 | @wrapMethod(SettingsSelectorController) 113 | protected cb func OnRight(e: ref) -> Bool { 114 | let og = wrappedMethod(e); 115 | if ModSettings.GetInstance().isActive { 116 | this.Refresh(); 117 | } 118 | return og; 119 | } 120 | 121 | // @wrapMethod(SettingsSelectorControllerListName) 122 | // public func Refresh() -> Void { 123 | // if ModSettings.GetInstance().isActive { 124 | // let params: ref; 125 | // super.Refresh(); 126 | // this.UpdateValueTextLanguageResources(); 127 | // if !this.m_realValue.ListHasDisplayValues() { 128 | // inkTextRef.SetText(this.m_ValueText, ToString(this.m_realValue.GetValueFor(this.m_currentIndex))); 129 | // } else { 130 | // if Equals(this.m_additionalText, n"") { 131 | // inkTextRef.SetText(this.m_ValueText, ToString(this.m_realValue.GetDisplayValue(this.m_currentIndex))); 132 | // } else { 133 | // params = new inkTextParams(); 134 | // // params.AddLocalizedString("description", GetLocalizedTextByKey(this.m_realValue.GetDisplayValue(this.m_currentIndex))); 135 | // params.AddString("description", ToString(this.m_realValue.GetDisplayValue(this.m_currentIndex))); 136 | // params.AddLocalizedString("additional_text", ToString(this.m_additionalText)); 137 | // inkTextRef.SetLocalizedTextScript(this.m_ValueText, "LocKey#76949", params); 138 | // }; 139 | // }; 140 | // this.SelectDot(this.m_currentIndex); 141 | // } else { 142 | // wrappedMethod(); 143 | // } 144 | // } 145 | 146 | 147 | 148 | 149 | 150 | 151 | public class ModStngsSelectorControllerInt extends SettingsSelectorControllerRange { 152 | private let m_newValue: Int32; 153 | private let m_sliderWidget: inkWidgetRef; 154 | private let m_sliderController: wref; 155 | 156 | public func Setup(entry: ref, isPreGame: Bool) -> Void { 157 | let value: ref; 158 | super.Setup(entry, isPreGame); 159 | value = this.m_SettingsEntry as ModConfigVarInt32; 160 | this.m_sliderController = inkWidgetRef.GetControllerByType(this.m_sliderWidget, n"inkSliderController") as inkSliderController; 161 | this.m_sliderController.Setup(Cast(value.GetMinValue()), Cast(value.GetMaxValue()), Cast(this.m_newValue), Cast(value.GetStepValue())); 162 | this.m_sliderController.RegisterToCallback(n"OnSliderValueChanged", this, n"OnSliderValueChanged"); 163 | this.m_sliderController.RegisterToCallback(n"OnSliderHandleReleased", this, n"OnHandleReleased"); 164 | } 165 | 166 | protected cb func OnSliderValueChanged(sliderController: wref, progress: Float, value: Float) -> Bool { 167 | this.m_newValue = Cast(value); 168 | this.Refresh(); 169 | } 170 | 171 | protected cb func OnHandleReleased() -> Bool { 172 | let value: ref = this.m_SettingsEntry as ModConfigVarInt32; 173 | value.SetValue(this.m_newValue); 174 | } 175 | 176 | private func RegisterShortcutCallbacks() -> Void { 177 | super.RegisterShortcutCallbacks(); 178 | this.RegisterToCallback(n"OnRepeat", this, n"OnShortcutRepeat"); 179 | } 180 | 181 | private func ChangeValue(forward: Bool) -> Void { 182 | let value: ref = this.m_SettingsEntry as ModConfigVarInt32; 183 | let step: Int32 = forward ? value.GetStepValue() : -value.GetStepValue(); 184 | this.m_newValue = Clamp(this.m_newValue + step, value.GetMinValue(), value.GetMaxValue()); 185 | this.Refresh(); 186 | } 187 | 188 | private func AcceptValue(forward: Bool) -> Void { 189 | let value: ref = this.m_SettingsEntry as ModConfigVarInt32; 190 | if value.GetValue() == this.m_newValue { 191 | this.ChangeValue(forward); 192 | }; 193 | value.SetValue(this.m_newValue); 194 | } 195 | 196 | public func Refresh() -> Void { 197 | super.Refresh(); 198 | this.UpdateValueTextLanguageResources(); 199 | inkTextRef.SetText(this.m_ValueText, IntToString(this.m_newValue)); 200 | this.m_sliderController.ChangeValue(Cast(this.m_newValue)); 201 | } 202 | 203 | protected cb func OnUpdateValue() -> Bool { 204 | let value: ref = this.m_SettingsEntry as ModConfigVarInt32; 205 | this.m_newValue = value.GetValue(); 206 | super.OnUpdateValue(); 207 | } 208 | } 209 | 210 | public class ModStngsSelectorControllerBool extends SettingsSelectorController { 211 | protected let m_onState: inkWidgetRef; 212 | protected let m_offState: inkWidgetRef; 213 | protected let m_onStateBody: inkWidgetRef; 214 | protected let m_offStateBody: inkWidgetRef; 215 | 216 | public func Setup(entry: ref, isPreGame: Bool) -> Void { 217 | super.Setup(entry, isPreGame); 218 | } 219 | 220 | public func Refresh() -> Void { 221 | let buttonLogic: ref; 222 | let value: Bool; 223 | let realValue: ref = this.m_SettingsEntry as ModConfigVarBool; 224 | super.Refresh(); 225 | value = realValue.GetValue(); 226 | inkWidgetRef.SetVisible(this.m_onState, value); 227 | inkWidgetRef.SetVisible(this.m_offState, !value); 228 | buttonLogic = inkWidgetRef.GetControllerByType(this.m_onState, n"inkButtonController") as inkButtonController; 229 | if IsDefined(buttonLogic) { 230 | buttonLogic.SetEnabled(!this.m_SettingsEntry.IsDisabled()); 231 | }; 232 | buttonLogic = inkWidgetRef.GetControllerByType(this.m_offState, n"inkButtonController") as inkButtonController; 233 | if IsDefined(buttonLogic) { 234 | buttonLogic.SetEnabled(!this.m_SettingsEntry.IsDisabled()); 235 | }; 236 | } 237 | 238 | protected cb func OnInitialize() -> Bool { 239 | super.OnInitialize(); 240 | if inkWidgetRef.IsValid(this.m_offStateBody) { 241 | inkWidgetRef.RegisterToCallback(this.m_offStateBody, n"OnRelease", this, n"OnLeft"); 242 | }; 243 | if inkWidgetRef.IsValid(this.m_onStateBody) { 244 | inkWidgetRef.RegisterToCallback(this.m_onStateBody, n"OnRelease", this, n"OnRight"); 245 | }; 246 | if inkWidgetRef.IsValid(this.m_Raycaster) { 247 | this.RegisterToCallback(n"OnRelease", this, n"OnShortcutPress"); 248 | }; 249 | } 250 | 251 | private func AcceptValue(forward: Bool) -> Void { 252 | let boolValue: ref = this.m_SettingsEntry as ModConfigVarBool; 253 | boolValue.Toggle(); 254 | this.Refresh(); 255 | } 256 | } 257 | 258 | public class ModStngsSelectorControllerFloat extends SettingsSelectorControllerRange { 259 | public let m_newValue: Float; 260 | private let m_sliderWidget: inkWidgetRef; 261 | private let m_sliderController: wref; 262 | 263 | public func Setup(entry: ref, isPreGame: Bool) -> Void { 264 | let value: ref; 265 | super.Setup(entry, isPreGame); 266 | value = this.m_SettingsEntry as ModConfigVarFloat; 267 | this.m_sliderController = inkWidgetRef.GetControllerByType(this.m_sliderWidget, n"inkSliderController") as inkSliderController; 268 | this.m_sliderController.Setup(value.GetMinValue(), value.GetMaxValue(), this.m_newValue, value.GetStepValue()); 269 | this.m_sliderController.RegisterToCallback(n"OnSliderValueChanged", this, n"OnSliderValueChanged"); 270 | this.m_sliderController.RegisterToCallback(n"OnSliderHandleReleased", this, n"OnHandleReleased"); 271 | } 272 | 273 | protected cb func OnSliderValueChanged(sliderController: wref, progress: Float, value: Float) -> Bool { 274 | this.m_newValue = value; 275 | this.Refresh(); 276 | } 277 | 278 | protected cb func OnHandleReleased() -> Bool { 279 | let value: ref = this.m_SettingsEntry as ModConfigVarFloat; 280 | value.SetValue(this.m_newValue); 281 | } 282 | 283 | private func RegisterShortcutCallbacks() -> Void { 284 | super.RegisterShortcutCallbacks(); 285 | this.RegisterToCallback(n"OnRepeat", this, n"OnShortcutRepeat"); 286 | } 287 | 288 | private func ChangeValue(forward: Bool) -> Void { 289 | let value: ref = this.m_SettingsEntry as ModConfigVarFloat; 290 | let step: Float = forward ? value.GetStepValue() : -value.GetStepValue(); 291 | this.m_newValue = ClampF(this.m_newValue + step, value.GetMinValue(), value.GetMaxValue()); 292 | this.Refresh(); 293 | } 294 | 295 | private func AcceptValue(forward: Bool) -> Void { 296 | let value: ref = this.m_SettingsEntry as ModConfigVarFloat; 297 | if value.GetValue() == this.m_newValue { 298 | this.ChangeValue(forward); 299 | }; 300 | value.SetValue(this.m_newValue); 301 | this.Refresh(); 302 | } 303 | 304 | public func Refresh() -> Void { 305 | super.Refresh(); 306 | this.UpdateValueTextLanguageResources(); 307 | let value = this.m_SettingsEntry as ModConfigVarFloat; 308 | let step = value.GetStepValue(); 309 | let prec = 1; 310 | if (step < 0.1) { 311 | prec = 2; 312 | } 313 | if (step < 0.01) { 314 | prec = 3; 315 | } 316 | if (step < 0.001) { 317 | prec = 4; 318 | } 319 | if (step < 0.0001) { 320 | prec = 5; 321 | } 322 | inkTextRef.SetText(this.m_ValueText, FloatToStringPrec(this.m_newValue, prec)); 323 | this.m_sliderController.ChangeValue(this.m_newValue); 324 | } 325 | 326 | protected cb func OnUpdateValue() -> Bool { 327 | let value: ref = this.m_SettingsEntry as ModConfigVarFloat; 328 | this.m_newValue = value.GetValue(); 329 | super.OnUpdateValue(); 330 | } 331 | } 332 | 333 | public class ModStngsSelectorControllerListInt extends SettingsSelectorControllerList { 334 | public func Setup(entry: ref, isPreGame: Bool) -> Void { 335 | let data: array; 336 | let value: ref; 337 | super.Setup(entry, isPreGame); 338 | value = this.m_SettingsEntry as ModConfigVarEnum; 339 | data = value.GetValues(); 340 | this.PopulateDots(ArraySize(data)); 341 | this.SelectDot(value.GetIndex()); 342 | } 343 | 344 | private func ChangeValue(forward: Bool) -> Void { 345 | let value: ref = this.m_SettingsEntry as ModConfigVarEnum; 346 | let listElements: array = value.GetValues(); 347 | let index: Int32 = value.GetIndex(); 348 | let newIndex: Int32 = index + (forward ? 1 : -1); 349 | if newIndex < 0 { 350 | newIndex = ArraySize(listElements) - 1; 351 | } else { 352 | if newIndex >= ArraySize(listElements) { 353 | newIndex = 0; 354 | }; 355 | }; 356 | if index != newIndex { 357 | value.SetIndex(newIndex); 358 | }; 359 | } 360 | 361 | public func Refresh() -> Void { 362 | let index: Int32; 363 | let value: ref; 364 | super.Refresh(); 365 | value = this.m_SettingsEntry as ModConfigVarEnum; 366 | index = value.GetIndex(); 367 | this.UpdateValueTextLanguageResources(); 368 | if !value.ListHasDisplayValues() { 369 | inkTextRef.SetText(this.m_ValueText, IntToString(value.GetValue())); 370 | } else { 371 | let text = GetLocalizedTextByKey(value.GetDisplayValue(index)); 372 | if StrLen(text) == 0 { 373 | text = ToString(value.GetDisplayValue(index)); 374 | }; 375 | inkTextRef.SetText(this.m_ValueText, text); 376 | }; 377 | this.SelectDot(index); 378 | } 379 | } -------------------------------------------------------------------------------- /src/redscript/mod_settings/_deathMenu.reds: -------------------------------------------------------------------------------- 1 | @replaceMethod(DeathMenuGameController) 2 | private func PopulateMenuItemList() -> Void { 3 | if this.GetSystemRequestsHandler().HasLastCheckpoint() { 4 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-LoadLastSavegame"), PauseMenuAction.QuickLoad); 5 | }; 6 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-LoadGame0"), n"OnSwitchToLoadGame"); 7 | this.AddMenuItem(GetLocalizedText("UI-Labels-Settings"), n"OnSwitchToSettings"); 8 | this.AddMenuItem("Mod Settings", n"OnSwitchToModSettings"); 9 | this.AddMenuItem(GetLocalizedText("UI-Labels-ExitToMenu"), PauseMenuAction.ExitToMainMenu); 10 | this.m_menuListController.Refresh(); 11 | this.SetCursorOverWidget(inkCompoundRef.GetWidgetByIndex(this.m_menuList, 0)); 12 | } -------------------------------------------------------------------------------- /src/redscript/mod_settings/_pauseMenu.reds: -------------------------------------------------------------------------------- 1 | @replaceMethod(PauseMenuGameController) 2 | private func PopulateMenuItemList() -> Void { 3 | this.AddMenuItem(GetLocalizedText("UI-Labels-Resume"), n"OnClosePauseMenu"); 4 | if !IsFinal() { 5 | this.AddMenuItem("OPEN DEBUG MENU", n"OnOpenDebugHubMenu"); 6 | }; 7 | this.AddMenuItem(GetLocalizedText("UI-ResourceExports-SaveGame"), PauseMenuAction.Save); 8 | if this.m_savesCount > 0 { 9 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-LoadGame0"), n"OnSwitchToLoadGame"); 10 | }; 11 | this.AddMenuItem(GetLocalizedText("UI-Labels-Settings"), n"OnSwitchToSettings"); 12 | this.AddMenuItem("Mod Settings", n"OnSwitchToModSettings"); 13 | this.AddMenuItem(GetLocalizedText("UI-DLC-MenuTitle"), n"OnSwitchToDlc"); 14 | this.AddMenuItem(GetLocalizedText("UI-Labels-Credits"), n"OnSwitchToCredits"); 15 | if TrialHelper.IsInPS5TrialMode() { 16 | this.AddMenuItem(GetLocalizedText("UI-Notifications-Ps5TrialBuyMenuItem"), n"OnBuyGame"); 17 | }; 18 | this.AddMenuItem(GetLocalizedText("UI-Labels-ExitToMenu"), PauseMenuAction.ExitToMainMenu); 19 | this.m_menuListController.Refresh(); 20 | this.SetCursorOverWidget(inkCompoundRef.GetWidgetByIndex(this.m_menuList, 0)); 21 | } -------------------------------------------------------------------------------- /src/redscript/mod_settings/_pauseScenario.reds: -------------------------------------------------------------------------------- 1 | @addMethod(MenuScenario_PauseMenu ) 2 | protected cb func OnSwitchToModSettings() -> Bool { 3 | this.SwitchMenu(n"mod_settings_main"); 4 | } -------------------------------------------------------------------------------- /src/redscript/mod_settings/_preGameScenarios.reds: -------------------------------------------------------------------------------- 1 | @addMethod(MenuScenario_SingleplayerMenu) 2 | protected cb func OnSwitchToModSettings() -> Bool { 3 | this.CloseSubMenu(); 4 | this.SwitchToScenario(n"MenuScenario_ModSettings"); 5 | } 6 | 7 | @addMethod(MenuScenario_SingleplayerMenu) 8 | protected cb func OnCloseModSettings() -> Bool { 9 | if Equals(this.m_currSubMenuName, n"mod_settings_main") { 10 | this.CloseSubMenu(); 11 | }; 12 | } 13 | 14 | 15 | 16 | public class MenuScenario_ModSettings extends MenuScenario_PreGameSubMenu { 17 | 18 | protected cb func OnEnterScenario(prevScenario: CName, userData: ref) -> Bool { 19 | super.OnEnterScenario(prevScenario, userData); 20 | this.GetMenusState().OpenMenu(n"mod_settings_main", userData); 21 | } 22 | 23 | protected cb func OnLeaveScenario(nextScenario: CName) -> Bool { 24 | super.OnLeaveScenario(nextScenario); 25 | this.GetMenusState().CloseMenu(n"mod_settings_main"); 26 | } 27 | 28 | protected func OnSubmenuOpen() -> Void { 29 | this.GetMenusState().CloseMenu(n"mod_settings_main"); 30 | } 31 | 32 | protected cb func OnSettingsBack() -> Bool { 33 | if NotEquals(this.m_currSubMenuName, n"") { 34 | this.CloseSubMenu(); 35 | this.GetMenusState().OpenMenu(n"mod_settings_main"); 36 | } else { 37 | this.CloseSettings(false); 38 | }; 39 | } 40 | 41 | protected cb func OnCloseModSettingsScreen() -> Bool { 42 | this.CloseSettings(true); 43 | } 44 | 45 | private final func CloseSettings(forceCloseSettings: Bool) -> Void { 46 | let menuState: wref = this.GetMenusState(); 47 | if forceCloseSettings { 48 | menuState.CloseMenu(n"mod_settings_main"); 49 | if NotEquals(this.m_currSubMenuName, n"") { 50 | if !menuState.DispatchEvent(this.m_currSubMenuName, n"OnBack") { 51 | this.CloseSubMenu(); 52 | }; 53 | } else { 54 | this.SwitchToScenario(this.m_prevScenario); 55 | }; 56 | } else { 57 | menuState.DispatchEvent(n"mod_settings_main", n"OnBack"); 58 | }; 59 | } 60 | 61 | protected cb func OnMainMenuBack() -> Bool { 62 | this.SwitchToScenario(this.m_prevScenario); 63 | } 64 | } -------------------------------------------------------------------------------- /src/redscript/mod_settings/_singleplayerMenu.reds: -------------------------------------------------------------------------------- 1 | @replaceMethod(SingleplayerMenuGameController) 2 | private func PopulateMenuItemList() -> Void { 3 | if this.m_savesCount > 0 { 4 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-Continue0"), PauseMenuAction.QuickLoad); 5 | }; 6 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-NewGame0"), n"OnNewGame"); 7 | this.AddMenuItem(GetLocalizedText("UI-ScriptExports-LoadGame0"), n"OnLoadGame"); 8 | this.AddMenuItem(GetLocalizedText("UI-Labels-Settings"), n"OnSwitchToSettings"); 9 | this.AddMenuItem("Mod Settings", n"OnSwitchToModSettings"); 10 | this.AddMenuItem(GetLocalizedText("UI-DLC-MenuTitle"), n"OnSwitchToDlc"); 11 | this.AddMenuItem(GetLocalizedText("UI-Labels-Credits"), n"OnSwitchToCredits"); 12 | if TrialHelper.IsInPS5TrialMode() { 13 | this.AddMenuItem(GetLocalizedText("UI-Notifications-Ps5TrialBuyMenuItem"), n"OnBuyGame"); 14 | }; 15 | if !IsFinal() { 16 | this.AddMenuItem("DEBUG NEW GAME", n"OnDebug"); 17 | }; 18 | this.m_menuListController.Refresh(); 19 | this.SetCursorOverWidget(inkCompoundRef.GetWidgetByIndex(this.m_menuList, 0)); 20 | } -------------------------------------------------------------------------------- /src/wolvenkit/ModSettings.cpmodproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ModSettings 6 | 7 | false 8 | false 9 | -------------------------------------------------------------------------------- /src/wolvenkit/install_log.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | C:\Program Files (x86)\Steam\steamapps\common\Cyberpunk 2077\archive\pc\mod\ModSettings.archive 8 | C:\Program Files (x86)\Steam\steamapps\common\Cyberpunk 2077\archive\pc\mod\ModSettings.xl 9 | 10 | 11 | 12 | 13 | 14 | C:\Program Files (x86)\Steam\steamapps\common\Cyberpunk 2077\mods\ModSettings\info.json 15 | 16 | C:\Program Files (x86)\Steam\steamapps\common\Cyberpunk 2077\mods\ModSettings\archives\ModSettings.archive 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/wolvenkit/layout.xml: -------------------------------------------------------------------------------- 1 | -1095,1266,320,940Normal0-12157.71428571428602157.714285714286224LogViewModelfalsefalseDockfalseBottomLeft2157.7142857142862249090falsetruetruetruetruetrue-1095,1266,320,940false01113000,0,124,50-1false0,0,124,24NormaltrueHorizontalLeft0LeftfalseBottomNormaltruetruetruetruetruetruetruetruefalsefalsefalsefalsefalse002157.7142857142861109.142857142857909090900falsefalsefalsefalse-1876,755,1528,353Normal0-1398.85714285714283878.2857142857142398.85714285714283582.2857142857143ProjectExplorerViewModelfalsefalseDockfalseTopLeftPropertiesViewModel398.85714285714283583.42857142857149090falsetruetruetruefalsetrue-1876,755,1528,353false01112000,0,124,50-1false0,0,124,24NormaltrueHorizontalLeft0LeftfalseTopNormaltruetruetruetruetruetruetruetruefalsefalsefalsefalsefalse00398.85714285714283880909090900falsefalsefalsefalse-2024,806,1884,90Normal0-1398.85714285714283878.2857142857142398.85714285714283290.85714285714283PropertiesViewModelfalsefalseDockfalseLeftLeft398.85714285714283291.428571428571449090falsetruetruetruefalsetrue-2024,806,1884,90false01112000,0,124,50-1false0,0,124,24NormaltrueHorizontalLeft0LeftfalseLeftNormaltruetruetruetruetruetruetruetruefalsefalsefalsefalsefalse00398.85714285714283880909090900falsefalsefalsefalse-178,810,763,711Normal0-10000AssetBrowserViewModelfalsefalseAutoHiddenfalseRightLeft763909090falsetruetruetruefalsetrue-178,810,763,71133235564true7168012000,0,124,50-1false0,0,124,24NormaltrueHorizontalRight0LeftfalseRightNormaltruetruetruetruetruetruetruetruefalsefalsefalsefalsefalse0000909090900falsefalsefalsefalse930,355,235,914Normal0-10000TweakBrowserViewModelfalsefalseAutoHiddenfalseRightLeft235909090falsetruetruetruefalsetrue930,355,235,91430684626true5120512000,0,124,50-1false0,0,124,24NormaltrueHorizontalRight5LeftfalseRightNormaltruetruetruetruetruetruetruetruefalsefalsefalsefalsefalse0000909090900falsefalsefalsefalse-193,805,271,711Normal0-10000LocKeyBrowserViewModelfalsefalseAutoHiddenfalseRightLeft271909090falsetruetruetruefalsetrue-193,805,271,7117726183true6144012000,0,124,50-1false0,0,124,24NormaltrueHorizontalRight0LeftfalseRightNormaltruetruetruetruetruetruetruetruefalsefalsefalsefalsefalse0000909090900falsefalsefalsefalse -------------------------------------------------------------------------------- /src/wolvenkit/packed/archive/pc/mod/ModSettings.archive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackhumbert/mod_settings/e3a6862e86258c547027e82b431b910012090c12/src/wolvenkit/packed/archive/pc/mod/ModSettings.archive -------------------------------------------------------------------------------- /src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/main_menu/pregame_menu.inkmenu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackhumbert/mod_settings/e3a6862e86258c547027e82b431b910012090c12/src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/main_menu/pregame_menu.inkmenu -------------------------------------------------------------------------------- /src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/main_menu/pregame_menu_old.inkmenu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackhumbert/mod_settings/e3a6862e86258c547027e82b431b910012090c12/src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/main_menu/pregame_menu_old.inkmenu -------------------------------------------------------------------------------- /src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/menu.inkmenu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackhumbert/mod_settings/e3a6862e86258c547027e82b431b910012090c12/src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/menu.inkmenu -------------------------------------------------------------------------------- /src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/menu_old.inkmenu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackhumbert/mod_settings/e3a6862e86258c547027e82b431b910012090c12/src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/menu_old.inkmenu -------------------------------------------------------------------------------- /src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/settings/mod_settings_main.inkwidget: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackhumbert/mod_settings/e3a6862e86258c547027e82b431b910012090c12/src/wolvenkit/source/archive/base/gameplay/gui/fullscreen/settings/mod_settings_main.inkwidget -------------------------------------------------------------------------------- /src/wolvenkit/source/archive/base/localization/en-us/onscreens/mod_settings.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackhumbert/mod_settings/e3a6862e86258c547027e82b431b910012090c12/src/wolvenkit/source/archive/base/localization/en-us/onscreens/mod_settings.json -------------------------------------------------------------------------------- /tools/ModStngs.1sc: -------------------------------------------------------------------------------- 1 | //------------------------------------------------ 2 | //--- 010 Editor v13.0.1 Script File 3 | // 4 | // File: 5 | // Authors: 6 | // Version: 7 | // Purpose: 8 | // Category: 9 | // History: 10 | //------------------------------------------------ 11 | 12 | Printf("Unkarking\n"); 13 | 14 | #include "C:\Users\Jack\Documents\SweetScape\010 Scripts\Repository\CP77_CR2W_Unkarkify.1sc" 15 | 16 | TFindResults r, changed; 17 | 18 | changed = FindAll("ModStngs"); 19 | if (changed.count) { 20 | Printf("Removing ModStngs\n"); 21 | 22 | r = FindAll("ModStngsMainGameController"); 23 | for( i = 0; i < r.count; i++ ) 24 | WriteString(r.start[i], "SettingsMainGameController"); 25 | 26 | r = FindAll("ModStngsSelectorControllerBool"); 27 | for( i = 0; i < r.count; i++ ) 28 | WriteString(r.start[i], "SettingsSelectorControllerBool"); 29 | 30 | r = FindAll("ModStngsSelectorControllerInt"); 31 | for( i = 0; i < r.count; i++ ) 32 | WriteString(r.start[i], "SettingsSelectorControllerInt"); 33 | 34 | r = FindAll("ModStngsSelectorControllerFloat"); 35 | for( i = 0; i < r.count; i++ ) 36 | WriteString(r.start[i], "SettingsSelectorControllerFloat"); 37 | 38 | r = FindAll("ModStngsSelectorControllerListInt"); 39 | for( i = 0; i < r.count; i++ ) 40 | WriteString(r.start[i], "SettingsSelectorControllerListInt"); 41 | 42 | } else { 43 | 44 | Printf("Adding ModStngs\n"); 45 | r = FindAll("SettingsMainGameController"); 46 | for( i = 0; i < r.count; i++ ) 47 | WriteString(r.start[i], "ModStngsMainGameController"); 48 | 49 | r = FindAll("SettingsSelectorControllerBool"); 50 | for( i = 0; i < r.count; i++ ) 51 | WriteString(r.start[i], "ModStngsSelectorControllerBool"); 52 | 53 | r = FindAll("SettingsSelectorControllerInt"); 54 | for( i = 0; i < r.count; i++ ) 55 | WriteString(r.start[i], "ModStngsSelectorControllerInt"); 56 | 57 | r = FindAll("SettingsSelectorControllerFloat"); 58 | for( i = 0; i < r.count; i++ ) 59 | WriteString(r.start[i], "ModStngsSelectorControllerFloat"); 60 | 61 | r = FindAll("SettingsSelectorControllerListInt"); 62 | for( i = 0; i < r.count; i++ ) 63 | WriteString(r.start[i], "ModStngsSelectorControllerListInt"); 64 | } 65 | 66 | Printf("Re-karking\n"); 67 | #include "C:\Users\Jack\Documents\SweetScape\010 Scripts\Repository\CP77_CR2W_Rebuild.1sc" 68 | 69 | Printf("Saving\n"); 70 | FileSave(); 71 | Printf("Completed!\n"); --------------------------------------------------------------------------------