├── .gitattributes ├── .gitignore ├── HighFPSFix.sln ├── HighFPSFix.vcxproj ├── HighFPSFix.vcxproj.filters ├── LICENSE.txt ├── README.md ├── calls.h ├── common ├── IDataStream.cpp ├── IDataStream.h ├── IDebugLog.cpp ├── IDebugLog.h ├── IDynamicCreate.cpp ├── IDynamicCreate.h ├── IErrors.cpp ├── IErrors.h ├── IPrefix.cpp ├── IPrefix.h ├── ISingleton.cpp ├── ISingleton.h ├── ITypes.cpp ├── ITypes.h ├── PluginAPI.h ├── SafeWrite.cpp ├── SafeWrite.h ├── Utilities.cpp ├── Utilities.h ├── utility.cpp └── utility.h ├── dllmain.c ├── fps.h └── main.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.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 results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /HighFPSFix.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.2.32630.192 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HighFPSFix", "HighFPSFix.vcxproj", "{AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Win32 = Debug|Win32 10 | Release|Win32 = Release|Win32 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}.Debug|Win32.ActiveCfg = Debug|Win32 14 | {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}.Debug|Win32.Build.0 = Debug|Win32 15 | {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}.Release|Win32.ActiveCfg = Release|Win32 16 | {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}.Release|Win32.Build.0 = Release|Win32 17 | EndGlobalSection 18 | GlobalSection(SolutionProperties) = preSolution 19 | HideSolutionNode = FALSE 20 | EndGlobalSection 21 | GlobalSection(ExtensibilityGlobals) = postSolution 22 | SolutionGuid = {71BF81C1-7B0B-49CB-92D7-5D7E4612E20C} 23 | EndGlobalSection 24 | EndGlobal 25 | -------------------------------------------------------------------------------- /HighFPSFix.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028} 15 | nvse_plugin_example 16 | Win32Proj 17 | 10.0 18 | HighFPSFix 19 | 20 | 21 | 22 | DynamicLibrary 23 | v143 24 | MultiByte 25 | true 26 | 27 | 28 | DynamicLibrary 29 | v143 30 | MultiByte 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | <_ProjectFileVersion>16.0.28916.169 44 | 45 | 46 | $(SolutionDir)$(Configuration)\ 47 | $(Configuration)\ 48 | true 49 | $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(DXSDK_DIR)Lib\x86 50 | $(VC_IncludePath);$(WindowsSDK_IncludePath);$(DXSDK_DIR)Include 51 | true 52 | 53 | 54 | $(SolutionDir)$(Configuration)\ 55 | $(Configuration)\ 56 | false 57 | $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86 58 | $(VC_IncludePath);$(WindowsSDK_IncludePath) 59 | true 60 | 61 | 62 | 63 | Disabled 64 | $(SolutionDir);$(SolutionDir)\common;%(AdditionalIncludeDirectories) 65 | WIN32;_DEBUG;_WINDOWS;_USRDLL;NVSE_PLUGIN_EXAMPLE_EXPORTS;RUNTIME_VERSION=0x040020D0;RUNTIME;SSAA;%(PreprocessorDefinitions) 66 | EnableFastChecks 67 | MultiThreadedDebugDLL 68 | 69 | Level3 70 | ProgramDatabase 71 | %(ForcedIncludeFiles) 72 | stdcpp20 73 | true 74 | 75 | 76 | 77 | 78 | true 79 | Windows 80 | false 81 | 82 | MachineX86 83 | winmm.lib;%(AdditionalDependencies) 84 | 85 | 86 | Installing DLL... 87 | copy "$(TargetPath)" "C:\Users\user\AppData\Local\ModOrganizer\Fallout 3\mods\High FPS Fix\fose\plugins\$(TargetFileName)" 88 | 89 | 90 | 91 | 92 | $(SolutionDir);$(SolutionDir)\common;;%(AdditionalIncludeDirectories) 93 | WIN32;NDEBUG;_WINDOWS;_USRDLL;NVSE_PLUGIN_EXAMPLE_EXPORTS;RUNTIME_VERSION=0x040020D0;RUNTIME;%(PreprocessorDefinitions) 94 | MultiThreadedDLL 95 | 96 | Level3 97 | ProgramDatabase 98 | %(ForcedIncludeFiles) 99 | stdcpp20 100 | Speed 101 | StreamingSIMDExtensions2 102 | Default 103 | false 104 | true 105 | Fast 106 | true 107 | true 108 | Full 109 | false 110 | 111 | 112 | 113 | 114 | true 115 | Windows 116 | true 117 | true 118 | false 119 | 120 | MachineX86 121 | winmm.lib;%(AdditionalDependencies) 122 | 123 | 124 | 125 | 126 | copy "$(TargetPath)" "C:\Users\ykzmd\AppData\Local\ModOrganizer\Fallout 3\mods\High FPS Fix\fose\plugins\$(TargetFileName)" 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /HighFPSFix.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {92a4f350-f2ec-4fca-bdd3-927732d91b34} 6 | 7 | 8 | 9 | 10 | 11 | 12 | fose 13 | 14 | 15 | 16 | 17 | fose 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # High FPS Physics Fix 2 | 3 | Fixes gameplay issues that occur in Fallout 3 when the game is running at framerates over 60. 4 | 5 | **Please note that 120 is considered a safe maximum and going above that is not advised.** 6 | 7 | To learn how to configure the game and your drivers for the best possible performance, read and follow [The Performance Guide](https://performance.moddinglinked.com/falloutnv.html#DriversAndOS) - most of it applies to both New Vegas and Fallout 3. 8 | 9 | Ported from [New Vegas Tick Fix](https://www.nexusmods.com/newvegas/mods/66537). 10 | 11 | Incompatible with [Fallout 3 Tick Fix](https://www.nexusmods.com/fallout3/mods/23542) and `bTickFix` option in [Stewie's Tweaks](https://www.nexusmods.com/fallout3/mods/23561) - their functionality is included. 12 | 13 | *Credits to carxt for the original code.* 14 | -------------------------------------------------------------------------------- /calls.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // thread-safe template versions of ThisStdCall() 3 | 4 | template 5 | __forceinline T_Ret ThisStdCall(UInt32 _addr, void* _this, Args ...args) { 6 | class T {}; 7 | union { 8 | UInt32 addr; 9 | T_Ret(T::* func)(Args...); 10 | } u = { _addr }; 11 | return ((T*)_this->*u.func)(std::forward(args)...); 12 | } 13 | template 14 | __forceinline T_Ret StdCall(UInt32 _addr, Args ...args) { 15 | return ((T_Ret(__stdcall*)(Args...))_addr)(std::forward(args)...); 16 | } 17 | 18 | template 19 | __forceinline T_Ret CdeclCall(UInt32 _addr, Args ...args) { 20 | return ((T_Ret(__cdecl*)(Args...))_addr)(std::forward(args)...); 21 | } 22 | // Templates for UInt8 return. 23 | 24 | __forceinline UInt8 ThisStdCall_B(UInt32 _f, void* _t) { 25 | class T {}; union { UInt32 x; UInt8(T::* m)(); } u = { _f }; 26 | return ((T*)_t->*u.m)(); 27 | } 28 | 29 | template 30 | __forceinline UInt8 ThisStdCall_B(UInt32 _f, void* _t, T1 a1) { 31 | class T {}; union { UInt32 x; UInt8(T::* m)(T1); } u = { _f }; 32 | return ((T*)_t->*u.m)(a1); 33 | } 34 | 35 | template 36 | __forceinline UInt8 ThisStdCall_B(UInt32 _f, void* _t, T1 a1, T2 a2) { 37 | class T {}; union { UInt32 x; UInt8(T::* m)(T1, T2); } u = { _f }; 38 | return ((T*)_t->*u.m)(a1, a2); 39 | } 40 | 41 | template 42 | __forceinline UInt8 ThisStdCall_B(UInt32 _f, void* _t, T1 a1, T2 a2, T3 a3) { 43 | class T {}; union { UInt32 x; UInt8(T::* m)(T1, T2, T3); } u = { _f }; 44 | return ((T*)_t->*u.m)(a1, a2, a3); 45 | } 46 | 47 | // Templates for float return. 48 | 49 | __forceinline float ThisStdCall_F(UInt32 _f, void* _t) { 50 | class T {}; union { UInt32 x; float (T::* m)(); } u = { _f }; 51 | return ((T*)_t->*u.m)(); 52 | } 53 | 54 | template 55 | __forceinline float ThisStdCall_F(UInt32 _f, void* _t, T1 a1) { 56 | class T {}; union { UInt32 x; float (T::* m)(T1); } u = { _f }; 57 | return ((T*)_t->*u.m)(a1); 58 | } -------------------------------------------------------------------------------- /common/IDataStream.cpp: -------------------------------------------------------------------------------- 1 | #include "IDataStream.h" 2 | 3 | /**** IDataStream *************************************************************/ 4 | 5 | IDataStream::IDataStream() 6 | :streamLength(0), streamOffset(0), swapBytes(false) 7 | { 8 | } 9 | 10 | IDataStream::~IDataStream() 11 | { 12 | } 13 | 14 | /** 15 | * Reads and returns an 8-bit value from the stream 16 | */ 17 | UInt8 IDataStream::Read8(void) 18 | { 19 | UInt8 out; 20 | 21 | ReadBuf(&out, sizeof(UInt8)); 22 | 23 | return out; 24 | } 25 | 26 | /** 27 | * Reads and returns a 16-bit value from the stream 28 | */ 29 | UInt16 IDataStream::Read16(void) 30 | { 31 | UInt16 out; 32 | 33 | ReadBuf(&out, sizeof(UInt16)); 34 | 35 | if (swapBytes) 36 | out = Swap16(out); 37 | 38 | return out; 39 | } 40 | 41 | /** 42 | * Reads and returns a 32-bit value from the stream 43 | */ 44 | UInt32 IDataStream::Read32(void) 45 | { 46 | UInt32 out; 47 | 48 | ReadBuf(&out, sizeof(UInt32)); 49 | 50 | if (swapBytes) 51 | out = Swap32(out); 52 | 53 | return out; 54 | } 55 | 56 | /** 57 | * Reads and returns a 64-bit value from the stream 58 | */ 59 | UInt64 IDataStream::Read64(void) 60 | { 61 | UInt64 out; 62 | 63 | ReadBuf(&out, sizeof(UInt64)); 64 | 65 | if (swapBytes) 66 | out = Swap64(out); 67 | 68 | return out; 69 | } 70 | 71 | /** 72 | * Reads and returns a 32-bit floating point value from the stream 73 | */ 74 | float IDataStream::ReadFloat(void) 75 | { 76 | UInt32 out = Read32(); 77 | 78 | return *((float*)&out); 79 | } 80 | 81 | /** 82 | * Reads a null-or-return-terminated string from the stream 83 | * 84 | * If the buffer is too small to hold the entire string, it is truncated and 85 | * properly terminated. 86 | * 87 | * @param buf the output buffer 88 | * @param bufLength the size of the output buffer 89 | * @return the number of characters written to the buffer 90 | */ 91 | UInt32 IDataStream::ReadString(char* buf, UInt32 bufLength, char altTerminator, char altTerminator2) 92 | { 93 | char* traverse = buf; 94 | bool breakOnReturns = false; 95 | 96 | if ((altTerminator == '\n') || (altTerminator2 == '\n')) 97 | breakOnReturns = true; 98 | 99 | ASSERT_STR(bufLength > 0, "IDataStream::ReadString: zero-sized buffer"); 100 | 101 | if (bufLength == 1) 102 | { 103 | buf[0] = 0; 104 | return 0; 105 | } 106 | 107 | bufLength--; 108 | 109 | for (UInt32 i = 0; i < bufLength; i++) 110 | { 111 | UInt8 data = Read8(); 112 | 113 | if (breakOnReturns) 114 | { 115 | if (data == 0x0D) 116 | { 117 | if (Peek8() == 0x0A) 118 | Skip(1); 119 | 120 | break; 121 | } 122 | } 123 | 124 | if (!data || (data == altTerminator) || (data == altTerminator2)) 125 | { 126 | break; 127 | } 128 | 129 | *traverse++ = data; 130 | } 131 | 132 | *traverse++ = 0; 133 | 134 | return traverse - buf - 1; 135 | } 136 | 137 | /** 138 | * Reads and returns an 8-bit value from the stream without advancing the stream's position 139 | */ 140 | UInt8 IDataStream::Peek8(void) 141 | { 142 | IDataStream_PositionSaver saver(this); 143 | 144 | return Read8(); 145 | } 146 | 147 | /** 148 | * Reads and returns a 16-bit value from the stream without advancing the stream's position 149 | */ 150 | UInt16 IDataStream::Peek16(void) 151 | { 152 | IDataStream_PositionSaver saver(this); 153 | 154 | return Read16(); 155 | } 156 | 157 | /** 158 | * Reads and returns a 32-bit value from the stream without advancing the stream's position 159 | */ 160 | UInt32 IDataStream::Peek32(void) 161 | { 162 | IDataStream_PositionSaver saver(this); 163 | 164 | return Read32(); 165 | } 166 | 167 | /** 168 | * Reads and returns a 32-bit value from the stream without advancing the stream's position 169 | */ 170 | UInt64 IDataStream::Peek64(void) 171 | { 172 | IDataStream_PositionSaver saver(this); 173 | 174 | return Read64(); 175 | } 176 | 177 | /** 178 | * Reads and returns a 32-bit floating point value from the stream without advancing the stream's position 179 | */ 180 | float IDataStream::PeekFloat(void) 181 | { 182 | IDataStream_PositionSaver saver(this); 183 | 184 | return ReadFloat(); 185 | } 186 | 187 | /** 188 | * Reads raw data into a buffer without advancing the stream's position 189 | */ 190 | void IDataStream::PeekBuf(void* buf, UInt32 inLength) 191 | { 192 | IDataStream_PositionSaver saver(this); 193 | 194 | ReadBuf(buf, inLength); 195 | } 196 | 197 | /** 198 | * Skips a specified number of bytes down the stream 199 | */ 200 | void IDataStream::Skip(SInt64 inBytes) 201 | { 202 | SetOffset(GetOffset() + inBytes); 203 | } 204 | 205 | /** 206 | * Writes an 8-bit value to the stream. 207 | */ 208 | void IDataStream::Write8(UInt8 inData) 209 | { 210 | WriteBuf(&inData, sizeof(UInt8)); 211 | } 212 | 213 | /** 214 | * Writes a 16-bit value to the stream. 215 | */ 216 | void IDataStream::Write16(UInt16 inData) 217 | { 218 | if (swapBytes) 219 | inData = Swap16(inData); 220 | 221 | WriteBuf(&inData, sizeof(UInt16)); 222 | } 223 | 224 | /** 225 | * Writes a 32-bit value to the stream. 226 | */ 227 | void IDataStream::Write32(UInt32 inData) 228 | { 229 | if (swapBytes) 230 | inData = Swap32(inData); 231 | 232 | WriteBuf(&inData, sizeof(UInt32)); 233 | } 234 | 235 | /** 236 | * Writes a 64-bit value to the stream. 237 | */ 238 | void IDataStream::Write64(UInt64 inData) 239 | { 240 | if (swapBytes) 241 | inData = Swap64(inData); 242 | 243 | WriteBuf(&inData, sizeof(UInt64)); 244 | } 245 | 246 | /** 247 | * Writes a 32-bit floating point value to the stream. 248 | */ 249 | void IDataStream::WriteFloat(float inData) 250 | { 251 | if (swapBytes) 252 | { 253 | UInt32 temp = *((UInt32*)&inData); 254 | 255 | temp = Swap32(temp); 256 | 257 | WriteBuf(&temp, sizeof(UInt32)); 258 | } 259 | else 260 | { 261 | WriteBuf(&inData, sizeof(float)); 262 | } 263 | } 264 | 265 | /** 266 | * Writes a null-terminated string to the stream. 267 | */ 268 | void IDataStream::WriteString(const char* buf) 269 | { 270 | WriteBuf(buf, std::strlen(buf) + 1); 271 | } 272 | 273 | /** 274 | * Returns the length of the stream 275 | */ 276 | SInt64 IDataStream::GetLength(void) 277 | { 278 | return streamLength; 279 | } 280 | 281 | /** 282 | * Returns the number of bytes remaining in the stream 283 | */ 284 | SInt64 IDataStream::GetRemain(void) 285 | { 286 | return streamLength - streamOffset; 287 | } 288 | 289 | /** 290 | * Returns the current offset into the stream 291 | */ 292 | SInt64 IDataStream::GetOffset(void) 293 | { 294 | return streamOffset; 295 | } 296 | 297 | /** 298 | * Returns whether we have reached the end of the stream or not 299 | */ 300 | bool IDataStream::HitEOF(void) 301 | { 302 | return streamOffset >= streamLength; 303 | } 304 | 305 | /** 306 | * Moves the current offset into the stream 307 | */ 308 | void IDataStream::SetOffset(SInt64 inOffset) 309 | { 310 | streamOffset = inOffset; 311 | } 312 | 313 | /** 314 | * Enables or disables byte swapping for basic data transfers 315 | */ 316 | void IDataStream::SwapBytes(bool inSwapBytes) 317 | { 318 | swapBytes = inSwapBytes; 319 | } 320 | 321 | IDataStream* IDataStream::GetRootParent(void) 322 | { 323 | IDataStream* parent = GetParent(); 324 | 325 | if (parent) 326 | return parent->GetRootParent(); 327 | else 328 | return this; 329 | } 330 | 331 | void IDataStream::CopyStreams(IDataStream* out, IDataStream* in, UInt64 bufferSize, UInt8* buf) 332 | { 333 | in->Rewind(); 334 | 335 | bool ourBuffer = false; 336 | 337 | if (!buf) 338 | { 339 | buf = new UInt8[bufferSize]; 340 | ourBuffer = true; 341 | } 342 | 343 | UInt64 remain = in->GetLength(); 344 | 345 | while (remain > 0) 346 | { 347 | UInt64 transferSize = remain; 348 | 349 | if (transferSize > bufferSize) 350 | transferSize = bufferSize; 351 | 352 | in->ReadBuf(buf, transferSize); 353 | out->WriteBuf(buf, transferSize); 354 | 355 | remain -= transferSize; 356 | } 357 | 358 | if (ourBuffer) 359 | delete[] buf; 360 | } 361 | 362 | void IDataStream::CopySubStreams(IDataStream* out, IDataStream* in, UInt64 remain, UInt64 bufferSize, UInt8* buf) 363 | { 364 | bool ourBuffer = false; 365 | 366 | if (!buf) 367 | { 368 | buf = new UInt8[bufferSize]; 369 | ourBuffer = true; 370 | } 371 | 372 | while (remain > 0) 373 | { 374 | UInt64 transferSize = remain; 375 | 376 | if (transferSize > bufferSize) 377 | transferSize = bufferSize; 378 | 379 | in->ReadBuf(buf, transferSize); 380 | out->WriteBuf(buf, transferSize); 381 | 382 | remain -= transferSize; 383 | } 384 | 385 | if (ourBuffer) 386 | delete[] buf; 387 | } 388 | 389 | /**** IDataStream_PositionSaver ***********************************************/ 390 | 391 | /** 392 | * The constructor; save the stream's position 393 | */ 394 | IDataStream_PositionSaver::IDataStream_PositionSaver(IDataStream* tgt) 395 | { 396 | stream = tgt; 397 | offset = tgt->GetOffset(); 398 | } 399 | 400 | /** 401 | * The destructor; restore the stream's saved position 402 | */ 403 | IDataStream_PositionSaver::~IDataStream_PositionSaver() 404 | { 405 | stream->SetOffset(offset); 406 | } 407 | 408 | /**** IDataSubStream **********************************************************/ 409 | 410 | IDataSubStream::IDataSubStream() 411 | :stream(NULL), subBase(0) 412 | { 413 | // 414 | } 415 | 416 | IDataSubStream::IDataSubStream(IDataStream* inStream, SInt64 inOffset, SInt64 inLength) 417 | { 418 | stream = inStream; 419 | subBase = inOffset; 420 | streamLength = inLength; 421 | 422 | stream->SetOffset(inOffset); 423 | } 424 | 425 | IDataSubStream::~IDataSubStream() 426 | { 427 | } 428 | 429 | void IDataSubStream::Attach(IDataStream* inStream, SInt64 inOffset, SInt64 inLength) 430 | { 431 | stream = inStream; 432 | subBase = inOffset; 433 | streamLength = inLength; 434 | 435 | stream->SetOffset(inOffset); 436 | } 437 | 438 | void IDataSubStream::ReadBuf(void* buf, UInt32 inLength) 439 | { 440 | ASSERT_STR(inLength <= GetRemain(), "IDataSubStream::ReadBuf: hit eof"); 441 | 442 | if (stream->GetOffset() != subBase + streamOffset) 443 | stream->SetOffset(subBase + streamOffset); 444 | 445 | stream->ReadBuf(buf, inLength); 446 | 447 | streamOffset += inLength; 448 | } 449 | 450 | void IDataSubStream::WriteBuf(const void* buf, UInt32 inLength) 451 | { 452 | if (stream->GetOffset() != subBase + streamOffset) 453 | stream->SetOffset(subBase + streamOffset); 454 | 455 | stream->WriteBuf(buf, inLength); 456 | 457 | streamOffset += inLength; 458 | 459 | if (streamLength < streamOffset) 460 | streamLength = streamOffset; 461 | } 462 | 463 | void IDataSubStream::SetOffset(SInt64 inOffset) 464 | { 465 | stream->SetOffset(subBase + inOffset); 466 | streamOffset = inOffset; 467 | } -------------------------------------------------------------------------------- /common/IDataStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/IErrors.h" 4 | 5 | /** 6 | * An arbitrary data stream 7 | */ 8 | class IDataStream 9 | { 10 | public: 11 | IDataStream(); 12 | virtual ~IDataStream(); 13 | 14 | // read 15 | virtual UInt8 Read8(void); 16 | virtual UInt16 Read16(void); 17 | virtual UInt32 Read32(void); 18 | virtual UInt64 Read64(void); 19 | virtual float ReadFloat(void); 20 | virtual UInt32 ReadString(char* buf, UInt32 bufLength, char altTerminator = 0, char altTerminator2 = 0); 21 | virtual void ReadBuf(void* buf, UInt32 inLength) = 0; 22 | 23 | // peek 24 | virtual UInt8 Peek8(void); 25 | virtual UInt16 Peek16(void); 26 | virtual UInt32 Peek32(void); 27 | virtual UInt64 Peek64(void); 28 | virtual float PeekFloat(void); 29 | virtual void PeekBuf(void* buf, UInt32 inLength); 30 | 31 | virtual void Skip(SInt64 inBytes); 32 | 33 | // write 34 | virtual void Write8(UInt8 inData); 35 | virtual void Write16(UInt16 inData); 36 | virtual void Write32(UInt32 inData); 37 | virtual void Write64(UInt64 inData); 38 | virtual void WriteFloat(float inData); 39 | virtual void WriteString(const char* buf); 40 | virtual void WriteBuf(const void* buf, UInt32 inLength) = 0; 41 | 42 | SInt64 GetLength(void); 43 | SInt64 GetRemain(void); 44 | SInt64 GetOffset(void); 45 | bool HitEOF(void); 46 | 47 | virtual void SetOffset(SInt64 inOffset); 48 | void Rewind(void) { SetOffset(0); } 49 | 50 | void SwapBytes(bool inSwapBytes); 51 | 52 | virtual SInt64 GetParentOffset(void) { return GetOffset(); } 53 | virtual IDataStream* GetParent(void) { return NULL; } 54 | 55 | IDataStream* GetRootParent(void); 56 | 57 | static void CopyStreams(IDataStream* out, IDataStream* in, UInt64 bufferSize = 1024 * 1024, UInt8* buf = NULL); 58 | static void CopySubStreams(IDataStream* out, IDataStream* in, UInt64 remain, UInt64 bufferSize = 1024 * 1024, UInt8* buf = NULL); 59 | 60 | protected: 61 | SInt64 streamLength; 62 | SInt64 streamOffset; 63 | bool swapBytes; 64 | }; 65 | 66 | /** 67 | * A utility class to automatically save and restore the current position of an IDataStream 68 | */ 69 | class IDataStream_PositionSaver 70 | { 71 | public: 72 | IDataStream_PositionSaver(IDataStream* tgt); 73 | ~IDataStream_PositionSaver(); 74 | 75 | private: 76 | IDataStream* stream; 77 | SInt64 offset; 78 | }; 79 | 80 | class IDataSubStream : public IDataStream 81 | { 82 | public: 83 | IDataSubStream(); 84 | IDataSubStream(IDataStream* inStream, SInt64 inOffset, SInt64 inLength); 85 | ~IDataSubStream(); 86 | 87 | void Attach(IDataStream* inStream, SInt64 inOffset, SInt64 inLength); 88 | 89 | void ReadBuf(void* buf, UInt32 inLength); 90 | void WriteBuf(const void* buf, UInt32 inLength); 91 | void SetOffset(SInt64 inOffset); 92 | 93 | virtual SInt64 GetParentOffset(void) { return stream->GetOffset(); } 94 | virtual IDataStream* GetParent(void) { return stream; } 95 | 96 | SInt64 GetSubBase(void) { return subBase; } 97 | 98 | private: 99 | IDataStream* stream; 100 | 101 | SInt64 subBase; 102 | }; 103 | -------------------------------------------------------------------------------- /common/IDebugLog.cpp: -------------------------------------------------------------------------------- 1 | #include "common/IDebugLog.h" 2 | #include 3 | #include "common/IFileStream.h" 4 | #include 5 | 6 | std::FILE* IDebugLog::logFile = NULL; 7 | char IDebugLog::sourceBuf[16] = { 0 }; 8 | char IDebugLog::headerText[16] = { 0 }; 9 | char IDebugLog::formatBuf[8192] = { 0 }; 10 | int IDebugLog::indentLevel = 0; 11 | int IDebugLog::rightMargin = 0; 12 | int IDebugLog::cursorPos = 0; 13 | int IDebugLog::inBlock = 0; 14 | bool IDebugLog::autoFlush = true; 15 | IDebugLog::LogLevel IDebugLog::logLevel = IDebugLog::kLevel_DebugMessage; 16 | IDebugLog::LogLevel IDebugLog::printLevel = IDebugLog::kLevel_Message; 17 | 18 | IDebugLog::IDebugLog() 19 | { 20 | // 21 | } 22 | 23 | IDebugLog::IDebugLog(const char* name) 24 | { 25 | Open(name); 26 | } 27 | 28 | IDebugLog::~IDebugLog() 29 | { 30 | if (logFile) 31 | fclose(logFile); 32 | } 33 | 34 | void IDebugLog::Open(const char* path) 35 | { 36 | logFile = _fsopen(path, "w", _SH_DENYWR); 37 | 38 | if (!logFile) 39 | { 40 | UInt32 id = 0; 41 | char name[1024]; 42 | 43 | do 44 | { 45 | sprintf_s(name, sizeof(name), "%s%d", path, id); 46 | id++; 47 | 48 | logFile = NULL; 49 | logFile = _fsopen(name, "w", _SH_DENYWR); 50 | } while (!logFile && (id < 5)); 51 | } 52 | } 53 | 54 | void IDebugLog::OpenRelative(int folderID, const char* relPath) 55 | { 56 | char path[MAX_PATH]; 57 | 58 | ASSERT(SUCCEEDED(SHGetFolderPath(NULL, folderID, NULL, SHGFP_TYPE_CURRENT, path))); 59 | 60 | strcat_s(path, sizeof(path), relPath); 61 | 62 | IFileStream::MakeAllDirs(path); 63 | 64 | Open(path); 65 | } 66 | 67 | /** 68 | * Output a non-formatted message to the log file 69 | * 70 | * @param message the message 71 | * @param source the source of the message, or NULL to use the previous source 72 | */ 73 | void IDebugLog::Message(const char* message, const char* source) 74 | { 75 | if (source) 76 | SetSource(source); 77 | 78 | if (inBlock) 79 | { 80 | SeekCursor(RoundToTab((indentLevel * 4) + strlen(headerText))); 81 | } 82 | else 83 | { 84 | SeekCursor(indentLevel * 4); 85 | 86 | PrintText(headerText); 87 | } 88 | 89 | PrintText(message); 90 | NewLine(); 91 | } 92 | 93 | /** 94 | * Output a formatted message to the log file 95 | * 96 | * @note It is impossible to set the source of a formatted message. 97 | * The previous source will be used. 98 | */ 99 | void IDebugLog::FormattedMessage(const char* fmt, ...) 100 | { 101 | va_list argList; 102 | 103 | va_start(argList, fmt); 104 | vsprintf_s(formatBuf, sizeof(formatBuf), fmt, argList); 105 | Message(formatBuf); 106 | va_end(argList); 107 | } 108 | 109 | /** 110 | * Output a formatted message to the log file 111 | * 112 | * @note It is impossible to set the source of a formatted message. 113 | * The previous source will be used. 114 | */ 115 | void IDebugLog::FormattedMessage(const char* fmt, va_list args) 116 | { 117 | vsprintf_s(formatBuf, sizeof(formatBuf), fmt, args); 118 | Message(formatBuf); 119 | } 120 | 121 | void IDebugLog::Log(LogLevel level, const char* fmt, va_list args) 122 | { 123 | bool log = (level <= logLevel); 124 | bool print = (level <= printLevel); 125 | 126 | if (log || print) 127 | vsprintf_s(formatBuf, sizeof(formatBuf), fmt, args); 128 | 129 | if (log) 130 | Message(formatBuf); 131 | 132 | if (print) 133 | printf("%s\n", formatBuf); 134 | } 135 | 136 | /** 137 | * Set the current message source 138 | */ 139 | void IDebugLog::SetSource(const char* source) 140 | { 141 | strcpy_s(sourceBuf, sizeof(sourceBuf), source); 142 | strcpy_s(headerText, sizeof(headerText), "[ ]\t"); 143 | 144 | char* tgt = headerText + 1; 145 | char* src = sourceBuf; 146 | 147 | for (int i = 0; (i < 8) && *src; i++, tgt++, src++) 148 | *tgt = *src; 149 | } 150 | 151 | /** 152 | * Clear the current message source 153 | */ 154 | void IDebugLog::ClearSource(void) 155 | { 156 | sourceBuf[0] = 0; 157 | } 158 | 159 | /** 160 | * Increase the indentation level 161 | */ 162 | void IDebugLog::Indent(void) 163 | { 164 | indentLevel++; 165 | } 166 | 167 | /** 168 | * Decrease the indentation level 169 | */ 170 | void IDebugLog::Outdent(void) 171 | { 172 | if (indentLevel) 173 | indentLevel--; 174 | } 175 | 176 | /** 177 | * Enter a logical block 178 | */ 179 | void IDebugLog::OpenBlock(void) 180 | { 181 | SeekCursor(indentLevel * 4); 182 | 183 | PrintText(headerText); 184 | 185 | inBlock = 1; 186 | } 187 | 188 | /** 189 | * Close a logical block 190 | */ 191 | void IDebugLog::CloseBlock(void) 192 | { 193 | inBlock = 0; 194 | } 195 | 196 | /** 197 | * Enable/disable autoflush 198 | * 199 | * @param inAutoFlush autoflush state 200 | */ 201 | void IDebugLog::SetAutoFlush(bool inAutoFlush) 202 | { 203 | autoFlush = inAutoFlush; 204 | } 205 | 206 | /** 207 | * Print spaces to the log 208 | * 209 | * If possible, tabs are used instead of spaces. 210 | */ 211 | void IDebugLog::PrintSpaces(int numSpaces) 212 | { 213 | int originalNumSpaces = numSpaces; 214 | 215 | if (logFile) 216 | { 217 | while (numSpaces > 0) 218 | { 219 | if (numSpaces >= TabSize()) 220 | { 221 | numSpaces -= TabSize(); 222 | fputc('\t', logFile); 223 | } 224 | else 225 | { 226 | numSpaces--; 227 | fputc(' ', logFile); 228 | } 229 | } 230 | } 231 | 232 | cursorPos += originalNumSpaces; 233 | } 234 | 235 | /** 236 | * Prints raw text to the log file 237 | */ 238 | void IDebugLog::PrintText(const char* buf) 239 | { 240 | if (logFile) 241 | { 242 | fputs(buf, logFile); 243 | if (autoFlush) 244 | fflush(logFile); 245 | } 246 | 247 | const char* traverse = buf; 248 | char data; 249 | 250 | while (data = *traverse++) 251 | { 252 | if (data == '\t') 253 | cursorPos += TabSize(); 254 | else 255 | cursorPos++; 256 | } 257 | } 258 | 259 | /** 260 | * Moves to the next line of the log file 261 | */ 262 | void IDebugLog::NewLine(void) 263 | { 264 | if (logFile) 265 | { 266 | fputc('\n', logFile); 267 | 268 | if (autoFlush) 269 | fflush(logFile); 270 | } 271 | 272 | cursorPos = 0; 273 | } 274 | 275 | /** 276 | * Prints spaces to align the cursor to the requested position 277 | * 278 | * @note The cursor move will not be performed if the request would move the cursor 279 | * backwards. 280 | */ 281 | void IDebugLog::SeekCursor(int position) 282 | { 283 | if (position > cursorPos) 284 | PrintSpaces(position - cursorPos); 285 | } 286 | 287 | /** 288 | * Returns the number of spaces a tab would occupy at the current cursor position 289 | */ 290 | int IDebugLog::TabSize(void) 291 | { 292 | return ((~cursorPos) & 3) + 1; 293 | } 294 | 295 | /** 296 | * Rounds a number of spaces to the nearest tab 297 | */ 298 | int IDebugLog::RoundToTab(int spaces) 299 | { 300 | return (spaces + 3) & ~3; 301 | } -------------------------------------------------------------------------------- /common/IDebugLog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * A simple debug log file 7 | * 8 | * This class supports prefix blocks describing the source of the log event. 9 | * It also allows logical blocks and outlining.\n 10 | */ 11 | class IDebugLog 12 | { 13 | public: 14 | IDebugLog(); 15 | IDebugLog(const char* name); 16 | ~IDebugLog(); 17 | 18 | static void Open(const char* path); 19 | static void OpenRelative(int folderID, const char* relPath); 20 | 21 | static void Message(const char* message, const char* source = NULL); 22 | static void FormattedMessage(const char* fmt, ...); 23 | static void FormattedMessage(const char* fmt, va_list args); 24 | 25 | enum LogLevel 26 | { 27 | kLevel_FatalError = 0, 28 | kLevel_Error, 29 | kLevel_Warning, 30 | kLevel_Message, 31 | kLevel_VerboseMessage, 32 | kLevel_DebugMessage 33 | }; 34 | 35 | static void Log(LogLevel level, const char* fmt, va_list args); 36 | 37 | static void SetSource(const char* source); 38 | static void ClearSource(void); 39 | 40 | static void Indent(void); 41 | static void Outdent(void); 42 | 43 | static void OpenBlock(void); 44 | static void CloseBlock(void); 45 | 46 | static void SetAutoFlush(bool inAutoFlush); 47 | 48 | static void SetLogLevel(LogLevel in) { logLevel = in; } 49 | static void SetPrintLevel(LogLevel in) { printLevel = in; } 50 | 51 | private: 52 | static void PrintSpaces(int numSpaces); 53 | static void PrintText(const char* buf); 54 | static void NewLine(void); 55 | 56 | static void SeekCursor(int position); 57 | 58 | static int TabSize(void); 59 | static int RoundToTab(int spaces); 60 | 61 | static FILE* logFile; //!< the output file 62 | 63 | static char sourceBuf[16]; //!< name of current source, used in prefix 64 | static char headerText[16]; //!< current text to use as line prefix 65 | static char formatBuf[8192]; //!< temp buffer used for formatted messages 66 | 67 | static int indentLevel; //!< the current indentation level (in tabs) 68 | static int rightMargin; //!< the column at which text should be wrapped 69 | static int cursorPos; //!< current cursor position 70 | static int inBlock; //!< are we in a block? 71 | 72 | static bool autoFlush; //!< automatically flush the file after writing 73 | 74 | static LogLevel logLevel; //!< least important log level to write 75 | static LogLevel printLevel; //!< least important log level to print 76 | }; 77 | 78 | extern IDebugLog gLog; 79 | 80 | inline void _FATALERROR(const char* fmt, ...) 81 | { 82 | va_list args; 83 | 84 | va_start(args, fmt); 85 | gLog.Log(IDebugLog::kLevel_FatalError, fmt, args); 86 | va_end(args); 87 | } 88 | 89 | inline void _ERROR(const char* fmt, ...) 90 | { 91 | va_list args; 92 | 93 | va_start(args, fmt); 94 | gLog.Log(IDebugLog::kLevel_Error, fmt, args); 95 | va_end(args); 96 | } 97 | 98 | inline void _WARNING(const char* fmt, ...) 99 | { 100 | va_list args; 101 | 102 | va_start(args, fmt); 103 | gLog.Log(IDebugLog::kLevel_Warning, fmt, args); 104 | va_end(args); 105 | } 106 | 107 | inline void _MESSAGE(const char* fmt, ...) 108 | { 109 | va_list args; 110 | 111 | va_start(args, fmt); 112 | gLog.Log(IDebugLog::kLevel_Message, fmt, args); 113 | va_end(args); 114 | } 115 | 116 | inline void _VMESSAGE(const char* fmt, ...) 117 | { 118 | va_list args; 119 | 120 | va_start(args, fmt); 121 | gLog.Log(IDebugLog::kLevel_VerboseMessage, fmt, args); 122 | va_end(args); 123 | } 124 | 125 | inline void _DMESSAGE(const char* fmt, ...) 126 | { 127 | va_list args; 128 | 129 | va_start(args, fmt); 130 | gLog.Log(IDebugLog::kLevel_DebugMessage, fmt, args); 131 | va_end(args); 132 | } 133 | -------------------------------------------------------------------------------- /common/IDynamicCreate.cpp: -------------------------------------------------------------------------------- 1 | #include "IDynamicCreate.h" 2 | 3 | #if ENABLE_IDYNAMICCREATE 4 | 5 | IClassRegistry _gClassRegistry; 6 | 7 | IClassRegistry::IClassRegistry() 8 | { 9 | // 10 | } 11 | 12 | IClassRegistry::~IClassRegistry() 13 | { 14 | // 15 | } 16 | 17 | void IClassRegistry::RegisterClassInfo(UInt32 id, IDynamicType* typeInfo) 18 | { 19 | theClassRegistry[id] = typeInfo; 20 | } 21 | 22 | IDynamicType* IClassRegistry::LookupClassInfo(UInt32 id) 23 | { 24 | ClassRegistryType::iterator iter = theClassRegistry.find(id); 25 | 26 | return (iter == theClassRegistry.end()) ? NULL : (*iter).second; 27 | } 28 | 29 | IDynamicType* IClassRegistry::LookupClassInfo(char* name) 30 | { 31 | for (ClassRegistryType::iterator iter = theClassRegistry.begin(); iter != theClassRegistry.end(); iter++) 32 | if (!strcmp((*iter).second->GetName(), name)) 33 | return (*iter).second; 34 | 35 | return NULL; 36 | } 37 | 38 | #endif -------------------------------------------------------------------------------- /common/IDynamicCreate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/IDataStream.h" 5 | #include "common/IErrors.h" 6 | 7 | // this screws with edit-and-continue and we don't use it 8 | #define ENABLE_IDYNAMICCREATE 0 9 | 10 | #if ENABLE_IDYNAMICCREATE 11 | 12 | //! Get a pointer to the IDynamicType for a class. 13 | //! @note This is not a function; the parameter must be constant. 14 | #define GetDynType(name) (&(##name##::__DYN_DynamicType)) 15 | 16 | //! Declare the members used for dynamic class creation 17 | #define DYNAMIC_DECLARE(name) \ 18 | public: \ 19 | class __DYN_##name##_DynamicType : public IDynamicType \ 20 | { \ 21 | public: \ 22 | __DYN_##name##_DynamicType() { } \ 23 | ~__DYN_##name##_DynamicType() { } \ 24 | \ 25 | virtual IDynamic * Create(void) { return new name; } \ 26 | virtual char * GetName(void) { return #name; } \ 27 | virtual IDynamic * Instantiate(IDataStream * stream); \ 28 | }; \ 29 | \ 30 | static __DYN_##name##_DynamicType __DYN_DynamicType; \ 31 | virtual IDynamicType * __DYN_GetDynamicType(void) { return &__DYN_DynamicType; } \ 32 | \ 33 | friend __DYN_##name##_DynamicType; 34 | 35 | //! Define the members used for dynamic class creation 36 | #define DYNAMIC_DEFINE(name) name##::__DYN_##name##_DynamicType name##::__DYN_DynamicType; 37 | 38 | //! Define a dynamic instantiation handler 39 | #define DYNAMIC_INSTANTIATE_HANDLER(name) IDynamic * name##::__DYN_##name##_DynamicType::Instantiate(IDataStream * stream) { name * object = new name; 40 | #define END_DYNAMIC_INSTANTIATE_HANDLER return object; } 41 | 42 | //! Specifies that a dynamic class should not be instantiated automatically 43 | #define NO_DYNAMIC_INSTANTIATE_HANDLER(name) DYNAMIC_INSTANTIATE_HANDLER(name) { HALT("attempted to instantiate " #name); } END_DYNAMIC_INSTANTIATE_HANDLER 44 | 45 | //! Casts 46 | #define CAST(ptr, type) _DynamicCast (ptr); 47 | 48 | class IDynamicType; 49 | 50 | /** 51 | * Pure virtual base class allowing dynamic creation of objects 52 | * 53 | * To allow dynamic creation of a class, publicly inherit IDynamic, add the 54 | * macro DYNAMIC_DECLARE(classname) first in the class declaration, and add 55 | * the macro DYNAMIC_DEFINE(classname) somewhere in the class definition file. 56 | */ 57 | class IDynamic 58 | { 59 | public: 60 | IDynamic() { } 61 | virtual ~IDynamic() { } 62 | 63 | virtual IDynamicType* __DYN_GetDynamicType(void) = 0; 64 | }; 65 | 66 | /** 67 | * Pure virtual base class allowing class instantiation and information retrieval 68 | */ 69 | class IDynamicType 70 | { 71 | public: 72 | IDynamicType() { } 73 | virtual ~IDynamicType() { } 74 | 75 | virtual IDynamic* Create(void) = 0; 76 | virtual char* GetName(void) = 0; 77 | 78 | virtual IDynamic* Instantiate(IDataStream* stream) = 0; 79 | }; 80 | 81 | //! 82 | template 83 | T* _DynamicCast(IDynamic* ptr) 84 | { 85 | if (ptr && (&T::__DYN_DynamicType == ptr->__DYN_GetDynamicType())) 86 | return static_cast(ptr); 87 | 88 | return NULL; 89 | } 90 | 91 | /** 92 | * Registry of dynamic classes 93 | */ 94 | class IClassRegistry 95 | { 96 | public: 97 | IClassRegistry(); 98 | ~IClassRegistry(); 99 | 100 | static void RegisterClassInfo(UInt32 id, IDynamicType* typeInfo); 101 | 102 | static IDynamicType* LookupClassInfo(UInt32 id); 103 | static IDynamicType* LookupClassInfo(char* name); 104 | 105 | static IDynamic* Create(UInt32 id) { IDynamicType* info = LookupClassInfo(id); return info ? info->Create() : NULL; } 106 | static IDynamic* Create(char* name) { IDynamicType* info = LookupClassInfo(name); return info ? info->Create() : NULL; } 107 | 108 | static IDynamic* Instantiate(UInt32 id, IDataStream* stream) { IDynamicType* info = LookupClassInfo(id); return info ? info->Instantiate(stream) : NULL; } 109 | static IDynamic* Instantiate(char* name, IDataStream* stream) { IDynamicType* info = LookupClassInfo(name); return info ? info->Instantiate(stream) : NULL; } 110 | 111 | static char* GetName(UInt32 id) { IDynamicType* info = LookupClassInfo(id); return info ? info->GetName() : NULL; } 112 | 113 | private: 114 | typedef std::map ClassRegistryType; 115 | static ClassRegistryType theClassRegistry; 116 | }; 117 | 118 | #endif 119 | -------------------------------------------------------------------------------- /common/IErrors.cpp: -------------------------------------------------------------------------------- 1 | #include "common/IErrors.h" 2 | #include "common/IDebugLog.h" 3 | #include 4 | 5 | __declspec(noreturn) static void IErrors_Halt(void) 6 | { 7 | // crash 8 | *((int*)0) = 0xDEADBEEF; 9 | } 10 | 11 | /** 12 | * Report a failed assertion and exit the program 13 | * 14 | * @param file the file where the error occured 15 | * @param line the line number where the error occured 16 | * @param desc an error message 17 | */ 18 | void _AssertionFailed(const char* file, unsigned long line, const char* desc) 19 | { 20 | _FATALERROR("Assertion failed in %s (%d): %s", file, line, desc); 21 | 22 | IErrors_Halt(); 23 | } 24 | 25 | /** 26 | * Report a failed assertion and exit the program 27 | * 28 | * @param file the file where the error occured 29 | * @param line the line number where the error occured 30 | * @param desc an error message 31 | * @param code the error code 32 | */ 33 | void _AssertionFailed_ErrCode(const char* file, unsigned long line, const char* desc, unsigned long long code) 34 | { 35 | if (code & 0xFFFFFFFF00000000) 36 | _FATALERROR("Assertion failed in %s (%d): %s (code = %16I64X (%I64d))", file, line, desc, code, code); 37 | else 38 | { 39 | UInt32 code32 = code; 40 | _FATALERROR("Assertion failed in %s (%d): %s (code = %08X (%d))", file, line, desc, code32, code32); 41 | } 42 | 43 | IErrors_Halt(); 44 | } 45 | 46 | /** 47 | * Report a failed assertion and exit the program 48 | * 49 | * @param file the file where the error occured 50 | * @param line the line number where the error occured 51 | * @param desc an error message 52 | * @param code the error code 53 | */ 54 | void _AssertionFailed_ErrCode(const char* file, unsigned long line, const char* desc, const char* code) 55 | { 56 | _FATALERROR("Assertion failed in %s (%d): %s (code = %s)", file, line, desc, code); 57 | 58 | IErrors_Halt(); 59 | } -------------------------------------------------------------------------------- /common/IErrors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void _AssertionFailed(const char* file, unsigned long line, const char* desc); 4 | void _AssertionFailed_ErrCode(const char* file, unsigned long line, const char* desc, unsigned long long code); 5 | void _AssertionFailed_ErrCode(const char* file, unsigned long line, const char* desc, const char* code); 6 | 7 | //! Exit the program if the condition is not true 8 | #define ASSERT(a) do { if(!(a)) _AssertionFailed(__FILE__, __LINE__, #a); } while(0) 9 | //! Exit the program if the condition is not true, with an error message 10 | #define ASSERT_STR(a, b) do { if(!(a)) _AssertionFailed(__FILE__, __LINE__, b); } while(0) 11 | //! Exit the program if the condition is not true, reporting an error code 12 | #define ASSERT_CODE(a, b) do { if(!(a)) _AssertionFailed_ErrCode(__FILE__, __LINE__, #a, b); } while(0) 13 | //! Exit the program if the condition is not true, reporting an error code and message 14 | #define ASSERT_STR_CODE(a, b, c) do { if(!(a)) _AssertionFailed_ErrCode(__FILE__, __LINE__, b, c); } while(0) 15 | //! Exit the program with an error message 16 | #define HALT(a) do { _AssertionFailed(__FILE__, __LINE__, a); } while(0) 17 | //! Exit the program with and error code and message 18 | #define HALT_CODE(a, b) do { _AssertionFailed_ErrCode(__FILE__, __LINE__, a, b); } while(0) 19 | 20 | // based on the boost implementation of static asserts 21 | template struct StaticAssertFailure; 22 | template <> struct StaticAssertFailure { enum { a = 1 }; }; 23 | template struct static_assert_test { }; 24 | 25 | #define __MACRO_JOIN__(a, b) __MACRO_JOIN_2__(a, b) 26 | #define __MACRO_JOIN_2__(a, b) __MACRO_JOIN_3__(a, b) 27 | #define __MACRO_JOIN_3__(a, b) a##b 28 | #define __PREPRO_TOKEN_STR2__(a) #a 29 | #define __PREPRO_TOKEN_STR__(a) __PREPRO_TOKEN_STR2__(a) 30 | #define __LOC__ __FILE__ "("__PREPRO_TOKEN_STR__(__LINE__)") : " 31 | 32 | //#define STATIC_ASSERT(a) typedef static_assert_test )> __MACRO_JOIN__(static_assert_typedef_, __COUNTER__) 33 | #define STATIC_ASSERT(a) static_assert(a) 34 | -------------------------------------------------------------------------------- /common/IPrefix.cpp: -------------------------------------------------------------------------------- 1 | #include "IPrefix.h" 2 | -------------------------------------------------------------------------------- /common/IPrefix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 4018 - signed/unsigned mismatch 4 | // 4244 - loss of data by assignment 5 | // 4267 - possible loss of data (truncation) 6 | // 4305 - truncation by assignment 7 | // 4288 - disable warning for crap microsoft extension screwing up the scope of variables defined in for loops 8 | // 4311 - pointer truncation 9 | // 4312 - pointer extension 10 | #pragma warning(disable: 4018 4244 4267 4305 4288 4312 4311) 11 | 12 | #include 13 | #include 14 | #include 15 | #include "common/ITypes.h" 16 | #include "common/IErrors.h" 17 | #include "common/IDynamicCreate.h" 18 | #include "common/IDebugLog.h" 19 | #include "common/ISingleton.h" 20 | #include 21 | #include 22 | -------------------------------------------------------------------------------- /common/ISingleton.cpp: -------------------------------------------------------------------------------- 1 | #include "common/ISingleton.h" 2 | 3 | //template T * Singleton ::ms_Singleton = 0; -------------------------------------------------------------------------------- /common/ISingleton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/IErrors.h" 4 | 5 | #pragma warning(push) 6 | #pragma warning(disable: 4311 4312) 7 | 8 | /** 9 | * A singleton base class 10 | * 11 | * Singletons are useful when you have a class that will be instantiated once, 12 | * like a global manager. 13 | */ 14 | template 15 | class ISingleton 16 | { 17 | static T * ms_Singleton; 18 | 19 | public: 20 | ISingleton() 21 | { 22 | ASSERT(!ms_Singleton); 23 | int offset = (int)(T *)1 - (int)(ISingleton *)(T *)1; 24 | ms_Singleton = (T *)((int)this + offset); 25 | } 26 | 27 | virtual ~ISingleton() 28 | { 29 | ASSERT(ms_Singleton); 30 | ms_Singleton = 0; 31 | } 32 | 33 | /** 34 | * Returns the single instance of the derived class 35 | */ 36 | static T& GetSingleton(void) 37 | { 38 | ASSERT(ms_Singleton); 39 | return *ms_Singleton; 40 | } 41 | 42 | /** 43 | * Returns a pointer to the single instance of the derived class 44 | */ 45 | static T * GetSingletonPtr(void) 46 | { 47 | return ms_Singleton; 48 | } 49 | }; 50 | 51 | template T * ISingleton ::ms_Singleton = 0; 52 | 53 | #pragma warning(pop) 54 | -------------------------------------------------------------------------------- /common/ITypes.cpp: -------------------------------------------------------------------------------- 1 | #include "ITypes.h" 2 | 3 | Bitstring::Bitstring() 4 | :data(NULL) 5 | { 6 | } 7 | 8 | Bitstring::Bitstring(UInt32 inLength) 9 | :data(NULL) 10 | { 11 | Alloc(inLength); 12 | } 13 | 14 | Bitstring::~Bitstring() 15 | { 16 | Dispose(); 17 | } 18 | 19 | void Bitstring::Alloc(UInt32 inLength) 20 | { 21 | Dispose(); 22 | 23 | inLength = (inLength + 7) & ~7; 24 | length = inLength >> 3; 25 | 26 | data = new UInt8[length]; 27 | } 28 | 29 | void Bitstring::Dispose(void) 30 | { 31 | delete[] data; 32 | } 33 | 34 | void Bitstring::Clear(void) 35 | { 36 | std::memset(data, 0, length); 37 | } 38 | 39 | void Bitstring::Clear(UInt32 idx) 40 | { 41 | ASSERT_STR(idx < (length << 3), "Bitstring::Clear: out of range"); 42 | 43 | data[idx >> 3] &= ~(1 << (idx & 7)); 44 | } 45 | 46 | void Bitstring::Set(UInt32 idx) 47 | { 48 | ASSERT_STR(idx < (length << 3), "Bitstring::Set: out of range"); 49 | 50 | data[idx >> 3] |= (1 << (idx & 7)); 51 | } 52 | 53 | bool Bitstring::IsSet(UInt32 idx) 54 | { 55 | ASSERT_STR(idx < (length << 3), "Bitstring::IsSet: out of range"); 56 | 57 | return (data[idx >> 3] & (1 << (idx & 7))) ? true : false; 58 | } 59 | 60 | bool Bitstring::IsClear(UInt32 idx) 61 | { 62 | ASSERT_STR(idx < (length << 3), "Bitstring::IsClear: out of range"); 63 | 64 | return (data[idx >> 3] & (1 << (idx & 7))) ? false : true; 65 | } -------------------------------------------------------------------------------- /common/ITypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/IErrors.h" 4 | 5 | #pragma warning(disable: 4221) 6 | #include 7 | 8 | typedef unsigned char UInt8; //!< An unsigned 8-bit integer value 9 | typedef unsigned short UInt16; //!< An unsigned 16-bit integer value 10 | typedef unsigned long UInt32; //!< An unsigned 32-bit integer value 11 | typedef unsigned long long UInt64; //!< An unsigned 64-bit integer value 12 | typedef signed char SInt8; //!< A signed 8-bit integer value 13 | typedef signed short SInt16; //!< A signed 16-bit integer value 14 | typedef signed long SInt32; //!< A signed 32-bit integer value 15 | typedef signed long long SInt64; //!< A signed 64-bit integer value 16 | typedef float Float32; //!< A 32-bit floating point value 17 | typedef double Float64; //!< A 64-bit floating point value 18 | 19 | inline UInt32 Extend16(UInt32 in) 20 | { 21 | return (in & 0x8000) ? (0xFFFF0000 | in) : in; 22 | } 23 | 24 | inline UInt32 Extend8(UInt32 in) 25 | { 26 | return (in & 0x80) ? (0xFFFFFF00 | in) : in; 27 | } 28 | 29 | inline UInt16 Swap16(UInt16 in) 30 | { 31 | return ((in >> 8) & 0x00FF) | 32 | ((in << 8) & 0xFF00); 33 | } 34 | 35 | inline UInt32 Swap32(UInt32 in) 36 | { 37 | return ((in >> 24) & 0x000000FF) | 38 | ((in >> 8) & 0x0000FF00) | 39 | ((in << 8) & 0x00FF0000) | 40 | ((in << 24) & 0xFF000000); 41 | } 42 | 43 | inline UInt64 Swap64(UInt64 in) 44 | { 45 | UInt64 temp; 46 | 47 | temp = Swap32(in); 48 | temp <<= 32; 49 | temp |= Swap32(in >> 32); 50 | 51 | return temp; 52 | } 53 | 54 | inline void SwapFloat(float* in) 55 | { 56 | UInt32* temp = (UInt32*)in; 57 | 58 | *temp = Swap32(*temp); 59 | } 60 | 61 | inline void SwapDouble(double* in) 62 | { 63 | UInt64* temp = (UInt64*)in; 64 | 65 | *temp = Swap64(*temp); 66 | } 67 | 68 | inline bool IsBigEndian(void) 69 | { 70 | union 71 | { 72 | UInt16 u16; 73 | UInt8 u8[2]; 74 | } temp; 75 | 76 | temp.u16 = 0x1234; 77 | 78 | return temp.u8[0] == 0x12; 79 | } 80 | 81 | inline bool IsLittleEndian(void) 82 | { 83 | return !IsBigEndian(); 84 | } 85 | 86 | #define CHAR_CODE(a, b, c, d) (((a & 0xFF) << 0) | ((b & 0xFF) << 8) | ((c & 0xFF) << 16) | ((d & 0xFF) << 24)) 87 | #define MACRO_SWAP16(a) ((((a) & 0x00FF) << 8) | (((a) & 0xFF00) >> 8)) 88 | #define MACRO_SWAP32(a) ((((a) & 0x000000FF) << 24) | (((a) & 0x0000FF00) << 8) | (((a) & 0x00FF0000) >> 8) | (((a) & 0xFF000000) >> 24)) 89 | 90 | #define VERSION_CODE(primary, secondary, sub) (((primary & 0xFFF) << 20) | ((secondary & 0xFFF) << 8) | ((sub & 0xFF) << 0)) 91 | #define VERSION_CODE_PRIMARY(in) ((in >> 20) & 0xFFF) 92 | #define VERSION_CODE_SECONDARY(in) ((in >> 8) & 0xFFF) 93 | #define VERSION_CODE_SUB(in) ((in >> 0) & 0xFF) 94 | 95 | #define MAKE_COLOR(a, r, g, b) (((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0)) 96 | #define COLOR_ALPHA(in) ((in >> 24) & 0xFF) 97 | #define COLOR_RED(in) ((in >> 16) & 0xFF) 98 | #define COLOR_GREEN(in) ((in >> 8) & 0xFF) 99 | #define COLOR_BLUE(in) ((in >> 0) & 0xFF) 100 | 101 | /** 102 | * A 64-bit variable combiner 103 | * 104 | * Useful for endian-independent value extraction. 105 | */ 106 | union VarCombiner 107 | { 108 | UInt64 u64; 109 | SInt64 s64; 110 | double f64; 111 | struct { UInt32 b; UInt32 a; } u32; 112 | struct { SInt32 b; SInt32 a; } s32; 113 | struct { float b; float a; } f32; 114 | struct { UInt16 d; UInt16 c; UInt16 b; UInt16 a; } u16; 115 | struct { SInt16 d; SInt16 c; SInt16 b; SInt16 a; } s16; 116 | struct { 117 | UInt8 h; UInt8 g; UInt8 f; UInt8 e; 118 | UInt8 d; UInt8 c; UInt8 b; UInt8 a; 119 | } u8; 120 | struct { 121 | SInt8 h; SInt8 g; SInt8 f; SInt8 e; 122 | SInt8 d; SInt8 c; SInt8 b; SInt8 a; 123 | } s8; 124 | }; 125 | 126 | /** 127 | * A bitfield. 128 | */ 129 | template 130 | class Bitfield 131 | { 132 | public: 133 | Bitfield() { } 134 | ~Bitfield() { } 135 | 136 | void Clear(void) { field = 0; } //!< Clears all bits 137 | void RawSet(UInt32 data) { field = data; } //!< Modifies all bits 138 | 139 | void Set(UInt32 data) { field |= data; } //!< Sets individual bits 140 | void Clear(UInt32 data) { field &= ~data; } //!< Clears individual bits 141 | void Unset(UInt32 data) { Clear(data); } //!< Clears individual bits 142 | void Mask(UInt32 data) { field &= data; } //!< Masks individual bits 143 | void Toggle(UInt32 data) { field ^= data; } //!< Toggles individual bits 144 | void SetBit(UInt32 data, bool state) 145 | { 146 | if (state) Set(data); else Clear(data); 147 | } 148 | 149 | void SetField(T data, T mask, T pos) { 150 | field = (field & ~mask) | (data << pos); 151 | } 152 | 153 | T Get(void) const { return field; } //!< Gets all bits 154 | T GetBit(UInt32 data) const { return field & data; } //!< Gets individual bits 155 | T Extract(UInt32 bit) const { return (field >> bit) & 1; } //!< Extracts a bit 156 | T ExtractField(UInt32 shift, UInt32 length) //!< Extracts a series of bits 157 | { 158 | return (field >> shift) & (0xFFFFFFFF >> (32 - length)); 159 | } 160 | 161 | bool IsSet(UInt32 data) const { return ((field & data) == data) ? true : false; } //!< Are all these bits set? 162 | bool IsUnSet(UInt32 data) const { return (field & data) ? false : true; } //!< Are all these bits clear? 163 | bool IsClear(UInt32 data) const { return IsUnSet(data); } //!< Are all these bits clear? 164 | 165 | T field; //!< bitfield data 166 | }; 167 | 168 | typedef Bitfield Bitfield8; //!< An 8-bit bitfield 169 | typedef Bitfield Bitfield16; //!< A 16-bit bitfield 170 | typedef Bitfield Bitfield32; //!< A 32-bit bitfield 171 | 172 | STATIC_ASSERT(sizeof(Bitfield8) == 1); 173 | STATIC_ASSERT(sizeof(Bitfield16) == 2); 174 | STATIC_ASSERT(sizeof(Bitfield32) == 4); 175 | 176 | /** 177 | * A bitstring 178 | * 179 | * Essentially a long bitvector. 180 | */ 181 | class Bitstring 182 | { 183 | public: 184 | Bitstring(); 185 | Bitstring(UInt32 inLength); 186 | ~Bitstring(); 187 | 188 | void Alloc(UInt32 inLength); 189 | void Dispose(void); 190 | 191 | void Clear(void); 192 | void Clear(UInt32 idx); 193 | void Set(UInt32 idx); 194 | 195 | bool IsSet(UInt32 idx); 196 | bool IsClear(UInt32 idx); 197 | 198 | private: 199 | UInt8* data; 200 | UInt32 length; //!< length in bytes 201 | }; 202 | 203 | /** 204 | * Time storage 205 | */ 206 | class Time 207 | { 208 | public: 209 | Time() { Clear(); } 210 | ~Time() { } 211 | 212 | //! Deinitialize the class 213 | void Clear(void) { seconds = minutes = hours = 0; hasData = false; } 214 | //! Sets the class to the current time 215 | //! @todo implement this 216 | void SetToNow(void) { Set(1, 2, 3); } 217 | 218 | //! Sets the class to the specified time 219 | void Set(UInt8 inS, UInt8 inM, UInt8 inH) 220 | { 221 | seconds = inS; minutes = inM; hours = inH; hasData = true; 222 | } 223 | 224 | //! Gets whether the class has been initialized or not 225 | bool IsSet(void) { return hasData; } 226 | 227 | UInt8 GetSeconds(void) { return seconds; } //!< return the seconds portion of the time 228 | UInt8 GetMinutes(void) { return minutes; } //!< return the minutes portion of the time 229 | UInt8 GetHours(void) { return hours; } //!< return the hours portion of the time 230 | 231 | private: 232 | UInt8 seconds, minutes, hours; 233 | bool hasData; 234 | }; 235 | 236 | const float kFloatEpsilon = 0.0001f; 237 | 238 | inline bool FloatEqual(float a, float b) { float magnitude = a - b; if (magnitude < 0) magnitude = -magnitude; return magnitude < kFloatEpsilon; } 239 | 240 | class Vector2 241 | { 242 | public: 243 | Vector2() { } 244 | Vector2(const Vector2& in) { x = in.x; y = in.y; } 245 | Vector2(float inX, float inY) { x = inX; y = inY; } 246 | ~Vector2() { } 247 | 248 | void Set(float inX, float inY) { x = inX; y = inY; } 249 | void SetX(float inX) { x = inX; } 250 | void SetY(float inY) { y = inY; } 251 | void Get(float* outX, float* outY) { *outX = x; *outY = y; } 252 | float GetX(void) { return x; } 253 | float GetY(void) { return y; } 254 | 255 | void Normalize(void) { float mag = Magnitude(); x /= mag; y /= mag; } 256 | float Magnitude(void) { return sqrt(x * x + y * y); } 257 | 258 | void Reverse(void) { float temp = -x; x = -y; y = temp; } 259 | 260 | void Scale(float scale) { x *= scale; y *= scale; } 261 | 262 | void SwapBytes(void) { SwapFloat(&x); SwapFloat(&y); } 263 | 264 | Vector2& operator+=(const Vector2& rhs) { x += rhs.x; y += rhs.y; return *this; } 265 | Vector2& operator-=(const Vector2& rhs) { x -= rhs.x; y -= rhs.y; return *this; } 266 | Vector2& operator*=(float rhs) { x *= rhs; y *= rhs; return *this; } 267 | Vector2& operator/=(float rhs) { x /= rhs; y /= rhs; return *this; } 268 | 269 | float x; 270 | float y; 271 | }; 272 | 273 | inline Vector2 operator+(const Vector2& lhs, const Vector2& rhs) 274 | { 275 | return Vector2(lhs.x + rhs.x, lhs.y + rhs.y); 276 | }; 277 | 278 | inline Vector2 operator-(const Vector2& lhs, const Vector2& rhs) 279 | { 280 | return Vector2(lhs.x - rhs.x, lhs.y - rhs.y); 281 | }; 282 | 283 | inline Vector2 operator*(const Vector2& lhs, float rhs) 284 | { 285 | return Vector2(lhs.x * rhs, lhs.y * rhs); 286 | }; 287 | 288 | inline Vector2 operator/(const Vector2& lhs, float rhs) 289 | { 290 | return Vector2(lhs.x / rhs, lhs.y / rhs); 291 | }; 292 | 293 | inline bool MaskCompare(void* lhs, void* rhs, void* mask, UInt32 size) 294 | { 295 | UInt8* lhs8 = (UInt8*)lhs; 296 | UInt8* rhs8 = (UInt8*)rhs; 297 | UInt8* mask8 = (UInt8*)mask; 298 | 299 | for (UInt32 i = 0; i < size; i++) 300 | if ((lhs8[i] & mask8[i]) != (rhs8[i] & mask8[i])) 301 | return false; 302 | 303 | return true; 304 | } 305 | 306 | class Vector3 307 | { 308 | public: 309 | Vector3() { } 310 | Vector3(const Vector3& in) { x = in.x; y = in.y; z = in.z; } 311 | Vector3(float inX, float inY, float inZ) { x = inX; y = inY; z = inZ; } 312 | ~Vector3() { } 313 | 314 | void Set(float inX, float inY, float inZ) { x = inX; y = inY; z = inZ; } 315 | void Get(float* outX, float* outY, float* outZ) { *outX = x; *outY = y; *outZ = z; } 316 | 317 | void Normalize(void) { float mag = Magnitude(); x /= mag; y /= mag; z /= mag; } 318 | float Magnitude(void) { return sqrt(x * x + y * y + z * z); } 319 | 320 | void Scale(float scale) { x *= scale; y *= scale; z *= scale; } 321 | 322 | void SwapBytes(void) { SwapFloat(&x); SwapFloat(&y); SwapFloat(&z); } 323 | 324 | Vector3& operator+=(const Vector3& rhs) { x += rhs.x; y += rhs.y; z += rhs.z; return *this; } 325 | Vector3& operator-=(const Vector3& rhs) { x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this; } 326 | Vector3& operator*=(const Vector3& rhs) { x *= rhs.x; y *= rhs.y; z *= rhs.z; return *this; } 327 | Vector3& operator/=(const Vector3& rhs) { x /= rhs.x; y /= rhs.y; z /= rhs.z; return *this; } 328 | 329 | union 330 | { 331 | struct 332 | { 333 | float x, y, z; 334 | }; 335 | float d[3]; 336 | }; 337 | }; 338 | 339 | inline Vector3 operator+(const Vector3& lhs, const Vector3& rhs) 340 | { 341 | return Vector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); 342 | } 343 | 344 | inline Vector3 operator-(const Vector3& lhs, const Vector3& rhs) 345 | { 346 | return Vector3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); 347 | } 348 | 349 | inline Vector3 operator*(const Vector3& lhs, const Vector3& rhs) 350 | { 351 | return Vector3(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z); 352 | } 353 | 354 | inline Vector3 operator/(const Vector3& lhs, const Vector3& rhs) 355 | { 356 | return Vector3(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z); 357 | } 358 | -------------------------------------------------------------------------------- /common/PluginAPI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct CommandInfo; 4 | struct ParamInfo; 5 | class TESObjectREFR; 6 | class Script; 7 | struct ScriptEventList; 8 | struct PluginInfo; 9 | 10 | typedef UInt32 PluginHandle; // treat this as an opaque type 11 | 12 | typedef UInt32 CommandReturnType; 13 | 14 | struct FOSEInterface 15 | { 16 | UInt32 foseVersion; 17 | UInt32 runtimeVersion; 18 | UInt32 editorVersion; 19 | UInt32 isEditor; 20 | bool (*RegisterCommand)(CommandInfo* info); // returns true for success, false for failure 21 | void (*SetOpcodeBase)(UInt32 opcode); 22 | void* (*QueryInterface)(UInt32 id); 23 | 24 | // call during your Query or Load functions to get a PluginHandle uniquely identifying your plugin 25 | // invalid if called at any other time, so call it once and save the result 26 | PluginHandle(*GetPluginHandle)(void); 27 | 28 | // CommandReturnType enum defined in CommandTable.h 29 | // does the same as RegisterCommand but includes return type; *required* for commands returning arrays 30 | bool (*RegisterTypedCommand)(CommandInfo* info, CommandReturnType retnType); 31 | // returns a full path the the game directory 32 | const char* (*GetRuntimeDirectory)(); 33 | 34 | // Allows checking for nogore edition 35 | UInt32 isNogore; 36 | }; 37 | 38 | struct PluginInfo 39 | { 40 | enum 41 | { 42 | kInfoVersion = 1 43 | }; 44 | 45 | UInt32 infoVersion; 46 | const char* name; 47 | UInt32 version; 48 | }; -------------------------------------------------------------------------------- /common/SafeWrite.cpp: -------------------------------------------------------------------------------- 1 | #include "SafeWrite.h" 2 | 3 | void SafeWrite8(UInt32 addr, UInt32 data) 4 | { 5 | UInt32 oldProtect; 6 | 7 | VirtualProtect((void *)addr, 4, PAGE_EXECUTE_READWRITE, &oldProtect); 8 | *((UInt8 *)addr) = data; 9 | VirtualProtect((void *)addr, 4, oldProtect, &oldProtect); 10 | } 11 | 12 | void SafeWrite16(UInt32 addr, UInt32 data) 13 | { 14 | UInt32 oldProtect; 15 | 16 | VirtualProtect((void *)addr, 4, PAGE_EXECUTE_READWRITE, &oldProtect); 17 | *((UInt16 *)addr) = data; 18 | VirtualProtect((void *)addr, 4, oldProtect, &oldProtect); 19 | } 20 | 21 | void SafeWrite32(UInt32 addr, UInt32 data) 22 | { 23 | UInt32 oldProtect; 24 | 25 | VirtualProtect((void *)addr, 4, PAGE_EXECUTE_READWRITE, &oldProtect); 26 | *((UInt32 *)addr) = data; 27 | VirtualProtect((void *)addr, 4, oldProtect, &oldProtect); 28 | } 29 | 30 | void SafeWriteBuf(UInt32 addr, void *data, UInt32 len) 31 | { 32 | UInt32 oldProtect; 33 | 34 | VirtualProtect((void *)addr, len, PAGE_EXECUTE_READWRITE, &oldProtect); 35 | memcpy((void *)addr, data, len); 36 | VirtualProtect((void *)addr, len, oldProtect, &oldProtect); 37 | } 38 | 39 | void WriteRelJump(UInt32 jumpSrc, UInt32 jumpTgt) 40 | { 41 | // jmp rel32 42 | SafeWrite8(jumpSrc, 0xE9); 43 | SafeWrite32(jumpSrc + 1, jumpTgt - jumpSrc - 1 - 4); 44 | } 45 | 46 | void WriteRelCall(UInt32 jumpSrc, UInt32 jumpTgt) 47 | { 48 | // call rel32 49 | SafeWrite8(jumpSrc, 0xE8); 50 | SafeWrite32(jumpSrc + 1, jumpTgt - jumpSrc - 1 - 4); 51 | } 52 | 53 | void ReplaceCall(UInt32 jumpSrc, UInt32 jumpTgt) 54 | { 55 | SafeWrite32(jumpSrc + 1, jumpTgt - jumpSrc - 1 - 4); 56 | } 57 | 58 | void ReplaceVirtualFunc(UInt32 jumpSrc, void* jumpTgt) { 59 | SafeWrite32(jumpSrc, (UInt32)jumpTgt); 60 | } 61 | 62 | void WriteRelJnz(UInt32 jumpSrc, UInt32 jumpTgt) 63 | { 64 | // jnz rel32 65 | SafeWrite16(jumpSrc, 0x850F); 66 | SafeWrite32(jumpSrc + 2, jumpTgt - jumpSrc - 2 - 4); 67 | } 68 | 69 | void WriteRelJle(UInt32 jumpSrc, UInt32 jumpTgt) 70 | { 71 | // jle rel32 72 | SafeWrite16(jumpSrc, 0x8E0F); 73 | SafeWrite32(jumpSrc + 2, jumpTgt - jumpSrc - 2 - 4); 74 | } 75 | 76 | void PatchMemoryNop(ULONG_PTR Address, SIZE_T Size) 77 | { 78 | DWORD d = 0; 79 | VirtualProtect((LPVOID)Address, Size, PAGE_EXECUTE_READWRITE, &d); 80 | 81 | for (SIZE_T i = 0; i < Size; i++) 82 | *(volatile BYTE*)(Address + i) = 0x90; //0x90 == opcode for NOP 83 | 84 | VirtualProtect((LPVOID)Address, Size, d, &d); 85 | 86 | FlushInstructionCache(GetCurrentProcess(), (LPVOID)Address, Size); 87 | } 88 | 89 | void PatchMemoryNopRange(ULONG_PTR StartAddress, ULONG_PTR EndAddress) { 90 | PatchMemoryNop(StartAddress, EndAddress - StartAddress); 91 | } 92 | 93 | void WriteRelLibCall(UInt32 jumpSrc, UInt32 jumpTgt) 94 | { 95 | // call rel32 96 | SafeWrite8(jumpSrc, 0xE8); 97 | SafeWrite32(jumpSrc + 1, jumpTgt - jumpSrc - 1 - 4); 98 | SafeWrite8(jumpSrc + 5, 0x90); 99 | } -------------------------------------------------------------------------------- /common/SafeWrite.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common/IPrefix.h" 3 | 4 | DECLSPEC_NOINLINE void SafeWrite8(UInt32 addr, UInt32 data); 5 | DECLSPEC_NOINLINE void SafeWrite16(UInt32 addr, UInt32 data); 6 | DECLSPEC_NOINLINE void SafeWrite32(UInt32 addr, UInt32 data); 7 | DECLSPEC_NOINLINE void SafeWriteBuf(UInt32 addr, void * data, UInt32 len); 8 | 9 | // 5 bytes 10 | DECLSPEC_NOINLINE void WriteRelJump(UInt32 jumpSrc, UInt32 jumpTgt); 11 | DECLSPEC_NOINLINE void WriteRelCall(UInt32 jumpSrc, UInt32 jumpTgt); 12 | 13 | 14 | // 6 bytes 15 | DECLSPEC_NOINLINE void WriteRelJnz(UInt32 jumpSrc, UInt32 jumpTgt); 16 | DECLSPEC_NOINLINE void WriteRelJle(UInt32 jumpSrc, UInt32 jumpTgt); 17 | 18 | DECLSPEC_NOINLINE void PatchMemoryNop(ULONG_PTR Address, SIZE_T Size); 19 | void PatchMemoryNopRange(ULONG_PTR StartAddress, ULONG_PTR EndAddress); 20 | 21 | template 22 | DECLSPEC_NOINLINE void WriteRelCall(UInt32 jumpSrc, T jumpTgt) 23 | { 24 | WriteRelCall(jumpSrc, (UInt32)jumpTgt); 25 | } 26 | 27 | template 28 | DECLSPEC_NOINLINE void WriteRelJump(UInt32 jumpSrc, T jumpTgt) 29 | { 30 | WriteRelJump(jumpSrc, (UInt32)jumpTgt); 31 | } 32 | 33 | DECLSPEC_NOINLINE void ReplaceCall(UInt32 jumpSrc, UInt32 jumpTgt); 34 | 35 | template 36 | DECLSPEC_NOINLINE void ReplaceCall(UInt32 jumpSrc, T jumpTgt) 37 | { 38 | ReplaceCall(jumpSrc, (UInt32)jumpTgt); 39 | } 40 | 41 | void ReplaceVirtualFunc(UInt32 jumpSrc, void* jumpTgt); -------------------------------------------------------------------------------- /common/Utilities.cpp: -------------------------------------------------------------------------------- 1 | #include "Utilities.h" 2 | #include "SafeWrite.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "containers.h" 8 | #include "GameData.h" 9 | #include "Hooks_Gameplay.h" 10 | #include "LambdaManager.h" 11 | #include "PluginAPI.h" 12 | #include "PluginManager.h" 13 | 14 | #if RUNTIME 15 | #include "GameAPI.h" 16 | #include "GameForms.h" 17 | #endif 18 | 19 | void DumpClass(void * theClassPtr, UInt32 nIntsToDump) 20 | { 21 | _MESSAGE("DumpClass:"); 22 | UInt32* basePtr = (UInt32*)theClassPtr; 23 | 24 | gLog.Indent(); 25 | 26 | if (!theClassPtr) return; 27 | for (UInt32 ix = 0; ix < nIntsToDump; ix++ ) { 28 | UInt32* curPtr = basePtr+ix; 29 | const char* curPtrName = NULL; 30 | UInt32 otherPtr = 0; 31 | float otherFloat = 0.0; 32 | const char* otherPtrName = NULL; 33 | if (curPtr) { 34 | curPtrName = GetObjectClassName((void*)curPtr); 35 | 36 | __try 37 | { 38 | otherPtr = *curPtr; 39 | otherFloat = *(float*)(curPtr); 40 | } 41 | __except(EXCEPTION_EXECUTE_HANDLER) 42 | { 43 | // 44 | } 45 | 46 | if (otherPtr) { 47 | otherPtrName = GetObjectClassName((void*)otherPtr); 48 | } 49 | } 50 | 51 | _MESSAGE("%3d +%03X ptr: 0x%08X: %32s *ptr: 0x%08x | %f: %32s", ix, ix*4, curPtr, curPtrName, otherPtr, otherFloat, otherPtrName); 52 | } 53 | 54 | gLog.Outdent(); 55 | } 56 | 57 | #pragma warning (push) 58 | #pragma warning (disable : 4200) 59 | struct RTTIType 60 | { 61 | void * typeInfo; 62 | UInt32 pad; 63 | char name[0]; 64 | }; 65 | 66 | struct RTTILocator 67 | { 68 | UInt32 sig, offset, cdOffset; 69 | RTTIType * type; 70 | }; 71 | #pragma warning (pop) 72 | 73 | // use the RTTI information to return an object's class name 74 | const char * GetObjectClassName(void * objBase) 75 | { 76 | const char * result = ""; 77 | 78 | __try 79 | { 80 | void ** obj = (void **)objBase; 81 | RTTILocator ** vtbl = (RTTILocator **)obj[0]; 82 | RTTILocator * rtti = vtbl[-1]; 83 | RTTIType * type = rtti->type; 84 | 85 | // starts with ,? 86 | if((type->name[0] == '.') && (type->name[1] == '?')) 87 | { 88 | // is at most 100 chars long 89 | for(UInt32 i = 0; i < 100; i++) 90 | { 91 | if(type->name[i] == 0) 92 | { 93 | // remove the .?AV 94 | result = type->name + 4; 95 | break; 96 | } 97 | } 98 | } 99 | } 100 | __except(EXCEPTION_EXECUTE_HANDLER) 101 | { 102 | // return the default 103 | } 104 | 105 | return result; 106 | } 107 | 108 | const std::string & GetFalloutDirectory(void) 109 | { 110 | static std::string s_falloutDirectory; 111 | 112 | if(s_falloutDirectory.empty()) 113 | { 114 | // can't determine how many bytes we'll need, hope it's not more than MAX_PATH 115 | char falloutPathBuf[MAX_PATH]; 116 | UInt32 falloutPathLength = GetModuleFileName(GetModuleHandle(NULL), falloutPathBuf, sizeof(falloutPathBuf)); 117 | 118 | if(falloutPathLength && (falloutPathLength < sizeof(falloutPathBuf))) 119 | { 120 | std::string falloutPath(falloutPathBuf, falloutPathLength); 121 | 122 | // truncate at last slash 123 | std::string::size_type lastSlash = falloutPath.rfind('\\'); 124 | if(lastSlash != std::string::npos) // if we don't find a slash something is VERY WRONG 125 | { 126 | s_falloutDirectory = falloutPath.substr(0, lastSlash + 1); 127 | 128 | _DMESSAGE("fallout root = %s", s_falloutDirectory.c_str()); 129 | } 130 | else 131 | { 132 | _WARNING("no slash in fallout path? (%s)", falloutPath.c_str()); 133 | } 134 | } 135 | else 136 | { 137 | _WARNING("couldn't find fallout path (len = %d, err = %08X)", falloutPathLength, GetLastError()); 138 | } 139 | } 140 | 141 | return s_falloutDirectory; 142 | } 143 | 144 | static const std::string & GetNVSEConfigPath(void) 145 | { 146 | static std::string s_configPath; 147 | 148 | if(s_configPath.empty()) 149 | { 150 | std::string falloutPath = GetFalloutDirectory(); 151 | if(!falloutPath.empty()) 152 | { 153 | s_configPath = falloutPath + "Data\\NVSE\\nvse_config.ini"; 154 | 155 | _MESSAGE("config path = %s", s_configPath.c_str()); 156 | } 157 | } 158 | 159 | return s_configPath; 160 | } 161 | 162 | std::string GetNVSEConfigOption(const char * section, const char * key) 163 | { 164 | std::string result; 165 | 166 | const std::string & configPath = GetNVSEConfigPath(); 167 | if(!configPath.empty()) 168 | { 169 | char resultBuf[256]; 170 | resultBuf[0] = 0; 171 | 172 | UInt32 resultLen = GetPrivateProfileString(section, key, NULL, resultBuf, 255, configPath.c_str()); 173 | 174 | result = resultBuf; 175 | } 176 | 177 | return result; 178 | } 179 | 180 | bool GetNVSEConfigOption_UInt32(const char * section, const char * key, UInt32 * dataOut) 181 | { 182 | std::string data = GetNVSEConfigOption(section, key); 183 | if(data.empty()) 184 | return false; 185 | 186 | return (sscanf(data.c_str(), "%lu", dataOut) == 1); 187 | } 188 | 189 | namespace MersenneTwister 190 | { 191 | 192 | /* 193 | A C-program for MT19937, with initialization improved 2002/1/26. 194 | Coded by Takuji Nishimura and Makoto Matsumoto. 195 | 196 | Before using, initialize the state by using init_genrand(seed) 197 | or init_by_array(init_key, key_length). 198 | 199 | Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, 200 | All rights reserved. 201 | 202 | Redistribution and use in source and binary forms, with or without 203 | modification, are permitted provided that the following conditions 204 | are met: 205 | 206 | 1. Redistributions of source code must retain the above copyright 207 | notice, this list of conditions and the following disclaimer. 208 | 209 | 2. Redistributions in binary form must reproduce the above copyright 210 | notice, this list of conditions and the following disclaimer in the 211 | documentation and/or other materials provided with the distribution. 212 | 213 | 3. The names of its contributors may not be used to endorse or promote 214 | products derived from this software without specific prior written 215 | permission. 216 | 217 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 218 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 219 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 220 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 221 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 222 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 223 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 224 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 225 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 226 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 227 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 228 | 229 | 230 | Any feedback is very welcome. 231 | http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html 232 | email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) 233 | */ 234 | 235 | /* Period parameters */ 236 | #define N 624 237 | #define M 397 238 | #define MATRIX_A 0x9908b0dfUL /* constant vector a */ 239 | #define UPPER_MASK 0x80000000UL /* most significant w-r bits */ 240 | #define LOWER_MASK 0x7fffffffUL /* least significant r bits */ 241 | 242 | static unsigned long mt[N]; /* the array for the state vector */ 243 | static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */ 244 | 245 | /* initializes mt[N] with a seed */ 246 | void init_genrand(unsigned long s) 247 | { 248 | mt[0]= s & 0xffffffffUL; 249 | for (mti=1; mti> 30)) + mti); 252 | /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ 253 | /* In the previous versions, MSBs of the seed affect */ 254 | /* only MSBs of the array mt[]. */ 255 | /* 2002/01/09 modified by Makoto Matsumoto */ 256 | mt[mti] &= 0xffffffffUL; 257 | /* for >32 bit machines */ 258 | } 259 | } 260 | 261 | /* initialize by an array with array-length */ 262 | /* init_key is the array for initializing keys */ 263 | /* key_length is its length */ 264 | /* slight change for C++, 2004/2/26 */ 265 | void init_by_array(unsigned long init_key[], int key_length) 266 | { 267 | int i, j, k; 268 | init_genrand(19650218UL); 269 | i=1; j=0; 270 | k = (N>key_length ? N : key_length); 271 | for (; k; k--) { 272 | mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL)) 273 | + init_key[j] + j; /* non linear */ 274 | mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ 275 | i++; j++; 276 | if (i>=N) { mt[0] = mt[N-1]; i=1; } 277 | if (j>=key_length) j=0; 278 | } 279 | for (k=N-1; k; k--) { 280 | mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL)) 281 | - i; /* non linear */ 282 | mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ 283 | i++; 284 | if (i>=N) { mt[0] = mt[N-1]; i=1; } 285 | } 286 | 287 | mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ 288 | } 289 | 290 | /* generates a random number on [0,0xffffffff]-interval */ 291 | unsigned long genrand_int32(void) 292 | { 293 | unsigned long y; 294 | static unsigned long mag01[2]={0x0UL, MATRIX_A}; 295 | /* mag01[x] = x * MATRIX_A for x=0,1 */ 296 | 297 | if (mti >= N) { /* generate N words at one time */ 298 | int kk; 299 | 300 | if (mti == N+1) /* if init_genrand() has not been called, */ 301 | init_genrand(5489UL); /* a default initial seed is used */ 302 | 303 | for (kk=0;kk> 1) ^ mag01[y & 0x1UL]; 306 | } 307 | for (;kk> 1) ^ mag01[y & 0x1UL]; 310 | } 311 | y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); 312 | mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; 313 | 314 | mti = 0; 315 | } 316 | 317 | y = mt[mti++]; 318 | 319 | /* Tempering */ 320 | y ^= (y >> 11); 321 | y ^= (y << 7) & 0x9d2c5680UL; 322 | y ^= (y << 15) & 0xefc60000UL; 323 | y ^= (y >> 18); 324 | 325 | return y; 326 | } 327 | 328 | /* generates a random number on [0,0x7fffffff]-interval */ 329 | long genrand_int31(void) 330 | { 331 | return (long)(genrand_int32()>>1); 332 | } 333 | 334 | /* generates a random number on [0,1]-real-interval */ 335 | double genrand_real1(void) 336 | { 337 | return genrand_int32()*(1.0/4294967295.0); 338 | /* divided by 2^32-1 */ 339 | } 340 | 341 | /* generates a random number on [0,1)-real-interval */ 342 | double genrand_real2(void) 343 | { 344 | return genrand_int32()*(1.0/4294967296.0); 345 | /* divided by 2^32 */ 346 | } 347 | 348 | /* generates a random number on (0,1)-real-interval */ 349 | double genrand_real3(void) 350 | { 351 | return (((double)genrand_int32()) + 0.5)*(1.0/4294967296.0); 352 | /* divided by 2^32 */ 353 | } 354 | 355 | /* generates a random number on [0,1) with 53-bit resolution*/ 356 | double genrand_res53(void) 357 | { 358 | unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6; 359 | return(a*67108864.0+b)*(1.0/9007199254740992.0); 360 | } 361 | /* These real versions are due to Isaku Wada, 2002/01/09 added */ 362 | 363 | #undef N 364 | #undef M 365 | #undef MATRIX_A 366 | #undef UPPER_MASK 367 | #undef LOWER_MASK 368 | 369 | }; 370 | 371 | Tokenizer::Tokenizer(const char* src, const char* delims) 372 | : m_offset(0), m_delims(delims), m_data(src) 373 | { 374 | // 375 | } 376 | 377 | UInt32 Tokenizer::NextToken(std::string& outStr) 378 | { 379 | if (m_offset == m_data.length()) 380 | return -1; 381 | 382 | size_t const start = m_data.find_first_not_of(m_delims, m_offset); 383 | if (start != -1) 384 | { 385 | size_t end = m_data.find_first_of(m_delims, start); 386 | if (end == -1) 387 | end = m_data.length(); 388 | 389 | m_offset = end; 390 | outStr = m_data.substr(start, end - start); 391 | return start; 392 | } 393 | 394 | return -1; 395 | } 396 | 397 | std::string Tokenizer::ToNewLine() 398 | { 399 | if (m_offset == m_data.length()) 400 | return ""; 401 | 402 | size_t const start = m_data.find_first_not_of(m_delims, m_offset); 403 | if (start != -1) 404 | { 405 | size_t end = m_data.find_first_of('\n', start); 406 | if (end == -1) 407 | end = m_data.length(); 408 | 409 | m_offset = end; 410 | return m_data.substr(start, end - start); 411 | } 412 | 413 | return ""; 414 | } 415 | 416 | UInt32 Tokenizer::PrevToken(std::string& outStr) 417 | { 418 | if (m_offset == 0) 419 | return -1; 420 | 421 | size_t searchStart = m_data.find_last_of(m_delims, m_offset - 1); 422 | if (searchStart == -1) 423 | return -1; 424 | 425 | size_t end = m_data.find_last_not_of(m_delims, searchStart); 426 | if (end == -1) 427 | return -1; 428 | 429 | size_t start = m_data.find_last_of(m_delims, end); // okay if start == -1 here 430 | 431 | m_offset = end + 1; 432 | outStr = m_data.substr(start + 1, end - start); 433 | return start + 1; 434 | } 435 | 436 | ScriptTokenizer::ScriptTokenizer(std::string_view scriptText) : m_scriptText(scriptText) 437 | { 438 | // 439 | } 440 | 441 | bool ScriptTokenizer::TryLoadNextLine() 442 | { 443 | m_loadedLineTokens = {}; 444 | m_tokenOffset = 0; 445 | if (m_scriptOffset == m_scriptText.length()) 446 | return false; 447 | 448 | #if _DEBUG 449 | ASSERT(m_scriptOffset <= m_scriptText.length()); 450 | #endif 451 | 452 | auto GetLineEndPos = [this](size_t startPos) -> size_t 453 | { 454 | auto result = m_scriptText.find_first_of("\n\r", startPos); 455 | if (result == std::string_view::npos) 456 | result = m_scriptText.length(); 457 | return result; 458 | }; 459 | 460 | // Finds the pos of the the start of a valid token on a new line, if any. 461 | auto GetNextLineStartPos = [this](size_t startPos) -> size_t 462 | { 463 | auto result = m_scriptText.find_first_of("\n\r", startPos); 464 | if (result == std::string_view::npos) 465 | result = m_scriptText.length(); 466 | else 467 | { 468 | // Skip over consecutive empty lines. 469 | result = m_scriptText.find_first_not_of(" \t\n\r", result + 1); 470 | if (result == std::string_view::npos) 471 | result = m_scriptText.length(); 472 | } 473 | return result; 474 | }; 475 | 476 | // Ignore spaces and newlines at the start - find the start of a REAL line. 477 | // Line pos is relative to the entire script text. 478 | if (auto linePos = m_scriptText.find_first_not_of(" \t\n\r", m_scriptOffset); 479 | linePos != std::string_view::npos) 480 | { 481 | if (m_inMultilineComment) 482 | { 483 | auto const multilineCommentEndStartPos = m_scriptText.find("*/", linePos); 484 | if (multilineCommentEndStartPos == std::string_view::npos) 485 | { 486 | m_scriptOffset = m_scriptText.size(); 487 | return false; // unable to find an end to the multiline comment (xEdit shenanigans?) 488 | } 489 | m_inMultilineComment = false; 490 | if (multilineCommentEndStartPos + 2 == m_scriptText.length()) 491 | { 492 | m_scriptOffset = m_scriptText.size(); 493 | return false; 494 | } 495 | 496 | // If there was a ';' comment inside of the multiline comment on the line where it ended, 497 | // .. it will still comment out the rest of the line. 498 | if (std::string_view const insideOfMultilineCommentOnThisLine(&m_scriptText.at(linePos), multilineCommentEndStartPos - linePos); 499 | insideOfMultilineCommentOnThisLine.find_first_of(';') != std::string_view::npos) 500 | { 501 | // Line is commented out; ignore it and try loading the next one. 502 | m_scriptOffset = GetNextLineStartPos(multilineCommentEndStartPos + 2); 503 | return TryLoadNextLine(); 504 | } 505 | //else, there might be something left in this line. 506 | linePos = multilineCommentEndStartPos + 2; 507 | } 508 | 509 | // If line is empty, skip to a new line. 510 | linePos = m_scriptText.find_first_not_of(" \t\n\r", linePos); 511 | if (linePos == std::string_view::npos) 512 | { 513 | m_scriptOffset = m_scriptText.size(); 514 | return false; // rest of file is empty 515 | } 516 | 517 | // Handle comments and try loading tokens that are on the same line. 518 | for (auto const lineEndPos = GetLineEndPos(linePos); true; ) 519 | { 520 | // Check if the rest of the line is commented out via ';'. 521 | if (m_scriptText.at(linePos) == ';') 522 | { 523 | m_scriptOffset = GetNextLineStartPos(linePos + 1); 524 | if (m_loadedLineTokens.empty()) 525 | { 526 | // Line is fully commented out; ignore it and try loading the next one. 527 | return TryLoadNextLine(); 528 | } 529 | //else, there were some tokens in the line before the comment. 530 | return true; 531 | } 532 | 533 | // Handle possible back-to-back multiline comments on the same line. 534 | // Ex: "/* *//* */ /* */ Finally,_I_Am_A_Real_Token. /* \n */\n" 535 | for (std::string_view lineView(&m_scriptText.at(linePos), lineEndPos - linePos); 536 | lineView.size() >= 2 && lineView.front() == '/' && lineView.at(1) == '*'; 537 | lineView = { &m_scriptText.at(linePos), lineEndPos - linePos }) 538 | { 539 | auto HandleCommentSpanningMultipleLines = [this, lineEndPos]() -> bool 540 | { 541 | // Line ended; assume multiline comment that spans multiple lines. 542 | m_inMultilineComment = true; 543 | m_scriptOffset = m_scriptText.find_first_not_of(" \t\n\r", lineEndPos); 544 | if (m_scriptOffset == std::string_view::npos) 545 | m_scriptOffset = m_scriptText.size(); 546 | if (!m_loadedLineTokens.empty()) 547 | return true; 548 | return TryLoadNextLine(); 549 | }; 550 | 551 | // ignore the "/*" chars 552 | if (linePos + 2 == lineEndPos) 553 | return HandleCommentSpanningMultipleLines(); 554 | 555 | // Check if the multiline comment ends on this line. 556 | if (auto endMultilineCommentStartPos = lineView.find("*/"); 557 | endMultilineCommentStartPos == std::string_view::npos) 558 | { 559 | return HandleCommentSpanningMultipleLines(); 560 | } 561 | else 562 | { 563 | // else, multiline comment ends in this line. 564 | // There might be tokens left in this line; if not, skip to the next if this line was empty. 565 | 566 | // Make pos relative to the entire script text. 567 | endMultilineCommentStartPos += linePos; 568 | if (endMultilineCommentStartPos + 2 == m_scriptText.size()) 569 | { 570 | m_scriptOffset = m_scriptText.size(); 571 | return false; 572 | } 573 | 574 | // If there was a ';' comment inside of the multiline comment on the line where it ended, 575 | // .. it will still comment out the rest of the line. 576 | if (std::string_view const insideOfMultilineComment(&m_scriptText.at(linePos + 2), endMultilineCommentStartPos - linePos); 577 | insideOfMultilineComment.find_first_of(';') != std::string_view::npos) 578 | { 579 | // Line is commented out; IF we didn't get tokens, ignore it and try loading the next one. 580 | m_scriptOffset = GetNextLineStartPos(endMultilineCommentStartPos + 2); 581 | if (!m_loadedLineTokens.empty()) 582 | return true; 583 | return TryLoadNextLine(); 584 | } 585 | 586 | // Handle multiline comment that ends in-line, but is followed by end-of-line. 587 | m_scriptOffset = m_scriptText.find_first_not_of(" \t", endMultilineCommentStartPos + 2); 588 | if (m_scriptOffset == std::string_view::npos) 589 | { 590 | m_scriptOffset = m_scriptText.size(); 591 | return !m_loadedLineTokens.empty(); 592 | } 593 | if (m_scriptOffset == lineEndPos) 594 | { 595 | if (!m_loadedLineTokens.empty()) 596 | return true; 597 | // Line is commented out; ignore it and try loading the next one. 598 | return TryLoadNextLine(); 599 | } 600 | 601 | // Else, line goes on. 602 | linePos = m_scriptText.find_first_not_of(" \t\n\r", endMultilineCommentStartPos + 2); 603 | if (linePos == std::string_view::npos) 604 | { 605 | m_scriptOffset = m_scriptText.size(); 606 | return !m_loadedLineTokens.empty(); 607 | } 608 | } 609 | } 610 | 611 | // Handle ';' comment right after 1-line /* */ comment(s) 612 | if (m_scriptText.at(linePos) == ';') 613 | { 614 | m_scriptOffset = GetNextLineStartPos(linePos + 1); 615 | if (m_loadedLineTokens.empty()) 616 | { 617 | // Line is fully commented out; ignore it and try loading the next one. 618 | return TryLoadNextLine(); 619 | } 620 | //else, there were some tokens in the line before the comment. 621 | return true; 622 | } 623 | 624 | // Line pos should now point to the start of a token. 625 | // Get the post-the-end character position for the token. 626 | size_t endOfTokenPos; 627 | if (m_scriptText.at(linePos) == '"') 628 | { 629 | // Get the full string as a single token. 630 | endOfTokenPos = m_scriptText.find_first_of('"', linePos + 1); 631 | if (endOfTokenPos == std::string_view::npos) 632 | endOfTokenPos = m_scriptText.size(); 633 | else 634 | ++endOfTokenPos; // include '"' char at the end. 635 | } 636 | else 637 | { 638 | endOfTokenPos = m_scriptText.find_first_of(" \t\n\r", linePos); 639 | if (endOfTokenPos == std::string_view::npos) 640 | endOfTokenPos = m_scriptText.size(); 641 | } 642 | 643 | auto tokenView = m_scriptText.substr(linePos, endOfTokenPos - linePos); 644 | 645 | // trim comments off of the end of the token 646 | if (tokenView.back() == ';') 647 | { 648 | --endOfTokenPos; 649 | } 650 | else if (tokenView.size() > 2 && 651 | tokenView.at(tokenView.size() - 2) == '/' && tokenView.back() == '*') 652 | { 653 | endOfTokenPos -= 2; 654 | } 655 | tokenView = m_scriptText.substr(linePos, endOfTokenPos - linePos); 656 | 657 | m_loadedLineTokens.push_back(tokenView); 658 | if (endOfTokenPos == m_scriptText.size()) 659 | break; 660 | 661 | linePos = m_scriptText.find_first_not_of(" \t", endOfTokenPos); 662 | if (linePos == std::string_view::npos) 663 | { 664 | linePos = m_scriptText.size(); 665 | break; 666 | } 667 | if (linePos == lineEndPos) 668 | break; 669 | } 670 | 671 | m_scriptOffset = GetNextLineStartPos(linePos); 672 | return !m_loadedLineTokens.empty(); 673 | } 674 | // else, rest of script is just spaces 675 | m_scriptOffset = m_scriptText.size(); 676 | return false; 677 | } 678 | 679 | std::string_view ScriptTokenizer::GetNextLineToken() 680 | { 681 | if (m_loadedLineTokens.empty() || m_tokenOffset >= m_loadedLineTokens.size()) 682 | return ""; 683 | return m_loadedLineTokens.at(m_tokenOffset++); 684 | } 685 | 686 | std::string_view ScriptTokenizer::GetLineText() 687 | { 688 | if (!m_loadedLineTokens.empty()) 689 | { 690 | if (m_loadedLineTokens.size() > 1) 691 | { 692 | const char* startAddr = m_loadedLineTokens[0].data(); 693 | std::string_view lastToken = m_loadedLineTokens[m_loadedLineTokens.size() - 1]; 694 | // assume lastToken isn't empty 695 | const char* endAddr = &lastToken.at(lastToken.size() - 1); 696 | 697 | size_t count = endAddr - startAddr + 1; 698 | size_t startPos = startAddr - m_scriptText.data(); 699 | return m_scriptText.substr(startPos, count); 700 | } 701 | else // only 1 token 702 | { 703 | return m_loadedLineTokens[0]; 704 | } 705 | } 706 | return ""; 707 | } 708 | 709 | #if RUNTIME 710 | 711 | const char GetSeparatorChar(Script * script) 712 | { 713 | if(IsConsoleMode()) 714 | { 715 | if(script && script->GetModIndex() != 0xFF) 716 | return '|'; 717 | else 718 | return '@'; 719 | } 720 | else 721 | return '|'; 722 | } 723 | 724 | const char * GetSeparatorChars(Script * script) 725 | { 726 | if(IsConsoleMode()) 727 | { 728 | if(script && script->GetModIndex() != 0xFF) 729 | return "|"; 730 | else 731 | return "@"; 732 | } 733 | else 734 | return "|"; 735 | } 736 | 737 | void Console_Print_Long(const std::string& str) 738 | { 739 | UInt32 numLines = str.length() / 500; 740 | for (UInt32 i = 0; i < numLines; i++) 741 | Console_Print("%s ...", str.substr(i*500, 500).c_str()); 742 | 743 | Console_Print("%s", str.substr(numLines*500, str.length() - numLines*500).c_str()); 744 | } 745 | 746 | void Console_Print_Str(const std::string& str) 747 | { 748 | if (str.size() < 512) 749 | Console_Print("%s", str.c_str()); 750 | else 751 | Console_Print_Long(str); 752 | } 753 | 754 | #endif 755 | 756 | struct ControlName 757 | { 758 | UInt32 unk0; 759 | const char * name; 760 | UInt32 unkC; 761 | }; 762 | 763 | ControlName ** g_keyNames = (ControlName **)0x011D52F0; 764 | ControlName ** g_mouseButtonNames = (ControlName **)0x011D5240; 765 | ControlName ** g_joystickNames = (ControlName **)0x011D51B0; 766 | 767 | const char * GetDXDescription(UInt32 keycode) 768 | { 769 | const char * keyName = ""; 770 | 771 | if(keycode <= 220) 772 | { 773 | if(g_keyNames[keycode]) 774 | keyName = g_keyNames[keycode]->name; 775 | } 776 | else if(255 <= keycode && keycode <= 263) 777 | { 778 | if(keycode == 255) 779 | keycode = 256; 780 | if(g_mouseButtonNames[keycode - 256]) 781 | keyName = g_mouseButtonNames[keycode - 256]->name; 782 | } 783 | else if (keycode == 264) 784 | keyName = "WheelUp"; 785 | else if (keycode == 265) 786 | keyName = "WheelDown"; 787 | 788 | return keyName; 789 | } 790 | 791 | bool ci_equal(char ch1, char ch2) 792 | { 793 | return game_tolower((unsigned char)ch1) == game_tolower((unsigned char)ch2); 794 | } 795 | 796 | bool ci_less(const char* lh, const char* rh) 797 | { 798 | ASSERT(lh && rh); 799 | while (*lh && *rh) { 800 | char l = game_toupper(*lh); 801 | char r = game_toupper(*rh); 802 | if (l < r) { 803 | return true; 804 | } 805 | else if (l > r) { 806 | return false; 807 | } 808 | lh++; 809 | rh++; 810 | } 811 | 812 | return game_toupper(*lh) < game_toupper(*rh); 813 | } 814 | 815 | void MakeUpper(std::string& str) 816 | { 817 | std::transform(str.begin(), str.end(), str.begin(), game_toupper); 818 | } 819 | 820 | void MakeLower(std::string& str) 821 | { 822 | std::transform(str.begin(), str.end(), str.begin(), game_tolower); 823 | } 824 | 825 | #if RUNTIME 826 | 827 | char* CopyCString(const char* src) 828 | { 829 | UInt32 size = src ? strlen(src) : 0; 830 | char* result = (char*)FormHeap_Allocate(size+1); 831 | result[size] = 0; 832 | if (size) { 833 | strcpy_s(result, size+1, src); 834 | } 835 | 836 | return result; 837 | } 838 | 839 | #endif 840 | 841 | #pragma warning(push) 842 | #pragma warning(disable: 4996) // warning about std::transform() 843 | 844 | void MakeUpper(char* str) 845 | { 846 | if (str) { 847 | UInt32 len = strlen(str); 848 | std::transform(str, str + len, str, game_toupper); 849 | } 850 | } 851 | 852 | void MakeLower(char* str) 853 | { 854 | if (str) { 855 | UInt32 len = strlen(str); 856 | std::transform(str, str + len, str, game_tolower); 857 | } 858 | } 859 | 860 | #pragma warning(pop) 861 | 862 | // ErrOutput 863 | ErrOutput::ErrOutput(_ShowError errorFunc, _ShowWarning warningFunc) 864 | { 865 | ShowWarning = warningFunc; 866 | ShowError = errorFunc; 867 | } 868 | 869 | void ErrOutput::vShow(ErrOutput::Message& msg, va_list args) 870 | { 871 | char msgText[0x400]; 872 | vsprintf_s(msgText, sizeof(msgText), msg.fmt, args); 873 | if (msg.bCanDisable) 874 | { 875 | if (!msg.bDisabled) 876 | if (ShowWarning(msgText)) 877 | msg.bDisabled = true; 878 | } 879 | else 880 | ShowError(msgText); 881 | } 882 | 883 | void ErrOutput::Show(ErrOutput::Message msg, ...) 884 | { 885 | va_list args; 886 | va_start(args, msg); 887 | 888 | vShow(msg, args); 889 | } 890 | 891 | void ErrOutput::Show(const char* msg, ...) 892 | { 893 | va_list args; 894 | va_start(args, msg); 895 | 896 | vShow(msg, args); 897 | } 898 | 899 | void ErrOutput::vShow(const char* msg, va_list args) 900 | { 901 | Message tempMsg; 902 | tempMsg.fmt = msg; 903 | tempMsg.bCanDisable = false; 904 | tempMsg.bDisabled = false; 905 | 906 | vShow(tempMsg, args); 907 | } 908 | 909 | void ShowErrorMessageBox(const char* message) 910 | { 911 | int msgboxID = MessageBox( 912 | NULL, 913 | message, 914 | "Error", 915 | MB_ICONWARNING | MB_OK 916 | ); 917 | } 918 | 919 | #if RUNTIME 920 | 921 | const char* GetModName(TESForm* form) 922 | { 923 | if (!form) 924 | return "Unknown or deleted script"; 925 | const char* modName = IS_ID(form, Script) ? "In-game console" : "Dynamic form"; 926 | if (form->mods.Head() && form->mods.Head()->data) 927 | return form->mods.Head()->Data()->name; 928 | if (form->GetModIndex() != 0xFF) 929 | { 930 | modName = DataHandler::Get()->GetNthModName(form->GetModIndex()); 931 | if (!modName || !modName[0]) 932 | modName = "Unknown"; 933 | } 934 | return modName; 935 | } 936 | #if NVSE_CORE 937 | UnorderedSet g_warnedScripts; 938 | 939 | void ShowRuntimeError(Script* script, const char* fmt, ...) 940 | { 941 | va_list args; 942 | va_start(args, fmt); 943 | 944 | char errorMsg[0x800]; 945 | vsprintf_s(errorMsg, sizeof(errorMsg), fmt, args); 946 | 947 | char errorHeader[0x900]; 948 | const auto* modName = GetModName(script); 949 | 950 | const auto* scriptName = script ? script->GetName() : nullptr; // JohnnyGuitarNVSE allows this 951 | auto refId = script ? script->refID : 0; 952 | const auto modIdx = script ? script->GetModIndex() : 0; 953 | if (script && LambdaManager::IsScriptLambda(script)) 954 | { 955 | Script* parentScript; 956 | if (auto* parentEventList = LambdaManager::GetParentEventList(script); 957 | parentEventList && ((parentScript = parentEventList->m_script))) 958 | { 959 | refId = parentScript->refID; 960 | scriptName = parentScript->GetName(); 961 | } 962 | } 963 | if (scriptName && strlen(scriptName) != 0) 964 | { 965 | sprintf_s(errorHeader, sizeof(errorHeader), "Error in script %08X (%s) in mod %s\n%s", refId, scriptName, modName, errorMsg); 966 | } 967 | else 968 | { 969 | sprintf_s(errorHeader, sizeof(errorHeader), "Error in script %08X in mod %s\n%s", refId, modName, errorMsg); 970 | } 971 | 972 | if (g_warnScriptErrors && g_myMods.contains(modIdx) && g_warnedScripts.Insert(refId)) 973 | { 974 | char message[512]; 975 | snprintf(message, sizeof(message), "%s: Script error (see console print)", GetModName(script)); 976 | if (!IsConsoleMode()) 977 | QueueUIMessage(message, 0, reinterpret_cast(0x1049638), nullptr, 2.5F, false); 978 | } 979 | 980 | if (strlen(errorHeader) < 512) 981 | Console_Print("%s", errorHeader); 982 | else 983 | Console_Print_Long(errorHeader); 984 | _MESSAGE("%s", errorHeader); 985 | 986 | PluginManager::Dispatch_Message(0, NVSEMessagingInterface::kMessage_RuntimeScriptError, errorMsg, 4, NULL); 987 | 988 | va_end(args); 989 | } 990 | #endif 991 | #endif 992 | 993 | std::string FormatString(const char* fmt, ...) 994 | { 995 | va_list args; 996 | va_start(args, fmt); 997 | 998 | char msg[0x800]; 999 | vsprintf_s(msg, 0x800, fmt, args); 1000 | return msg; 1001 | } 1002 | 1003 | std::vector GetCallStack(int i) 1004 | { 1005 | std::vector vecTrace(i, nullptr); 1006 | CaptureStackBackTrace(1, i, reinterpret_cast(vecTrace.data()), nullptr); 1007 | return vecTrace; 1008 | } 1009 | 1010 | bool FindStringCI(const std::string& strHaystack, const std::string& strNeedle) 1011 | { 1012 | auto it = std::search( 1013 | strHaystack.begin(), strHaystack.end(), 1014 | strNeedle.begin(), strNeedle.end(), 1015 | [](char ch1, char ch2) { return game_toupper(ch1) == game_toupper(ch2); } 1016 | ); 1017 | return (it != strHaystack.end()); 1018 | } 1019 | 1020 | void ReplaceAll(std::string &str, const std::string& from, const std::string& to) 1021 | { 1022 | size_t startPos = 0; 1023 | while((startPos = str.find(from, startPos)) != std::string::npos) 1024 | { 1025 | str.replace(startPos, from.length(), to); 1026 | startPos += to.length(); // Handles case where 'to' is a substring of 'from' 1027 | } 1028 | 1029 | } 1030 | 1031 | void GeckExtenderMessageLog(const char* fmt, ...) 1032 | { 1033 | va_list args; 1034 | va_start(args, fmt); 1035 | 1036 | auto* window = FindWindow("RTEDITLOG", nullptr); 1037 | if (!window) 1038 | { 1039 | _MESSAGE("Failed to find GECK Extender Message Log window"); 1040 | return; 1041 | } 1042 | 1043 | char buffer[0x400]; 1044 | vsprintf_s(buffer, 0x400, fmt, args); 1045 | strcat_s(buffer, "\n"); 1046 | 1047 | SendMessage(window, 0x8004, 0, reinterpret_cast(buffer)); 1048 | } 1049 | 1050 | bool Cmd_Default_Execute(COMMAND_ARGS) 1051 | { 1052 | return true; 1053 | } 1054 | 1055 | bool Cmd_Default_Eval(COMMAND_ARGS_EVAL) 1056 | { 1057 | return true; 1058 | } 1059 | 1060 | void ToWChar(wchar_t* ws, const char* c) 1061 | { 1062 | swprintf(ws, 100, L"%hs", c); 1063 | } 1064 | 1065 | bool IsProcessRunning(const char* executableName) 1066 | { 1067 | PROCESSENTRY32 entry; 1068 | entry.dwSize = sizeof(PROCESSENTRY32); 1069 | 1070 | const auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 1071 | 1072 | if (!Process32First(snapshot, &entry)) { 1073 | CloseHandle(snapshot); 1074 | return false; 1075 | } 1076 | 1077 | do { 1078 | if (!_stricmp(entry.szExeFile, executableName)) { 1079 | CloseHandle(snapshot); 1080 | return true; 1081 | } 1082 | } while (Process32Next(snapshot, &entry)); 1083 | 1084 | CloseHandle(snapshot); 1085 | return false; 1086 | } 1087 | #if NVSE_CORE && RUNTIME 1088 | void DisplayMessage(const char* msg) 1089 | { 1090 | ShowMessageBox(msg, 0, 0, ShowMessageBox_Callback, 0, 0x17, 0.0, 0.0, "Ok", 0); 1091 | } 1092 | #endif 1093 | 1094 | std::string GetCurPath() 1095 | { 1096 | char buffer[MAX_PATH] = { 0 }; 1097 | GetModuleFileName(NULL, buffer, MAX_PATH); 1098 | std::string::size_type pos = std::string(buffer).find_last_of("\\/"); 1099 | return std::string(buffer).substr(0, pos); 1100 | } 1101 | 1102 | bool ValidString(const char* str) 1103 | { 1104 | return str && strlen(str); 1105 | } 1106 | 1107 | #if _DEBUG 1108 | // debugger can't call unused member functions 1109 | const char* GetFormName(TESForm* form) 1110 | { 1111 | return form ? form->GetName() : ""; 1112 | } 1113 | 1114 | const char* GetFormName(UInt32 formId) 1115 | { 1116 | return GetFormName(LookupFormByID(formId)); 1117 | } 1118 | 1119 | #endif 1120 | 1121 | std::string& ToLower(std::string&& data) 1122 | { 1123 | ra::transform(data, data.begin(), [](const unsigned char c) { return game_tolower(c); }); 1124 | return data; 1125 | } 1126 | 1127 | std::string& StripSpace(std::string&& data) 1128 | { 1129 | std::erase_if(data, isspace); 1130 | return data; 1131 | } 1132 | 1133 | bool StartsWith(std::string left, std::string right) 1134 | { 1135 | return ToLower(std::move(left)).starts_with(ToLower(std::move(right))); 1136 | } 1137 | 1138 | std::vector SplitString(std::string s, std::string delimiter) 1139 | { 1140 | size_t pos_start = 0, pos_end, delim_len = delimiter.length(); 1141 | std::string token; 1142 | std::vector res; 1143 | 1144 | while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { 1145 | token = s.substr(pos_start, pos_end - pos_start); 1146 | pos_start = pos_end + delim_len; 1147 | res.push_back(token); 1148 | } 1149 | 1150 | res.push_back(s.substr(pos_start)); 1151 | return res; 1152 | } 1153 | 1154 | UInt8* GetParentBasePtr(void* addressOfReturnAddress, bool lambda) 1155 | { 1156 | auto* basePtr = static_cast(addressOfReturnAddress) - 4; 1157 | #if _DEBUG 1158 | if (lambda) // in debug mode, lambdas are wrapped inside a closure wrapper function, so one more step needed 1159 | basePtr = *reinterpret_cast(basePtr); 1160 | #endif 1161 | return *reinterpret_cast(basePtr); 1162 | } -------------------------------------------------------------------------------- /common/Utilities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class Script; 9 | 10 | void DumpClass(void * theClassPtr, UInt32 nIntsToDump = 512); 11 | const char * GetObjectClassName(void * obj); 12 | const std::string & GetFalloutDirectory(void); 13 | std::string GetNVSEConfigOption(const char * section, const char * key); 14 | bool GetNVSEConfigOption_UInt32(const char * section, const char * key, UInt32 * dataOut); 15 | 16 | // this has been tested to work for non-varargs functions 17 | // varargs functions end up with 'this' passed as the last parameter (ie. probably broken) 18 | // do NOT use with classes that have multiple inheritance 19 | 20 | // if many member functions are to be declared, use MEMBER_FN_PREFIX to create a type with a known name 21 | // so it doesn't need to be restated throughout the member list 22 | 23 | // all of the weirdness with the _GetType function is because you can't declare a static const pointer 24 | // inside the class definition. inlining automatically makes the function call go away since it's a const 25 | 26 | #define MEMBER_FN_PREFIX(className) \ 27 | typedef className _MEMBER_FN_BASE_TYPE 28 | 29 | #define DEFINE_MEMBER_FN_LONG(className, functionName, retnType, address, ...) \ 30 | typedef retnType (className::* _##functionName##_type)(__VA_ARGS__); \ 31 | \ 32 | inline _##functionName##_type * _##functionName##_GetPtr(void) \ 33 | { \ 34 | static const UInt32 _address = address; \ 35 | return (_##functionName##_type *)&_address; \ 36 | } 37 | 38 | #define DEFINE_MEMBER_FN(functionName, retnType, address, ...) \ 39 | DEFINE_MEMBER_FN_LONG(_MEMBER_FN_BASE_TYPE, functionName, retnType, address, __VA_ARGS__) 40 | 41 | #define CALL_MEMBER_FN(obj, fn) \ 42 | ((*(obj)).*(*((obj)->_##fn##_GetPtr()))) 43 | 44 | 45 | // ConsolePrint() limited to 512 chars; use this to print longer strings to console 46 | void Console_Print_Long(const std::string& str); 47 | 48 | // Calls Print_Long or Print depending on the size of the string. 49 | void Console_Print_Str(const std::string& str); 50 | 51 | // Macro for debug output to console at runtime 52 | #if RUNTIME 53 | #ifdef _DEBUG 54 | #define DEBUG_PRINT(x, ...) { Console_Print((x), __VA_ARGS__); } 55 | #define DEBUG_MESSAGE(x, ...) { _MESSAGE((x), __VA_ARGS__); } 56 | #else 57 | #define DEBUG_PRINT(x, ...) { } 58 | #define DEBUG_MESSAGE(x, ...) { } 59 | #endif //_DEBUG 60 | #else 61 | #define DEBUG_PRINT(x, ...) { } 62 | #define DEBUG_MESSAGE(x, ...) { } 63 | // This is so we don't have to handle size change with EditorData :) 64 | #undef STATIC_ASSERT 65 | #define STATIC_ASSERT(a) 66 | #endif // RUNTIME 67 | 68 | #define SIZEOF_ARRAY(arrayName, elementType) (sizeof(arrayName) / sizeof(elementType)) 69 | 70 | class TESForm; 71 | 72 | class FormMatcher 73 | { 74 | public: 75 | virtual bool Matches(TESForm* pForm) const = 0; 76 | }; 77 | 78 | namespace MersenneTwister 79 | { 80 | 81 | /* initializes mt[N] with a seed */ 82 | void init_genrand(unsigned long s); 83 | 84 | /* initialize by an array with array-length */ 85 | void init_by_array(unsigned long init_key[], int key_length); 86 | 87 | /* generates a random number on [0,0xffffffff]-interval */ 88 | unsigned long genrand_int32(void); 89 | 90 | /* generates a random number on [0,0x7fffffff]-interval */ 91 | long genrand_int31(void); 92 | 93 | /* generates a random number on [0,1]-real-interval */ 94 | double genrand_real1(void); 95 | 96 | /* generates a random number on [0,1)-real-interval */ 97 | double genrand_real2(void); 98 | 99 | /* generates a random number on (0,1)-real-interval */ 100 | double genrand_real3(void); 101 | 102 | /* generates a random number on [0,1) with 53-bit resolution*/ 103 | double genrand_res53(void); 104 | 105 | }; 106 | 107 | // alternative to strtok; doesn't modify src string, supports forward/backward iteration 108 | class Tokenizer 109 | { 110 | public: 111 | Tokenizer(const char* src, const char* delims); 112 | ~Tokenizer() = default; 113 | 114 | // these return the offset of token in src, or -1 if no token 115 | UInt32 NextToken(std::string& outStr); 116 | std::string ToNewLine(); 117 | UInt32 PrevToken(std::string& outStr); 118 | 119 | private: 120 | std::string m_delims; 121 | size_t m_offset; 122 | std::string m_data; 123 | }; 124 | 125 | 126 | // For parsing lexical tokens inside script text line-by-line, while skipping over those inside comments. 127 | // Strings are passed as a single token (including the '"' characters). 128 | // Everything else will have to be manually handled. 129 | class ScriptTokenizer 130 | { 131 | public: 132 | ScriptTokenizer(std::string_view scriptText); 133 | ~ScriptTokenizer() = default; 134 | 135 | // Returns true if a new line could be read, false at the end of the script. 136 | // Skips over commented-out lines and empty lines. 137 | [[nodiscard]] bool TryLoadNextLine(); 138 | 139 | // Gets the next space-separated token from the loaded line, ignoring tokens placed inside comments. 140 | // Returns an empty string_view if no line is loaded / end-of-line is reached. 141 | std::string_view GetNextLineToken(); 142 | 143 | // Gets the entire line, for manual tokenizing. 144 | // Returns an empty string_view if no line is loaded. 145 | std::string_view GetLineText(); 146 | 147 | private: 148 | std::string_view m_scriptText; 149 | size_t m_scriptOffset = 0; 150 | std::vector m_loadedLineTokens; 151 | size_t m_tokenOffset = 0; 152 | bool m_inMultilineComment = false; 153 | }; 154 | 155 | #if RUNTIME 156 | 157 | const char GetSeparatorChar(Script * script); 158 | const char * GetSeparatorChars(Script * script); 159 | 160 | #endif 161 | 162 | const char * GetDXDescription(UInt32 keycode); 163 | 164 | bool ci_equal(char ch1, char ch2); 165 | bool ci_less(const char* lh, const char* rh); 166 | void MakeUpper(std::string& str); 167 | void MakeUpper(char* str); 168 | void MakeLower(std::string& str); 169 | 170 | // this copies the string onto the FormHeap - used to work around alloc/dealloc mismatch when passing 171 | // data between nvse and plugins 172 | char* CopyCString(const char* src); 173 | 174 | // Generic error/warning output 175 | // provides a common way to output errors and warnings 176 | class ErrOutput 177 | { 178 | typedef void (* _ShowError)(const char* msg); 179 | typedef bool (* _ShowWarning)(const char* msg); // returns true if user requests to disable warning 180 | 181 | _ShowError ShowError; 182 | _ShowWarning ShowWarning; 183 | public: 184 | ErrOutput(_ShowError errorFunc, _ShowWarning warningFunc); 185 | 186 | struct Message 187 | { 188 | const char* fmt; 189 | bool bCanDisable; 190 | bool bDisabled; 191 | }; 192 | 193 | void Show(Message msg, ...); 194 | void Show(const char* msg, ...); 195 | void vShow(Message& msg, va_list args); 196 | void vShow(const char* msg, va_list args); 197 | }; 198 | 199 | // thread-safe template versions of ThisStdCall() 200 | 201 | template 202 | __forceinline T_Ret ThisStdCall(UInt32 _addr, const void *_this, Args ...args) 203 | { 204 | return ((T_Ret (__thiscall *)(const void*, Args...))_addr)(_this, std::forward(args)...); 205 | } 206 | 207 | template 208 | __forceinline T_Ret StdCall(UInt32 _addr, Args ...args) 209 | { 210 | return ((T_Ret (__stdcall *)(Args...))_addr)(std::forward(args)...); 211 | } 212 | 213 | template 214 | __forceinline T_Ret CdeclCall(UInt32 _addr, Args ...args) 215 | { 216 | return ((T_Ret (__cdecl *)(Args...))_addr)(std::forward(args)...); 217 | } 218 | 219 | void ShowErrorMessageBox(const char* message); 220 | 221 | #if RUNTIME 222 | 223 | const char* GetModName(TESForm* form); 224 | 225 | void ShowRuntimeError(Script* script, const char* fmt, ...); 226 | 227 | #endif 228 | 229 | std::string FormatString(const char* fmt, ...); 230 | 231 | #if EDITOR 232 | void GeckExtenderMessageLog(const char* fmt, ...); 233 | #endif 234 | 235 | std::vector GetCallStack(int i); 236 | 237 | bool FindStringCI(const std::string& strHaystack, const std::string& strNeedle); 238 | void ReplaceAll(std::string &str, const std::string& from, const std::string& to); 239 | 240 | extern bool g_warnScriptErrors; 241 | 242 | template 243 | bool Contains(const L& list, T t) 244 | { 245 | for (auto i : list) 246 | { 247 | if (i == t) 248 | return true; 249 | } 250 | return false; 251 | } 252 | 253 | template 254 | bool Contains(std::initializer_list list, const T& t) 255 | { 256 | for (auto i : list) 257 | { 258 | if (i == t) 259 | return true; 260 | } 261 | return false; 262 | } 263 | 264 | #define _L(x, y) [&](x) {return y;} 265 | namespace ra = std::ranges; 266 | 267 | bool IsProcessRunning(const char* processName); 268 | 269 | void DisplayMessage(const char* msg); 270 | 271 | std::string GetCurPath(); 272 | 273 | bool ValidString(const char* str); 274 | 275 | #if _DEBUG 276 | 277 | 278 | const char* GetFormName(TESForm* form); 279 | const char* GetFormName(UInt32 formId); 280 | 281 | 282 | #endif 283 | 284 | typedef void* (*_FormHeap_Allocate)(UInt32 size); 285 | extern const _FormHeap_Allocate FormHeap_Allocate; 286 | 287 | template 288 | T* New(Args &&... args) 289 | { 290 | auto* alloc = FormHeap_Allocate(sizeof(T)); 291 | if constexpr (ConstructorPtr) 292 | { 293 | ThisStdCall(ConstructorPtr, alloc, std::forward(args)...); 294 | } 295 | else 296 | { 297 | memset(alloc, 0, sizeof(T)); 298 | } 299 | return static_cast(alloc); 300 | } 301 | 302 | typedef void (*_FormHeap_Free)(void* ptr); 303 | extern const _FormHeap_Free FormHeap_Free; 304 | 305 | template 306 | void Delete(T* t, Args &&... args) 307 | { 308 | if constexpr (DestructorPtr) 309 | { 310 | ThisStdCall(DestructorPtr, t, std::forward(args)...); 311 | } 312 | FormHeap_Free(t); 313 | } 314 | 315 | template 316 | using game_unique_ptr = std::unique_ptr>; 317 | 318 | template 319 | game_unique_ptr MakeUnique(T* t) 320 | { 321 | return game_unique_ptr(t, [](T* t2) { Delete(t2); }); 322 | } 323 | 324 | template 325 | game_unique_ptr MakeUnique(ConstructorArgs &&... args) 326 | { 327 | auto* obj = New(std::forward(args)...); 328 | return MakeUnique(obj); 329 | } 330 | 331 | bool StartsWith(std::string left, std::string right); 332 | std::string& ToLower(std::string&& data); 333 | std::string& StripSpace(std::string&& data); 334 | std::vector SplitString(std::string s, std::string delimiter); 335 | 336 | #define INLINE_HOOK(retnType, callingConv, ...) static_cast([](__VA_ARGS__) [[msvc::forceinline]] -> retnType 337 | 338 | UInt8* GetParentBasePtr(void* addressOfReturnAddress, bool lambda = false); 339 | 340 | //Example in https://en.cppreference.com/w/cpp/utility/variant/visit 341 | //Allows function overloading with c++ lambdas. 342 | template struct overloaded : Ts... { using Ts::operator()...; }; 343 | 344 | #if RUNTIME 345 | inline int __cdecl game_toupper(int _C) { return CdeclCall(0xECA7F4, _C); } 346 | inline int __cdecl game_tolower(int _C) { return CdeclCall(0xEC67AA, _C); } 347 | #else 348 | // GECK and other non-runtime code (ex: steam_loader) probably don't need localized stuff...? 349 | inline int __cdecl game_toupper(int _C) { return toupper(_C); } 350 | inline int __cdecl game_tolower(int _C) { return tolower(_C); } 351 | #endif -------------------------------------------------------------------------------- /common/utility.cpp: -------------------------------------------------------------------------------- 1 | #include "nvse/utility.h" 2 | 3 | memcpy_t _memcpy = memcpy, _memmove = memmove; 4 | 5 | __declspec(naked) PrimitiveCS *PrimitiveCS::Enter() 6 | { 7 | __asm 8 | { 9 | push ebx 10 | mov ebx, ecx 11 | call GetCurrentThreadId 12 | cmp [ebx], eax 13 | jnz doSpin 14 | done: 15 | mov eax, ebx 16 | pop ebx 17 | retn 18 | NOP_0xA 19 | doSpin: 20 | mov ecx, eax 21 | xor eax, eax 22 | lock cmpxchg [ebx], ecx 23 | test eax, eax 24 | jz done 25 | push esi 26 | push edi 27 | mov esi, ecx 28 | mov edi, 0x2710 29 | spinHead: 30 | dec edi 31 | mov edx, edi 32 | shr edx, 0x1F 33 | push edx 34 | call Sleep 35 | xor eax, eax 36 | lock cmpxchg [ebx], esi 37 | test eax, eax 38 | jnz spinHead 39 | pop edi 40 | pop esi 41 | mov eax, ebx 42 | pop ebx 43 | retn 44 | } 45 | } 46 | 47 | __declspec(naked) TESForm* __stdcall LookupFormByRefID(UInt32 refID) 48 | { 49 | __asm 50 | { 51 | mov ecx, ds:[0x11C54C0] 52 | mov eax, [esp+4] 53 | xor edx, edx 54 | div dword ptr [ecx+4] 55 | mov eax, [ecx+8] 56 | mov eax, [eax+edx*4] 57 | test eax, eax 58 | jz done 59 | mov edx, [esp+4] 60 | ALIGN 16 61 | iterHead: 62 | cmp [eax+4], edx 63 | jz found 64 | mov eax, [eax] 65 | test eax, eax 66 | jnz iterHead 67 | retn 4 68 | found: 69 | mov eax, [eax+8] 70 | done: 71 | retn 4 72 | } 73 | } 74 | 75 | __declspec(naked) int __vectorcall ifloor(float value) 76 | { 77 | __asm 78 | { 79 | movd eax, xmm0 80 | test eax, eax 81 | jns isPos 82 | push 0x3FA0 83 | ldmxcsr [esp] 84 | cvtss2si eax, xmm0 85 | mov dword ptr [esp], 0x1FA0 86 | ldmxcsr [esp] 87 | pop ecx 88 | retn 89 | isPos: 90 | cvttss2si eax, xmm0 91 | retn 92 | } 93 | } 94 | 95 | __declspec(naked) int __vectorcall iceil(float value) 96 | { 97 | __asm 98 | { 99 | movd eax, xmm0 100 | test eax, eax 101 | js isNeg 102 | push 0x5FA0 103 | ldmxcsr [esp] 104 | cvtss2si eax, xmm0 105 | mov dword ptr [esp], 0x1FA0 106 | ldmxcsr [esp] 107 | pop ecx 108 | retn 109 | isNeg: 110 | cvttss2si eax, xmm0 111 | retn 112 | } 113 | } 114 | 115 | __declspec(naked) UInt32 __fastcall StrLen(const char *str) 116 | { 117 | __asm 118 | { 119 | test ecx, ecx 120 | jnz proceed 121 | xor eax, eax 122 | retn 123 | proceed: 124 | push ecx 125 | test ecx, 3 126 | jz iter4 127 | iter1: 128 | mov al, [ecx] 129 | inc ecx 130 | test al, al 131 | jz done1 132 | test ecx, 3 133 | jnz iter1 134 | ALIGN 16 135 | iter4: 136 | mov eax, [ecx] 137 | mov edx, 0x7EFEFEFF 138 | add edx, eax 139 | not eax 140 | xor eax, edx 141 | add ecx, 4 142 | test eax, 0x81010100 143 | jz iter4 144 | mov eax, [ecx-4] 145 | test al, al 146 | jz done4 147 | test ah, ah 148 | jz done3 149 | test eax, 0xFF0000 150 | jz done2 151 | test eax, 0xFF000000 152 | jnz iter4 153 | done1: 154 | lea eax, [ecx-1] 155 | pop ecx 156 | sub eax, ecx 157 | retn 158 | done2: 159 | lea eax, [ecx-2] 160 | pop ecx 161 | sub eax, ecx 162 | retn 163 | done3: 164 | lea eax, [ecx-3] 165 | pop ecx 166 | sub eax, ecx 167 | retn 168 | done4: 169 | lea eax, [ecx-4] 170 | pop ecx 171 | sub eax, ecx 172 | retn 173 | } 174 | } 175 | 176 | __declspec(naked) void __fastcall MemZero(void *dest, UInt32 bsize) 177 | { 178 | __asm 179 | { 180 | push edi 181 | test ecx, ecx 182 | jz done 183 | mov edi, ecx 184 | xor eax, eax 185 | mov ecx, edx 186 | shr ecx, 2 187 | jz write1 188 | rep stosd 189 | write1: 190 | and edx, 3 191 | jz done 192 | mov ecx, edx 193 | rep stosb 194 | done: 195 | pop edi 196 | retn 197 | } 198 | } 199 | 200 | __declspec(naked) char* __fastcall StrCopy(char *dest, const char *src) 201 | { 202 | __asm 203 | { 204 | mov eax, ecx 205 | test ecx, ecx 206 | jz done 207 | test edx, edx 208 | jnz proceed 209 | mov [eax], 0 210 | done: 211 | retn 212 | proceed: 213 | push ecx 214 | mov ecx, edx 215 | call StrLen 216 | pop edx 217 | push eax 218 | inc eax 219 | push eax 220 | push ecx 221 | push edx 222 | call _memmove 223 | add esp, 0xC 224 | pop ecx 225 | add eax, ecx 226 | retn 227 | } 228 | } 229 | 230 | __declspec(naked) char* __fastcall StrNCopy(char *dest, const char *src, UInt32 length) 231 | { 232 | __asm 233 | { 234 | mov eax, ecx 235 | test ecx, ecx 236 | jz done 237 | test edx, edx 238 | jz nullTerm 239 | cmp dword ptr [esp+4], 0 240 | jz nullTerm 241 | push esi 242 | mov esi, ecx 243 | mov ecx, edx 244 | call StrLen 245 | mov edx, [esp+8] 246 | cmp edx, eax 247 | cmova edx, eax 248 | push edx 249 | push ecx 250 | push esi 251 | add esi, edx 252 | call _memmove 253 | add esp, 0xC 254 | mov eax, esi 255 | pop esi 256 | nullTerm: 257 | mov [eax], 0 258 | done: 259 | retn 4 260 | } 261 | } 262 | 263 | __declspec(naked) char* __fastcall StrCat(char *dest, const char *src) 264 | { 265 | __asm 266 | { 267 | test ecx, ecx 268 | jnz proceed 269 | mov eax, ecx 270 | retn 271 | proceed: 272 | push edx 273 | call StrLen 274 | pop edx 275 | add ecx, eax 276 | jmp StrCopy 277 | } 278 | } 279 | 280 | alignas(16) const char 281 | kLwrCaseConverter[] = 282 | { 283 | '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', 284 | '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F', 285 | '\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2A', '\x2B', '\x2C', '\x2D', '\x2E', '\x2F', 286 | '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39', '\x3A', '\x3B', '\x3C', '\x3D', '\x3E', '\x3F', 287 | '\x40', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6A', '\x6B', '\x6C', '\x6D', '\x6E', '\x6F', 288 | '\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', '\x78', '\x79', '\x7A', '\x5B', '\x5C', '\x5D', '\x5E', '\x5F', 289 | '\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6A', '\x6B', '\x6C', '\x6D', '\x6E', '\x6F', 290 | '\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', '\x78', '\x79', '\x7A', '\x7B', '\x7C', '\x7D', '\x7E', '\x7F', 291 | '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', '\x89', '\x8A', '\x8B', '\x8C', '\x8D', '\x8E', '\x8F', 292 | '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', '\x99', '\x9A', '\x9B', '\x9C', '\x9D', '\x9E', '\x9F', 293 | '\xA0', '\xA1', '\xA2', '\xA3', '\xA4', '\xA5', '\xA6', '\xA7', '\xA8', '\xA9', '\xAA', '\xAB', '\xAC', '\xAD', '\xAE', '\xAF', 294 | '\xB0', '\xB1', '\xB2', '\xB3', '\xB4', '\xB5', '\xB6', '\xB7', '\xB8', '\xB9', '\xBA', '\xBB', '\xBC', '\xBD', '\xBE', '\xBF', 295 | '\xC0', '\xC1', '\xC2', '\xC3', '\xC4', '\xC5', '\xC6', '\xC7', '\xC8', '\xC9', '\xCA', '\xCB', '\xCC', '\xCD', '\xCE', '\xCF', 296 | '\xD0', '\xD1', '\xD2', '\xD3', '\xD4', '\xD5', '\xD6', '\xD7', '\xD8', '\xD9', '\xDA', '\xDB', '\xDC', '\xDD', '\xDE', '\xDF', 297 | '\xE0', '\xE1', '\xE2', '\xE3', '\xE4', '\xE5', '\xE6', '\xE7', '\xE8', '\xE9', '\xEA', '\xEB', '\xEC', '\xED', '\xEE', '\xEF', 298 | '\xF0', '\xF1', '\xF2', '\xF3', '\xF4', '\xF5', '\xF6', '\xF7', '\xF8', '\xF9', '\xFA', '\xFB', '\xFC', '\xFD', '\xFE', '\xFF' 299 | }; 300 | 301 | // Returns 0 if both strings are equal. 302 | char __fastcall StrCompare(const char *lstr, const char *rstr) 303 | { 304 | if (!lstr) return rstr ? -1 : 0; 305 | if (!rstr) return 1; 306 | UInt8 lchr, rchr; 307 | while (*lstr) 308 | { 309 | lchr = game_toupper(*(UInt8*)lstr); 310 | rchr = game_toupper(*(UInt8*)rstr); 311 | if (lchr == rchr) 312 | { 313 | lstr++; 314 | rstr++; 315 | continue; 316 | } 317 | return (lchr < rchr) ? -1 : 1; 318 | } 319 | return *rstr ? -1 : 0; 320 | } 321 | 322 | void __fastcall StrToLower(char *str) 323 | { 324 | if (!str) return; 325 | UInt8 curr; 326 | while (curr = *str) 327 | { 328 | *str = game_tolower(curr); 329 | str++; 330 | } 331 | } 332 | 333 | char* __fastcall SubStrCI(const char *srcStr, const char *subStr) 334 | { 335 | int srcLen = StrLen(srcStr); 336 | if (!srcLen) return NULL; 337 | int subLen = StrLen(subStr); 338 | if (!subLen) return NULL; 339 | srcLen -= subLen; 340 | if (srcLen < 0) return NULL; 341 | int index; 342 | do 343 | { 344 | index = 0; 345 | while (true) 346 | { 347 | if (game_tolower(*(UInt8*)(srcStr + index)) != game_tolower(*(UInt8*)(subStr + index))) 348 | break; 349 | if (++index == subLen) 350 | return const_cast(srcStr); 351 | } 352 | srcStr++; 353 | } 354 | while (--srcLen >= 0); 355 | return NULL; 356 | } 357 | 358 | char* __fastcall SlashPos(const char *str) 359 | { 360 | if (!str) return NULL; 361 | char curr; 362 | while (curr = *str) 363 | { 364 | if ((curr == '/') || (curr == '\\')) 365 | return const_cast(str); 366 | str++; 367 | } 368 | return NULL; 369 | } 370 | 371 | __declspec(naked) char* __fastcall CopyString(const char *key) 372 | { 373 | __asm 374 | { 375 | call StrLen 376 | inc eax 377 | push eax 378 | push ecx 379 | push eax 380 | #if !_DEBUG 381 | call _malloc_base 382 | #else 383 | call malloc 384 | #endif 385 | pop ecx 386 | push eax 387 | call _memcpy 388 | add esp, 0xC 389 | retn 390 | } 391 | } 392 | 393 | __declspec(naked) char* __fastcall CopyString(const char* key, UInt32 length) 394 | { 395 | __asm 396 | { 397 | mov eax, edx // length 398 | inc eax // length++ to account for null terminator 399 | push eax 400 | push ecx 401 | push eax 402 | #if !_DEBUG 403 | call _malloc_base 404 | #else 405 | call malloc 406 | #endif 407 | pop ecx // equivalent to "add esp, 4" here 408 | // stack is now: 409 | // [esp+4]: length + 1 410 | // [esp]: "key" arg passed to CopyString 411 | 412 | // length -= 1, to avoid copying null terminator or past-the-end part of string (if it had no null terminator) 413 | dec [esp+4] 414 | 415 | push eax // dest = malloc's new ptr 416 | call _memcpy 417 | // eax is now memcpy's new string 418 | add esp, 8 419 | pop edx // get pushed eax back (length) 420 | // add null terminator to copied string in case string arg didn't have one 421 | add edx, eax // advance to last char of string (where null terminator should be) 422 | mov byte ptr[edx], 0 423 | retn 424 | } 425 | } 426 | 427 | __declspec(naked) char* __fastcall IntToStr(char *str, int num) 428 | { 429 | __asm 430 | { 431 | push esi 432 | push edi 433 | test edx, edx 434 | jns skipNeg 435 | neg edx 436 | mov [ecx], '-' 437 | inc ecx 438 | skipNeg: 439 | mov esi, ecx 440 | mov edi, ecx 441 | mov eax, edx 442 | mov ecx, 0xA 443 | workIter: 444 | xor edx, edx 445 | div ecx 446 | add dl, '0' 447 | mov [esi], dl 448 | inc esi 449 | test eax, eax 450 | jnz workIter 451 | mov [esi], 0 452 | mov eax, esi 453 | swapIter: 454 | dec esi 455 | cmp esi, edi 456 | jbe done 457 | mov dl, [esi] 458 | mov cl, [edi] 459 | mov [esi], cl 460 | mov [edi], dl 461 | inc edi 462 | jmp swapIter 463 | done: 464 | pop edi 465 | pop esi 466 | retn 467 | } 468 | } 469 | 470 | // By JazzIsParis 471 | __declspec(naked) UInt32 __fastcall StrHashCS(const char *inKey) 472 | { 473 | __asm 474 | { 475 | mov eax, 0x6B49D20B 476 | test ecx, ecx 477 | jz done 478 | ALIGN 16 479 | iterHead: 480 | movzx edx, byte ptr[ecx] 481 | test dl, dl 482 | jz done 483 | shl edx, 4 484 | sub eax, edx 485 | mov edx, eax 486 | shl eax, 5 487 | sub eax, edx 488 | movzx edx, byte ptr[ecx + 1] 489 | test dl, dl 490 | jz done 491 | shl edx, 0xC 492 | sub eax, edx 493 | mov edx, eax 494 | shl eax, 5 495 | sub eax, edx 496 | movzx edx, byte ptr[ecx + 2] 497 | test dl, dl 498 | jz done 499 | shl edx, 0x14 500 | sub eax, edx 501 | mov edx, eax 502 | shl eax, 5 503 | sub eax, edx 504 | movzx edx, byte ptr[ecx + 3] 505 | test dl, dl 506 | jz done 507 | sub eax, edx 508 | mov edx, eax 509 | shl eax, 5 510 | sub eax, edx 511 | add ecx, 4 512 | jmp iterHead 513 | done : 514 | ret 515 | } 516 | } 517 | 518 | // By JazzIsParis 519 | // "only < 0.008% collisions" 520 | __declspec(naked) UInt32 __fastcall StrHashCI(const char* inKey) 521 | { 522 | __asm 523 | { 524 | push esi 525 | mov eax, 0x6B49D20B 526 | test ecx, ecx 527 | jz done 528 | mov esi, ecx 529 | xor ecx, ecx 530 | ALIGN 16 531 | iterHead: 532 | mov cl, [esi] 533 | test cl, cl 534 | jz done 535 | movzx edx, kLwrCaseConverter[ecx] 536 | shl edx, 4 537 | sub eax, edx 538 | mov edx, eax 539 | shl eax, 5 540 | sub eax, edx 541 | mov cl, [esi + 1] 542 | test cl, cl 543 | jz done 544 | movzx edx, kLwrCaseConverter[ecx] 545 | shl edx, 0xC 546 | sub eax, edx 547 | mov edx, eax 548 | shl eax, 5 549 | sub eax, edx 550 | mov cl, [esi + 2] 551 | test cl, cl 552 | jz done 553 | movzx edx, kLwrCaseConverter[ecx] 554 | shl edx, 0x14 555 | sub eax, edx 556 | mov edx, eax 557 | shl eax, 5 558 | sub eax, edx 559 | mov cl, [esi + 3] 560 | test cl, cl 561 | jz done 562 | movzx edx, kLwrCaseConverter[ecx] 563 | sub eax, edx 564 | mov edx, eax 565 | shl eax, 5 566 | sub eax, edx 567 | add esi, 4 568 | jmp iterHead 569 | done : 570 | pop esi 571 | retn 572 | } 573 | } 574 | 575 | void SpinLock::Enter() 576 | { 577 | UInt32 threadID = GetCurrentThreadId(); 578 | if (owningThread == threadID) 579 | { 580 | enterCount++; 581 | return; 582 | } 583 | while (InterlockedCompareExchange(&owningThread, threadID, 0)); 584 | enterCount = 1; 585 | } 586 | 587 | #define FAST_SLEEP_COUNT 10000UL 588 | 589 | void SpinLock::EnterSleep() 590 | { 591 | UInt32 threadID = GetCurrentThreadId(); 592 | if (owningThread == threadID) 593 | { 594 | enterCount++; 595 | return; 596 | } 597 | UInt32 fastIdx = FAST_SLEEP_COUNT; 598 | while (InterlockedCompareExchange(&owningThread, threadID, 0)) 599 | { 600 | if (fastIdx) 601 | { 602 | fastIdx--; 603 | Sleep(0); 604 | } 605 | else Sleep(1); 606 | } 607 | enterCount = 1; 608 | } 609 | 610 | void SpinLock::Leave() 611 | { 612 | if (owningThread && !--enterCount) 613 | owningThread = 0; 614 | } 615 | 616 | // From JIP 617 | alignas(16) const UInt32 kPackedValues[] = 618 | { 619 | PS_DUP_4(0x7FFFFFFF), 620 | PS_DUP_1(0x7FFFFFFF), 621 | PS_DUP_4(0x80000000), 622 | PS_DUP_1(0x80000000), 623 | PS_DUP_3(0xFFFFFFFF), 624 | 0xFFFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF, 625 | 0, 0x80000000, 0, 0x80000000, 626 | PS_DUP_4(HEX(FLT_EPSILON)), 627 | PS_DUP_3(HEX(FltPId180)), 628 | PS_DUP_3(HEX(Flt180dPI)), 629 | PS_DUP_3(HEX(FltPId2)), 630 | PS_DUP_3(HEX(FltPI)), 631 | PS_DUP_3(HEX(FltPIx2)), 632 | PS_DUP_3(HEX(0.5F)), 633 | PS_DUP_3(HEX(1.0F)), 634 | PS_DUP_3(0x40DFF8D6), 635 | HEX(0.001F), HEX(0.01F), HEX(0.1F), HEX(0.25F), 636 | HEX(3.0F), HEX(10.0F), HEX(100.0F), 0 637 | }; -------------------------------------------------------------------------------- /common/utility.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Utilities.h" // for ThisStdCall 5 | #include 6 | 7 | typedef void* (*memcpy_t)(void*, const void*, size_t); 8 | extern memcpy_t _memcpy, _memmove; 9 | 10 | // Workaround for bypassing the compiler calling the d'tor on function-scope objects. 11 | template class TempObject 12 | { 13 | friend T; 14 | 15 | struct Buffer 16 | { 17 | UInt8 bytes[sizeof(T)]; 18 | } 19 | objData; 20 | 21 | public: 22 | TempObject() 23 | { 24 | if constexpr (InitConstructor) 25 | Reset(); 26 | } 27 | TempObject(const T &src) {objData = *(Buffer*)&src;} 28 | 29 | void Reset() {new ((T*)&objData) T();} 30 | 31 | T& operator()() {return *(T*)&objData;} 32 | 33 | TempObject& operator=(const T &rhs) {objData = *(Buffer*)&rhs;} 34 | TempObject& operator=(const TempObject &rhs) {objData = rhs.objData;} 35 | }; 36 | 37 | // Assign rhs to lhs, bypassing operator= 38 | template __forceinline void RawAssign(const T &lhs, const T &rhs) 39 | { 40 | struct Helper 41 | { 42 | UInt8 bytes[sizeof(T)]; 43 | }; 44 | *(Helper*)&lhs = *(Helper*)&rhs; 45 | } 46 | 47 | // Swap lhs and rhs, bypassing operator= 48 | template __forceinline void RawSwap(const T &lhs, const T &rhs) 49 | { 50 | struct Helper 51 | { 52 | UInt8 bytes[sizeof(T)]; 53 | } 54 | temp = *(Helper*)&lhs; 55 | *(Helper*)&lhs = *(Helper*)&rhs; 56 | *(Helper*)&rhs = temp; 57 | } 58 | 59 | // These are used for 10h aligning segments in ASM code (massive performance gain, particularly with loops). 60 | #define EMIT(bt) __asm _emit bt 61 | #define NOP_0x1 EMIT(0x90) 62 | #define NOP_0x2 EMIT(0x66) EMIT(0x90) 63 | #define NOP_0x3 EMIT(0x0F) EMIT(0x1F) EMIT(0x00) 64 | #define NOP_0x4 EMIT(0x0F) EMIT(0x1F) EMIT(0x40) EMIT(0x00) 65 | #define NOP_0x5 EMIT(0x0F) EMIT(0x1F) EMIT(0x44) EMIT(0x00) EMIT(0x00) 66 | #define NOP_0x6 EMIT(0x66) EMIT(0x0F) EMIT(0x1F) EMIT(0x44) EMIT(0x00) EMIT(0x00) 67 | #define NOP_0x7 EMIT(0x0F) EMIT(0x1F) EMIT(0x80) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) 68 | #define NOP_0x8 EMIT(0x0F) EMIT(0x1F) EMIT(0x84) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) 69 | #define NOP_0x9 EMIT(0x66) EMIT(0x0F) EMIT(0x1F) EMIT(0x84) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) 70 | #define NOP_0xA NOP_0x5 NOP_0x5 71 | #define NOP_0xB NOP_0x5 NOP_0x6 72 | #define NOP_0xC NOP_0x6 NOP_0x6 73 | #define NOP_0xD NOP_0x6 NOP_0x7 74 | #define NOP_0xE NOP_0x7 NOP_0x7 75 | #define NOP_0xF NOP_0x7 NOP_0x8 76 | 77 | class PrimitiveCS 78 | { 79 | DWORD m_owningThread; 80 | 81 | public: 82 | PrimitiveCS() : m_owningThread(0) {} 83 | 84 | PrimitiveCS *Enter(); 85 | __forceinline void Leave() {m_owningThread = 0;} 86 | }; 87 | 88 | class PrimitiveScopedLock 89 | { 90 | PrimitiveCS *m_cs; 91 | 92 | public: 93 | PrimitiveScopedLock(PrimitiveCS &cs) : m_cs(&cs) {cs.Enter();} 94 | ~PrimitiveScopedLock() {m_cs->Leave();} 95 | }; 96 | 97 | class TESForm; 98 | TESForm* __stdcall LookupFormByRefID(UInt32 refID); 99 | 100 | int __vectorcall ifloor(float value); 101 | 102 | int __vectorcall iceil(float value); 103 | 104 | UInt32 __fastcall StrLen(const char* str); 105 | 106 | void __fastcall MemZero(void* dest, UInt32 bsize); 107 | 108 | char* __fastcall StrCopy(char* dest, const char* src); 109 | 110 | char* __fastcall StrNCopy(char* dest, const char* src, UInt32 length); 111 | 112 | char* __fastcall StrCat(char* dest, const char* src); 113 | 114 | char __fastcall StrCompare(const char* lstr, const char* rstr); 115 | 116 | void __fastcall StrToLower(char* str); 117 | 118 | char* __fastcall SubStrCI(const char *srcStr, const char *subStr); 119 | 120 | char* __fastcall SlashPos(const char *str); 121 | 122 | char* __fastcall CopyString(const char* key); 123 | char* __fastcall CopyString(const char* key, UInt32 length); 124 | 125 | char* __fastcall IntToStr(char *str, int num); 126 | 127 | UInt32 __fastcall StrHashCS(const char* inKey); 128 | 129 | UInt32 __fastcall StrHashCI(const char* inKey); 130 | 131 | class SpinLock 132 | { 133 | UInt32 owningThread; 134 | UInt32 enterCount; 135 | 136 | public: 137 | SpinLock() : owningThread(0), enterCount(0) {} 138 | 139 | void Enter(); 140 | void EnterSleep(); 141 | void Leave(); 142 | }; 143 | 144 | #define GetRandomUInt(n) ThisStdCall(0xAA5230, (void*)0x11C4180, n) 145 | 146 | // From JIP 147 | #define PS_DUP_1(a) a, 0UL, 0UL, 0UL 148 | #define PS_DUP_2(a) a, a, 0UL, 0UL 149 | #define PS_DUP_3(a) a, a, a, 0UL 150 | #define PS_DUP_4(a) a, a, a, a 151 | 152 | // From JIP 153 | #define HEX(a) std::bit_cast(a) 154 | #define UBYT(a) *((UInt8*)&a) 155 | #define USHT(a) *((UInt16*)&a) 156 | #define ULNG(a) *((UInt32*)&a) 157 | 158 | // From JIP 159 | extern const UInt32 kPackedValues[]; 160 | 161 | // From JIP 162 | #define GET_PS(i) ((const __m128*)kPackedValues)[i] 163 | #define GET_SS(i) ((const float*)kPackedValues)[i << 2] 164 | 165 | // From JIP 166 | #define PS_AbsMask kPackedValues 167 | #define PS_AbsMask0 kPackedValues+0x10 168 | #define PS_FlipSignMask kPackedValues+0x20 169 | #define PS_FlipSignMask0 kPackedValues+0x30 170 | #define PS_XYZ0Mask kPackedValues+0x40 171 | #define PD_AbsMask kPackedValues+0x50 172 | #define PD_FlipSignMask kPackedValues+0x60 173 | 174 | // From JIP 175 | #define PS_Epsilon kPackedValues+0x70 176 | #define PS_V3_PId180 kPackedValues+0x80 177 | #define PS_V3_180dPI kPackedValues+0x90 178 | #define PS_V3_PId2 kPackedValues+0xA0 179 | #define PS_V3_PI kPackedValues+0xB0 180 | #define PS_V3_PIx2 kPackedValues+0xC0 181 | #define PS_V3_Half kPackedValues+0xD0 182 | #define PS_V3_One kPackedValues+0xE0 183 | #define PS_HKUnitCnvrt kPackedValues+0xF0 184 | 185 | // From JIP 186 | #define SS_1d1K kPackedValues+0x100 187 | #define SS_1d100 kPackedValues+0x104 188 | #define SS_1d10 kPackedValues+0x108 189 | #define SS_1d4 kPackedValues+0x10C 190 | #define SS_3 kPackedValues+0x110 191 | #define SS_10 kPackedValues+0x114 192 | #define SS_100 kPackedValues+0x118 193 | 194 | // From JIP 195 | #define FltPId2 1.570796371F 196 | #define FltPI 3.141592741F 197 | #define FltPIx2 6.283185482F 198 | #define FltPId180 0.01745329238F 199 | #define Flt180dPI 57.29578018F 200 | #define DblPId180 0.017453292519943295 201 | #define Dbl180dPI 57.29577951308232 -------------------------------------------------------------------------------- /dllmain.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | BOOL WINAPI DllMain( 4 | HANDLE hDllHandle, 5 | DWORD dwReason, 6 | LPVOID lpreserved 7 | ) 8 | { 9 | return TRUE; 10 | } 11 | -------------------------------------------------------------------------------- /fps.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | double g_iMaxFPSTolerance = 800; 5 | double g_iMinFPSTolerance = 5; 6 | double fDesiredMin = 1; 7 | double fDesiredMax = 1000; 8 | float fLowerMaxTimeBoundary = 0.016; 9 | float fMaxTimeDefault = 0; 10 | constexpr float fTimerOffsetMult = 0.9875; 11 | 12 | float* g_FPSGlobal = (float*)0x1090BA4; 13 | float* fMaxTime = (float*)0x10F2BE8; 14 | 15 | DWORD** InterfSingleton = (DWORD**)0x1075B24; 16 | DWORD** BGSLoadGameSingleton = (DWORD**)0x1079858; 17 | 18 | bool* g_DialogMenu = (bool*)0x107613C; 19 | bool* g_DialogMenu2 = (bool*)0x107A0F4; 20 | bool* g_bIsMenuMode = (bool*)0x107A0F3; 21 | bool* g_bIsInPauseFade = (bool*)0x107A0F5; 22 | bool* g_bIsLoadingNewGame = (bool*)0x1075A07; 23 | 24 | namespace QPC { 25 | 26 | double lastCount = 0; 27 | signed int tickCountBias = 0; 28 | using namespace std::chrono; 29 | 30 | void StartCounter() 31 | { 32 | auto now = duration_cast(duration_cast(time_point_cast(steady_clock::now()).time_since_epoch())); 33 | long duration = now.count(); 34 | 35 | 36 | QPC::tickCountBias = duration - long(GetTickCount()); //this will fail if your system was started 53 or more days ago 37 | QPC::lastCount = duration; 38 | 39 | } 40 | DWORD ReturnCounter() 41 | { 42 | auto now = duration_cast(duration_cast(time_point_cast(steady_clock::now()).time_since_epoch())); 43 | return unsigned long(now.count() + tickCountBias); 44 | } 45 | 46 | double UpdateCounterMS() 47 | { 48 | 49 | auto now = duration_cast(duration_cast(time_point_cast(steady_clock::now()).time_since_epoch())); 50 | double currentCount = double(now.count()) / 1000000; 51 | double toReturn = currentCount - lastCount; 52 | lastCount = currentCount; 53 | return toReturn; 54 | 55 | } 56 | 57 | } 58 | 59 | 60 | void* __stdcall TimeGlobalHook() { 61 | 62 | double delta = QPC::UpdateCounterMS(); 63 | 64 | if (delta <= FLT_EPSILON) { 65 | *fMaxTime = fLowerMaxTimeBoundary; 66 | } 67 | else if (delta >= fDesiredMax) { 68 | *fMaxTime = fDesiredMax / 1000; 69 | } 70 | else if (delta <= fDesiredMin) { 71 | *fMaxTime = fDesiredMin / 1000; 72 | } 73 | else { 74 | *fMaxTime = delta / 1000; 75 | } 76 | 77 | if ((*BGSLoadGameSingleton && (*(*BGSLoadGameSingleton + 0x91) & 2) != 0) || *g_bIsLoadingNewGame || delta <= 0) { 78 | *g_FPSGlobal = 0; 79 | } 80 | else if (delta >= fDesiredMax) { 81 | *g_FPSGlobal = fDesiredMax; 82 | } 83 | else if (delta <= fDesiredMin) { 84 | *g_FPSGlobal = fDesiredMin; 85 | } 86 | else { 87 | *g_FPSGlobal = delta; 88 | } 89 | 90 | if (*g_FPSGlobal > FLT_EPSILON) 91 | { 92 | *g_FPSGlobal = 1000 / ((1000 / *g_FPSGlobal) * fTimerOffsetMult); 93 | *fMaxTime = 1000 / ((1000 / *fMaxTime) * fTimerOffsetMult); 94 | 95 | if (*fMaxTime < FLT_EPSILON) { 96 | *fMaxTime = FLT_EPSILON; 97 | } 98 | 99 | } 100 | if (*g_bIsInPauseFade || *g_FPSGlobal < FLT_EPSILON) { 101 | *fMaxTime = fMaxTimeDefault; 102 | } 103 | else if (*fMaxTime > fLowerMaxTimeBoundary) { 104 | *fMaxTime = fLowerMaxTimeBoundary; 105 | } 106 | 107 | return ThisStdCall(0x7F92A0, nullptr); 108 | } 109 | 110 | void FastExit() 111 | { 112 | TerminateProcess(GetCurrentProcess(), 0); 113 | } 114 | 115 | void RemoveRenderer180CriticalSections() 116 | { 117 | SafeWrite16(0x873165, 0x0BEB); 118 | SafeWrite8(0x87317C, 0x13); 119 | SafeWrite16(0x873189, 0x05EB); 120 | 121 | SafeWrite16(0x88B92F, 0x0BEB); 122 | SafeWrite8(0x88B942, 0x2F); 123 | SafeWrite8(0x88B954, 0x4E); 124 | SafeWrite16(0x88B96B, 0x05EB); 125 | 126 | SafeWrite16(0x88BA7A, 0x0BEB); 127 | SafeWrite8(0x88BA8D, 0x28); 128 | SafeWrite8(0x88BA9F, 0x5); 129 | SafeWrite16(0x88BAAF, 0x05EB); 130 | } 131 | 132 | 133 | void WritePatches() { 134 | 135 | 136 | QPC::StartCounter(); 137 | SafeWrite32(0xD9B090, (uintptr_t)QPC::ReturnCounter); 138 | 139 | fDesiredMin = 1000.0 / g_iMaxFPSTolerance; 140 | fDesiredMax = 1000.0 / g_iMinFPSTolerance; 141 | fMaxTimeDefault = *fMaxTime; 142 | 143 | WriteRelCall(0x6EDC02, (uintptr_t)TimeGlobalHook); 144 | 145 | WriteRelJump(0x6EED54, UInt32(FastExit)); 146 | RemoveRenderer180CriticalSections(); 147 | 148 | } -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "common/IPrefix.h" 2 | #include "common/PluginAPI.h" 3 | #include "SafeWrite.h" 4 | #include "calls.h" 5 | #include "fps.h" 6 | extern "C" { 7 | __declspec(dllexport) bool FOSEPlugin_Query(const FOSEInterface* fose, PluginInfo* info) { 8 | info->infoVersion = PluginInfo::kInfoVersion; 9 | info->name = "HighFpsFix"; 10 | info->version = 100; 11 | return true; 12 | } 13 | 14 | __declspec(dllexport) bool FOSEPlugin_Load(FOSEInterface* fose) { 15 | if (!fose->isEditor) { 16 | WritePatches(); 17 | } 18 | return true; 19 | } 20 | } 21 | --------------------------------------------------------------------------------