From ffdd413e674679d90156830752de7e1b83778a30 Mon Sep 17 00:00:00 2001 From: c6 <31777460+c6-dev@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:34:13 +0400 Subject: [PATCH] Add project files. --- .gitattributes | 63 ++ .gitignore | 363 +++++++++++ HighFPSFix.sln | 24 + HighFPSFix.vcxproj | 146 +++++ HighFPSFix.vcxproj.filters | 22 + LICENSE.txt | 201 +++++++ README.md | 1 + calls.h | 58 ++ common/IDataStream.cpp | 467 +++++++++++++++ common/IDataStream.h | 102 ++++ common/IDebugLog.cpp | 301 ++++++++++ common/IDebugLog.h | 132 ++++ common/IDynamicCreate.cpp | 38 ++ common/IDynamicCreate.h | 118 ++++ common/IErrors.cpp | 59 ++ common/IErrors.h | 33 + common/IPrefix.cpp | 1 + common/IPrefix.h | 21 + common/ISingleton.cpp | 3 + common/ISingleton.h | 53 ++ common/ITypes.cpp | 65 ++ common/ITypes.h | 357 +++++++++++ common/PluginAPI.h | 48 ++ common/SafeWrite.cpp | 99 +++ common/SafeWrite.h | 41 ++ common/Utilities.cpp | 1162 ++++++++++++++++++++++++++++++++++++ common/Utilities.h | 351 +++++++++++ common/utility.cpp | 637 ++++++++++++++++++++ common/utility.h | 201 +++++++ dllmain.c | 10 + fps.h | 124 ++++ main.cpp | 20 + 32 files changed, 5321 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 HighFPSFix.sln create mode 100644 HighFPSFix.vcxproj create mode 100644 HighFPSFix.vcxproj.filters create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 calls.h create mode 100644 common/IDataStream.cpp create mode 100644 common/IDataStream.h create mode 100644 common/IDebugLog.cpp create mode 100644 common/IDebugLog.h create mode 100644 common/IDynamicCreate.cpp create mode 100644 common/IDynamicCreate.h create mode 100644 common/IErrors.cpp create mode 100644 common/IErrors.h create mode 100644 common/IPrefix.cpp create mode 100644 common/IPrefix.h create mode 100644 common/ISingleton.cpp create mode 100644 common/ISingleton.h create mode 100644 common/ITypes.cpp create mode 100644 common/ITypes.h create mode 100644 common/PluginAPI.h create mode 100644 common/SafeWrite.cpp create mode 100644 common/SafeWrite.h create mode 100644 common/Utilities.cpp create mode 100644 common/Utilities.h create mode 100644 common/utility.cpp create mode 100644 common/utility.h create mode 100644 dllmain.c create mode 100644 fps.h create mode 100644 main.cpp diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/HighFPSFix.sln b/HighFPSFix.sln new file mode 100644 index 0000000..697ead0 --- /dev/null +++ b/HighFPSFix.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32630.192 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HighFPSFix", "HighFPSFix.vcxproj", "{AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}.Debug|Win32.ActiveCfg = Debug|Win32 + {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}.Debug|Win32.Build.0 = Debug|Win32 + {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}.Release|Win32.ActiveCfg = Release|Win32 + {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {71BF81C1-7B0B-49CB-92D7-5D7E4612E20C} + EndGlobalSection +EndGlobal diff --git a/HighFPSFix.vcxproj b/HighFPSFix.vcxproj new file mode 100644 index 0000000..cb83395 --- /dev/null +++ b/HighFPSFix.vcxproj @@ -0,0 +1,146 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {AE7CFEE7-4058-4E3C-ADC2-AE7480EE2028} + nvse_plugin_example + Win32Proj + 10.0 + HighFPSFix + + + + DynamicLibrary + v143 + MultiByte + true + + + DynamicLibrary + v143 + MultiByte + + + + + + + + + + + + + <_ProjectFileVersion>16.0.28916.169 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(DXSDK_DIR)Lib\x86 + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(DXSDK_DIR)Include + true + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86 + $(VC_IncludePath);$(WindowsSDK_IncludePath) + true + + + + Disabled + $(SolutionDir);$(SolutionDir)\common;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;NVSE_PLUGIN_EXAMPLE_EXPORTS;RUNTIME_VERSION=0x040020D0;RUNTIME;SSAA;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + ProgramDatabase + %(ForcedIncludeFiles) + stdcpp20 + true + + + + + true + Windows + false + + MachineX86 + winmm.lib;%(AdditionalDependencies) + + + Installing DLL... + copy "$(TargetPath)" "C:\Users\user\AppData\Local\ModOrganizer\Fallout 3\mods\High FPS Fix\fose\plugins\$(TargetFileName)" + + + + + $(SolutionDir);$(SolutionDir)\common;;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;NVSE_PLUGIN_EXAMPLE_EXPORTS;RUNTIME_VERSION=0x040020D0;RUNTIME;%(PreprocessorDefinitions) + MultiThreadedDLL + + Level3 + ProgramDatabase + %(ForcedIncludeFiles) + stdcpp20 + Speed + StreamingSIMDExtensions2 + Default + false + true + Fast + true + true + Full + false + + + + + true + Windows + true + true + false + + MachineX86 + winmm.lib;%(AdditionalDependencies) + + + + + copy "$(TargetPath)" "C:\Users\user\AppData\Local\ModOrganizer\Fallout 3\mods\High FPS Fix\fose\plugins\$(TargetFileName)" + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HighFPSFix.vcxproj.filters b/HighFPSFix.vcxproj.filters new file mode 100644 index 0000000..70cad01 --- /dev/null +++ b/HighFPSFix.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {92a4f350-f2ec-4fca-bdd3-927732d91b34} + + + + + + + fose + + + + + fose + + + + + \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..877ce92 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# code \ No newline at end of file diff --git a/calls.h b/calls.h new file mode 100644 index 0000000..0872370 --- /dev/null +++ b/calls.h @@ -0,0 +1,58 @@ +#pragma once +// thread-safe template versions of ThisStdCall() + +template +__forceinline T_Ret ThisStdCall(UInt32 _addr, void* _this, Args ...args) { + class T {}; + union { + UInt32 addr; + T_Ret(T::* func)(Args...); + } u = { _addr }; + return ((T*)_this->*u.func)(std::forward(args)...); +} +template +__forceinline T_Ret StdCall(UInt32 _addr, Args ...args) { + return ((T_Ret(__stdcall*)(Args...))_addr)(std::forward(args)...); +} + +template +__forceinline T_Ret CdeclCall(UInt32 _addr, Args ...args) { + return ((T_Ret(__cdecl*)(Args...))_addr)(std::forward(args)...); +} +// Templates for UInt8 return. + +__forceinline UInt8 ThisStdCall_B(UInt32 _f, void* _t) { + class T {}; union { UInt32 x; UInt8(T::* m)(); } u = { _f }; + return ((T*)_t->*u.m)(); +} + +template +__forceinline UInt8 ThisStdCall_B(UInt32 _f, void* _t, T1 a1) { + class T {}; union { UInt32 x; UInt8(T::* m)(T1); } u = { _f }; + return ((T*)_t->*u.m)(a1); +} + +template +__forceinline UInt8 ThisStdCall_B(UInt32 _f, void* _t, T1 a1, T2 a2) { + class T {}; union { UInt32 x; UInt8(T::* m)(T1, T2); } u = { _f }; + return ((T*)_t->*u.m)(a1, a2); +} + +template +__forceinline UInt8 ThisStdCall_B(UInt32 _f, void* _t, T1 a1, T2 a2, T3 a3) { + class T {}; union { UInt32 x; UInt8(T::* m)(T1, T2, T3); } u = { _f }; + return ((T*)_t->*u.m)(a1, a2, a3); +} + +// Templates for float return. + +__forceinline float ThisStdCall_F(UInt32 _f, void* _t) { + class T {}; union { UInt32 x; float (T::* m)(); } u = { _f }; + return ((T*)_t->*u.m)(); +} + +template +__forceinline float ThisStdCall_F(UInt32 _f, void* _t, T1 a1) { + class T {}; union { UInt32 x; float (T::* m)(T1); } u = { _f }; + return ((T*)_t->*u.m)(a1); +} \ No newline at end of file diff --git a/common/IDataStream.cpp b/common/IDataStream.cpp new file mode 100644 index 0000000..d149681 --- /dev/null +++ b/common/IDataStream.cpp @@ -0,0 +1,467 @@ +#include "IDataStream.h" + +/**** IDataStream *************************************************************/ + +IDataStream::IDataStream() + :streamLength(0), streamOffset(0), swapBytes(false) +{ +} + +IDataStream::~IDataStream() +{ +} + +/** + * Reads and returns an 8-bit value from the stream + */ +UInt8 IDataStream::Read8(void) +{ + UInt8 out; + + ReadBuf(&out, sizeof(UInt8)); + + return out; +} + +/** + * Reads and returns a 16-bit value from the stream + */ +UInt16 IDataStream::Read16(void) +{ + UInt16 out; + + ReadBuf(&out, sizeof(UInt16)); + + if (swapBytes) + out = Swap16(out); + + return out; +} + +/** + * Reads and returns a 32-bit value from the stream + */ +UInt32 IDataStream::Read32(void) +{ + UInt32 out; + + ReadBuf(&out, sizeof(UInt32)); + + if (swapBytes) + out = Swap32(out); + + return out; +} + +/** + * Reads and returns a 64-bit value from the stream + */ +UInt64 IDataStream::Read64(void) +{ + UInt64 out; + + ReadBuf(&out, sizeof(UInt64)); + + if (swapBytes) + out = Swap64(out); + + return out; +} + +/** + * Reads and returns a 32-bit floating point value from the stream + */ +float IDataStream::ReadFloat(void) +{ + UInt32 out = Read32(); + + return *((float*)&out); +} + +/** + * Reads a null-or-return-terminated string from the stream + * + * If the buffer is too small to hold the entire string, it is truncated and + * properly terminated. + * + * @param buf the output buffer + * @param bufLength the size of the output buffer + * @return the number of characters written to the buffer + */ +UInt32 IDataStream::ReadString(char* buf, UInt32 bufLength, char altTerminator, char altTerminator2) +{ + char* traverse = buf; + bool breakOnReturns = false; + + if ((altTerminator == '\n') || (altTerminator2 == '\n')) + breakOnReturns = true; + + ASSERT_STR(bufLength > 0, "IDataStream::ReadString: zero-sized buffer"); + + if (bufLength == 1) + { + buf[0] = 0; + return 0; + } + + bufLength--; + + for (UInt32 i = 0; i < bufLength; i++) + { + UInt8 data = Read8(); + + if (breakOnReturns) + { + if (data == 0x0D) + { + if (Peek8() == 0x0A) + Skip(1); + + break; + } + } + + if (!data || (data == altTerminator) || (data == altTerminator2)) + { + break; + } + + *traverse++ = data; + } + + *traverse++ = 0; + + return traverse - buf - 1; +} + +/** + * Reads and returns an 8-bit value from the stream without advancing the stream's position + */ +UInt8 IDataStream::Peek8(void) +{ + IDataStream_PositionSaver saver(this); + + return Read8(); +} + +/** + * Reads and returns a 16-bit value from the stream without advancing the stream's position + */ +UInt16 IDataStream::Peek16(void) +{ + IDataStream_PositionSaver saver(this); + + return Read16(); +} + +/** + * Reads and returns a 32-bit value from the stream without advancing the stream's position + */ +UInt32 IDataStream::Peek32(void) +{ + IDataStream_PositionSaver saver(this); + + return Read32(); +} + +/** + * Reads and returns a 32-bit value from the stream without advancing the stream's position + */ +UInt64 IDataStream::Peek64(void) +{ + IDataStream_PositionSaver saver(this); + + return Read64(); +} + +/** + * Reads and returns a 32-bit floating point value from the stream without advancing the stream's position + */ +float IDataStream::PeekFloat(void) +{ + IDataStream_PositionSaver saver(this); + + return ReadFloat(); +} + +/** + * Reads raw data into a buffer without advancing the stream's position + */ +void IDataStream::PeekBuf(void* buf, UInt32 inLength) +{ + IDataStream_PositionSaver saver(this); + + ReadBuf(buf, inLength); +} + +/** + * Skips a specified number of bytes down the stream + */ +void IDataStream::Skip(SInt64 inBytes) +{ + SetOffset(GetOffset() + inBytes); +} + +/** + * Writes an 8-bit value to the stream. + */ +void IDataStream::Write8(UInt8 inData) +{ + WriteBuf(&inData, sizeof(UInt8)); +} + +/** + * Writes a 16-bit value to the stream. + */ +void IDataStream::Write16(UInt16 inData) +{ + if (swapBytes) + inData = Swap16(inData); + + WriteBuf(&inData, sizeof(UInt16)); +} + +/** + * Writes a 32-bit value to the stream. + */ +void IDataStream::Write32(UInt32 inData) +{ + if (swapBytes) + inData = Swap32(inData); + + WriteBuf(&inData, sizeof(UInt32)); +} + +/** + * Writes a 64-bit value to the stream. + */ +void IDataStream::Write64(UInt64 inData) +{ + if (swapBytes) + inData = Swap64(inData); + + WriteBuf(&inData, sizeof(UInt64)); +} + +/** + * Writes a 32-bit floating point value to the stream. + */ +void IDataStream::WriteFloat(float inData) +{ + if (swapBytes) + { + UInt32 temp = *((UInt32*)&inData); + + temp = Swap32(temp); + + WriteBuf(&temp, sizeof(UInt32)); + } + else + { + WriteBuf(&inData, sizeof(float)); + } +} + +/** + * Writes a null-terminated string to the stream. + */ +void IDataStream::WriteString(const char* buf) +{ + WriteBuf(buf, std::strlen(buf) + 1); +} + +/** + * Returns the length of the stream + */ +SInt64 IDataStream::GetLength(void) +{ + return streamLength; +} + +/** + * Returns the number of bytes remaining in the stream + */ +SInt64 IDataStream::GetRemain(void) +{ + return streamLength - streamOffset; +} + +/** + * Returns the current offset into the stream + */ +SInt64 IDataStream::GetOffset(void) +{ + return streamOffset; +} + +/** + * Returns whether we have reached the end of the stream or not + */ +bool IDataStream::HitEOF(void) +{ + return streamOffset >= streamLength; +} + +/** + * Moves the current offset into the stream + */ +void IDataStream::SetOffset(SInt64 inOffset) +{ + streamOffset = inOffset; +} + +/** + * Enables or disables byte swapping for basic data transfers + */ +void IDataStream::SwapBytes(bool inSwapBytes) +{ + swapBytes = inSwapBytes; +} + +IDataStream* IDataStream::GetRootParent(void) +{ + IDataStream* parent = GetParent(); + + if (parent) + return parent->GetRootParent(); + else + return this; +} + +void IDataStream::CopyStreams(IDataStream* out, IDataStream* in, UInt64 bufferSize, UInt8* buf) +{ + in->Rewind(); + + bool ourBuffer = false; + + if (!buf) + { + buf = new UInt8[bufferSize]; + ourBuffer = true; + } + + UInt64 remain = in->GetLength(); + + while (remain > 0) + { + UInt64 transferSize = remain; + + if (transferSize > bufferSize) + transferSize = bufferSize; + + in->ReadBuf(buf, transferSize); + out->WriteBuf(buf, transferSize); + + remain -= transferSize; + } + + if (ourBuffer) + delete[] buf; +} + +void IDataStream::CopySubStreams(IDataStream* out, IDataStream* in, UInt64 remain, UInt64 bufferSize, UInt8* buf) +{ + bool ourBuffer = false; + + if (!buf) + { + buf = new UInt8[bufferSize]; + ourBuffer = true; + } + + while (remain > 0) + { + UInt64 transferSize = remain; + + if (transferSize > bufferSize) + transferSize = bufferSize; + + in->ReadBuf(buf, transferSize); + out->WriteBuf(buf, transferSize); + + remain -= transferSize; + } + + if (ourBuffer) + delete[] buf; +} + +/**** IDataStream_PositionSaver ***********************************************/ + +/** + * The constructor; save the stream's position + */ +IDataStream_PositionSaver::IDataStream_PositionSaver(IDataStream* tgt) +{ + stream = tgt; + offset = tgt->GetOffset(); +} + +/** + * The destructor; restore the stream's saved position + */ +IDataStream_PositionSaver::~IDataStream_PositionSaver() +{ + stream->SetOffset(offset); +} + +/**** IDataSubStream **********************************************************/ + +IDataSubStream::IDataSubStream() + :stream(NULL), subBase(0) +{ + // +} + +IDataSubStream::IDataSubStream(IDataStream* inStream, SInt64 inOffset, SInt64 inLength) +{ + stream = inStream; + subBase = inOffset; + streamLength = inLength; + + stream->SetOffset(inOffset); +} + +IDataSubStream::~IDataSubStream() +{ +} + +void IDataSubStream::Attach(IDataStream* inStream, SInt64 inOffset, SInt64 inLength) +{ + stream = inStream; + subBase = inOffset; + streamLength = inLength; + + stream->SetOffset(inOffset); +} + +void IDataSubStream::ReadBuf(void* buf, UInt32 inLength) +{ + ASSERT_STR(inLength <= GetRemain(), "IDataSubStream::ReadBuf: hit eof"); + + if (stream->GetOffset() != subBase + streamOffset) + stream->SetOffset(subBase + streamOffset); + + stream->ReadBuf(buf, inLength); + + streamOffset += inLength; +} + +void IDataSubStream::WriteBuf(const void* buf, UInt32 inLength) +{ + if (stream->GetOffset() != subBase + streamOffset) + stream->SetOffset(subBase + streamOffset); + + stream->WriteBuf(buf, inLength); + + streamOffset += inLength; + + if (streamLength < streamOffset) + streamLength = streamOffset; +} + +void IDataSubStream::SetOffset(SInt64 inOffset) +{ + stream->SetOffset(subBase + inOffset); + streamOffset = inOffset; +} \ No newline at end of file diff --git a/common/IDataStream.h b/common/IDataStream.h new file mode 100644 index 0000000..4e08af9 --- /dev/null +++ b/common/IDataStream.h @@ -0,0 +1,102 @@ +#pragma once + +#include "common/IErrors.h" + +/** + * An arbitrary data stream + */ +class IDataStream +{ +public: + IDataStream(); + virtual ~IDataStream(); + + // read + virtual UInt8 Read8(void); + virtual UInt16 Read16(void); + virtual UInt32 Read32(void); + virtual UInt64 Read64(void); + virtual float ReadFloat(void); + virtual UInt32 ReadString(char* buf, UInt32 bufLength, char altTerminator = 0, char altTerminator2 = 0); + virtual void ReadBuf(void* buf, UInt32 inLength) = 0; + + // peek + virtual UInt8 Peek8(void); + virtual UInt16 Peek16(void); + virtual UInt32 Peek32(void); + virtual UInt64 Peek64(void); + virtual float PeekFloat(void); + virtual void PeekBuf(void* buf, UInt32 inLength); + + virtual void Skip(SInt64 inBytes); + + // write + virtual void Write8(UInt8 inData); + virtual void Write16(UInt16 inData); + virtual void Write32(UInt32 inData); + virtual void Write64(UInt64 inData); + virtual void WriteFloat(float inData); + virtual void WriteString(const char* buf); + virtual void WriteBuf(const void* buf, UInt32 inLength) = 0; + + SInt64 GetLength(void); + SInt64 GetRemain(void); + SInt64 GetOffset(void); + bool HitEOF(void); + + virtual void SetOffset(SInt64 inOffset); + void Rewind(void) { SetOffset(0); } + + void SwapBytes(bool inSwapBytes); + + virtual SInt64 GetParentOffset(void) { return GetOffset(); } + virtual IDataStream* GetParent(void) { return NULL; } + + IDataStream* GetRootParent(void); + + static void CopyStreams(IDataStream* out, IDataStream* in, UInt64 bufferSize = 1024 * 1024, UInt8* buf = NULL); + static void CopySubStreams(IDataStream* out, IDataStream* in, UInt64 remain, UInt64 bufferSize = 1024 * 1024, UInt8* buf = NULL); + +protected: + SInt64 streamLength; + SInt64 streamOffset; + bool swapBytes; +}; + +/** + * A utility class to automatically save and restore the current position of an IDataStream + */ +class IDataStream_PositionSaver +{ +public: + IDataStream_PositionSaver(IDataStream* tgt); + ~IDataStream_PositionSaver(); + +private: + IDataStream* stream; + SInt64 offset; +}; + +class IDataSubStream : public IDataStream +{ +public: + IDataSubStream(); + IDataSubStream(IDataStream* inStream, SInt64 inOffset, SInt64 inLength); + ~IDataSubStream(); + + void Attach(IDataStream* inStream, SInt64 inOffset, SInt64 inLength); + + void ReadBuf(void* buf, UInt32 inLength); + void WriteBuf(const void* buf, UInt32 inLength); + void SetOffset(SInt64 inOffset); + + virtual SInt64 GetParentOffset(void) { return stream->GetOffset(); } + virtual IDataStream* GetParent(void) { return stream; } + + SInt64 GetSubBase(void) { return subBase; } + +private: + IDataStream* stream; + + SInt64 subBase; +}; diff --git a/common/IDebugLog.cpp b/common/IDebugLog.cpp new file mode 100644 index 0000000..b503e33 --- /dev/null +++ b/common/IDebugLog.cpp @@ -0,0 +1,301 @@ +#include "common/IDebugLog.h" +#include +#include "common/IFileStream.h" +#include + +std::FILE* IDebugLog::logFile = NULL; +char IDebugLog::sourceBuf[16] = { 0 }; +char IDebugLog::headerText[16] = { 0 }; +char IDebugLog::formatBuf[8192] = { 0 }; +int IDebugLog::indentLevel = 0; +int IDebugLog::rightMargin = 0; +int IDebugLog::cursorPos = 0; +int IDebugLog::inBlock = 0; +bool IDebugLog::autoFlush = true; +IDebugLog::LogLevel IDebugLog::logLevel = IDebugLog::kLevel_DebugMessage; +IDebugLog::LogLevel IDebugLog::printLevel = IDebugLog::kLevel_Message; + +IDebugLog::IDebugLog() +{ + // +} + +IDebugLog::IDebugLog(const char* name) +{ + Open(name); +} + +IDebugLog::~IDebugLog() +{ + if (logFile) + fclose(logFile); +} + +void IDebugLog::Open(const char* path) +{ + logFile = _fsopen(path, "w", _SH_DENYWR); + + if (!logFile) + { + UInt32 id = 0; + char name[1024]; + + do + { + sprintf_s(name, sizeof(name), "%s%d", path, id); + id++; + + logFile = NULL; + logFile = _fsopen(name, "w", _SH_DENYWR); + } while (!logFile && (id < 5)); + } +} + +void IDebugLog::OpenRelative(int folderID, const char* relPath) +{ + char path[MAX_PATH]; + + ASSERT(SUCCEEDED(SHGetFolderPath(NULL, folderID, NULL, SHGFP_TYPE_CURRENT, path))); + + strcat_s(path, sizeof(path), relPath); + + IFileStream::MakeAllDirs(path); + + Open(path); +} + +/** + * Output a non-formatted message to the log file + * + * @param message the message + * @param source the source of the message, or NULL to use the previous source + */ +void IDebugLog::Message(const char* message, const char* source) +{ + if (source) + SetSource(source); + + if (inBlock) + { + SeekCursor(RoundToTab((indentLevel * 4) + strlen(headerText))); + } + else + { + SeekCursor(indentLevel * 4); + + PrintText(headerText); + } + + PrintText(message); + NewLine(); +} + +/** + * Output a formatted message to the log file + * + * @note It is impossible to set the source of a formatted message. + * The previous source will be used. + */ +void IDebugLog::FormattedMessage(const char* fmt, ...) +{ + va_list argList; + + va_start(argList, fmt); + vsprintf_s(formatBuf, sizeof(formatBuf), fmt, argList); + Message(formatBuf); + va_end(argList); +} + +/** + * Output a formatted message to the log file + * + * @note It is impossible to set the source of a formatted message. + * The previous source will be used. + */ +void IDebugLog::FormattedMessage(const char* fmt, va_list args) +{ + vsprintf_s(formatBuf, sizeof(formatBuf), fmt, args); + Message(formatBuf); +} + +void IDebugLog::Log(LogLevel level, const char* fmt, va_list args) +{ + bool log = (level <= logLevel); + bool print = (level <= printLevel); + + if (log || print) + vsprintf_s(formatBuf, sizeof(formatBuf), fmt, args); + + if (log) + Message(formatBuf); + + if (print) + printf("%s\n", formatBuf); +} + +/** + * Set the current message source + */ +void IDebugLog::SetSource(const char* source) +{ + strcpy_s(sourceBuf, sizeof(sourceBuf), source); + strcpy_s(headerText, sizeof(headerText), "[ ]\t"); + + char* tgt = headerText + 1; + char* src = sourceBuf; + + for (int i = 0; (i < 8) && *src; i++, tgt++, src++) + *tgt = *src; +} + +/** + * Clear the current message source + */ +void IDebugLog::ClearSource(void) +{ + sourceBuf[0] = 0; +} + +/** + * Increase the indentation level + */ +void IDebugLog::Indent(void) +{ + indentLevel++; +} + +/** + * Decrease the indentation level + */ +void IDebugLog::Outdent(void) +{ + if (indentLevel) + indentLevel--; +} + +/** + * Enter a logical block + */ +void IDebugLog::OpenBlock(void) +{ + SeekCursor(indentLevel * 4); + + PrintText(headerText); + + inBlock = 1; +} + +/** + * Close a logical block + */ +void IDebugLog::CloseBlock(void) +{ + inBlock = 0; +} + +/** + * Enable/disable autoflush + * + * @param inAutoFlush autoflush state + */ +void IDebugLog::SetAutoFlush(bool inAutoFlush) +{ + autoFlush = inAutoFlush; +} + +/** + * Print spaces to the log + * + * If possible, tabs are used instead of spaces. + */ +void IDebugLog::PrintSpaces(int numSpaces) +{ + int originalNumSpaces = numSpaces; + + if (logFile) + { + while (numSpaces > 0) + { + if (numSpaces >= TabSize()) + { + numSpaces -= TabSize(); + fputc('\t', logFile); + } + else + { + numSpaces--; + fputc(' ', logFile); + } + } + } + + cursorPos += originalNumSpaces; +} + +/** + * Prints raw text to the log file + */ +void IDebugLog::PrintText(const char* buf) +{ + if (logFile) + { + fputs(buf, logFile); + if (autoFlush) + fflush(logFile); + } + + const char* traverse = buf; + char data; + + while (data = *traverse++) + { + if (data == '\t') + cursorPos += TabSize(); + else + cursorPos++; + } +} + +/** + * Moves to the next line of the log file + */ +void IDebugLog::NewLine(void) +{ + if (logFile) + { + fputc('\n', logFile); + + if (autoFlush) + fflush(logFile); + } + + cursorPos = 0; +} + +/** + * Prints spaces to align the cursor to the requested position + * + * @note The cursor move will not be performed if the request would move the cursor + * backwards. + */ +void IDebugLog::SeekCursor(int position) +{ + if (position > cursorPos) + PrintSpaces(position - cursorPos); +} + +/** + * Returns the number of spaces a tab would occupy at the current cursor position + */ +int IDebugLog::TabSize(void) +{ + return ((~cursorPos) & 3) + 1; +} + +/** + * Rounds a number of spaces to the nearest tab + */ +int IDebugLog::RoundToTab(int spaces) +{ + return (spaces + 3) & ~3; +} \ No newline at end of file diff --git a/common/IDebugLog.h b/common/IDebugLog.h new file mode 100644 index 0000000..e4bfce9 --- /dev/null +++ b/common/IDebugLog.h @@ -0,0 +1,132 @@ +#pragma once + +#include + +/** + * A simple debug log file + * + * This class supports prefix blocks describing the source of the log event. + * It also allows logical blocks and outlining.\n + */ +class IDebugLog +{ +public: + IDebugLog(); + IDebugLog(const char* name); + ~IDebugLog(); + + static void Open(const char* path); + static void OpenRelative(int folderID, const char* relPath); + + static void Message(const char* message, const char* source = NULL); + static void FormattedMessage(const char* fmt, ...); + static void FormattedMessage(const char* fmt, va_list args); + + enum LogLevel + { + kLevel_FatalError = 0, + kLevel_Error, + kLevel_Warning, + kLevel_Message, + kLevel_VerboseMessage, + kLevel_DebugMessage + }; + + static void Log(LogLevel level, const char* fmt, va_list args); + + static void SetSource(const char* source); + static void ClearSource(void); + + static void Indent(void); + static void Outdent(void); + + static void OpenBlock(void); + static void CloseBlock(void); + + static void SetAutoFlush(bool inAutoFlush); + + static void SetLogLevel(LogLevel in) { logLevel = in; } + static void SetPrintLevel(LogLevel in) { printLevel = in; } + +private: + static void PrintSpaces(int numSpaces); + static void PrintText(const char* buf); + static void NewLine(void); + + static void SeekCursor(int position); + + static int TabSize(void); + static int RoundToTab(int spaces); + + static FILE* logFile; //!< the output file + + static char sourceBuf[16]; //!< name of current source, used in prefix + static char headerText[16]; //!< current text to use as line prefix + static char formatBuf[8192]; //!< temp buffer used for formatted messages + + static int indentLevel; //!< the current indentation level (in tabs) + static int rightMargin; //!< the column at which text should be wrapped + static int cursorPos; //!< current cursor position + static int inBlock; //!< are we in a block? + + static bool autoFlush; //!< automatically flush the file after writing + + static LogLevel logLevel; //!< least important log level to write + static LogLevel printLevel; //!< least important log level to print +}; + +extern IDebugLog gLog; + +inline void _FATALERROR(const char* fmt, ...) +{ + va_list args; + + va_start(args, fmt); + gLog.Log(IDebugLog::kLevel_FatalError, fmt, args); + va_end(args); +} + +inline void _ERROR(const char* fmt, ...) +{ + va_list args; + + va_start(args, fmt); + gLog.Log(IDebugLog::kLevel_Error, fmt, args); + va_end(args); +} + +inline void _WARNING(const char* fmt, ...) +{ + va_list args; + + va_start(args, fmt); + gLog.Log(IDebugLog::kLevel_Warning, fmt, args); + va_end(args); +} + +inline void _MESSAGE(const char* fmt, ...) +{ + va_list args; + + va_start(args, fmt); + gLog.Log(IDebugLog::kLevel_Message, fmt, args); + va_end(args); +} + +inline void _VMESSAGE(const char* fmt, ...) +{ + va_list args; + + va_start(args, fmt); + gLog.Log(IDebugLog::kLevel_VerboseMessage, fmt, args); + va_end(args); +} + +inline void _DMESSAGE(const char* fmt, ...) +{ + va_list args; + + va_start(args, fmt); + gLog.Log(IDebugLog::kLevel_DebugMessage, fmt, args); + va_end(args); +} diff --git a/common/IDynamicCreate.cpp b/common/IDynamicCreate.cpp new file mode 100644 index 0000000..a32e7f7 --- /dev/null +++ b/common/IDynamicCreate.cpp @@ -0,0 +1,38 @@ +#include "IDynamicCreate.h" + +#if ENABLE_IDYNAMICCREATE + +IClassRegistry _gClassRegistry; + +IClassRegistry::IClassRegistry() +{ + // +} + +IClassRegistry::~IClassRegistry() +{ + // +} + +void IClassRegistry::RegisterClassInfo(UInt32 id, IDynamicType* typeInfo) +{ + theClassRegistry[id] = typeInfo; +} + +IDynamicType* IClassRegistry::LookupClassInfo(UInt32 id) +{ + ClassRegistryType::iterator iter = theClassRegistry.find(id); + + return (iter == theClassRegistry.end()) ? NULL : (*iter).second; +} + +IDynamicType* IClassRegistry::LookupClassInfo(char* name) +{ + for (ClassRegistryType::iterator iter = theClassRegistry.begin(); iter != theClassRegistry.end(); iter++) + if (!strcmp((*iter).second->GetName(), name)) + return (*iter).second; + + return NULL; +} + +#endif \ No newline at end of file diff --git a/common/IDynamicCreate.h b/common/IDynamicCreate.h new file mode 100644 index 0000000..70a7e23 --- /dev/null +++ b/common/IDynamicCreate.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include "common/IDataStream.h" +#include "common/IErrors.h" + +// this screws with edit-and-continue and we don't use it +#define ENABLE_IDYNAMICCREATE 0 + +#if ENABLE_IDYNAMICCREATE + +//! Get a pointer to the IDynamicType for a class. +//! @note This is not a function; the parameter must be constant. +#define GetDynType(name) (&(##name##::__DYN_DynamicType)) + +//! Declare the members used for dynamic class creation +#define DYNAMIC_DECLARE(name) \ +public: \ + class __DYN_##name##_DynamicType : public IDynamicType \ + { \ + public: \ + __DYN_##name##_DynamicType() { } \ + ~__DYN_##name##_DynamicType() { } \ + \ + virtual IDynamic * Create(void) { return new name; } \ + virtual char * GetName(void) { return #name; } \ + virtual IDynamic * Instantiate(IDataStream * stream); \ + }; \ + \ + static __DYN_##name##_DynamicType __DYN_DynamicType; \ + virtual IDynamicType * __DYN_GetDynamicType(void) { return &__DYN_DynamicType; } \ + \ + friend __DYN_##name##_DynamicType; + +//! Define the members used for dynamic class creation +#define DYNAMIC_DEFINE(name) name##::__DYN_##name##_DynamicType name##::__DYN_DynamicType; + +//! Define a dynamic instantiation handler +#define DYNAMIC_INSTANTIATE_HANDLER(name) IDynamic * name##::__DYN_##name##_DynamicType::Instantiate(IDataStream * stream) { name * object = new name; +#define END_DYNAMIC_INSTANTIATE_HANDLER return object; } + +//! Specifies that a dynamic class should not be instantiated automatically +#define NO_DYNAMIC_INSTANTIATE_HANDLER(name) DYNAMIC_INSTANTIATE_HANDLER(name) { HALT("attempted to instantiate " #name); } END_DYNAMIC_INSTANTIATE_HANDLER + +//! Casts +#define CAST(ptr, type) _DynamicCast (ptr); + +class IDynamicType; + +/** + * Pure virtual base class allowing dynamic creation of objects + * + * To allow dynamic creation of a class, publicly inherit IDynamic, add the + * macro DYNAMIC_DECLARE(classname) first in the class declaration, and add + * the macro DYNAMIC_DEFINE(classname) somewhere in the class definition file. + */ +class IDynamic +{ +public: + IDynamic() { } + virtual ~IDynamic() { } + + virtual IDynamicType* __DYN_GetDynamicType(void) = 0; +}; + +/** + * Pure virtual base class allowing class instantiation and information retrieval + */ +class IDynamicType +{ +public: + IDynamicType() { } + virtual ~IDynamicType() { } + + virtual IDynamic* Create(void) = 0; + virtual char* GetName(void) = 0; + + virtual IDynamic* Instantiate(IDataStream* stream) = 0; +}; + +//! +template +T* _DynamicCast(IDynamic* ptr) +{ + if (ptr && (&T::__DYN_DynamicType == ptr->__DYN_GetDynamicType())) + return static_cast(ptr); + + return NULL; +} + +/** + * Registry of dynamic classes + */ +class IClassRegistry +{ +public: + IClassRegistry(); + ~IClassRegistry(); + + static void RegisterClassInfo(UInt32 id, IDynamicType* typeInfo); + + static IDynamicType* LookupClassInfo(UInt32 id); + static IDynamicType* LookupClassInfo(char* name); + + static IDynamic* Create(UInt32 id) { IDynamicType* info = LookupClassInfo(id); return info ? info->Create() : NULL; } + static IDynamic* Create(char* name) { IDynamicType* info = LookupClassInfo(name); return info ? info->Create() : NULL; } + + static IDynamic* Instantiate(UInt32 id, IDataStream* stream) { IDynamicType* info = LookupClassInfo(id); return info ? info->Instantiate(stream) : NULL; } + static IDynamic* Instantiate(char* name, IDataStream* stream) { IDynamicType* info = LookupClassInfo(name); return info ? info->Instantiate(stream) : NULL; } + + static char* GetName(UInt32 id) { IDynamicType* info = LookupClassInfo(id); return info ? info->GetName() : NULL; } + +private: + typedef std::map ClassRegistryType; + static ClassRegistryType theClassRegistry; +}; + +#endif diff --git a/common/IErrors.cpp b/common/IErrors.cpp new file mode 100644 index 0000000..37ac9c7 --- /dev/null +++ b/common/IErrors.cpp @@ -0,0 +1,59 @@ +#include "common/IErrors.h" +#include "common/IDebugLog.h" +#include + +__declspec(noreturn) static void IErrors_Halt(void) +{ + // crash + *((int*)0) = 0xDEADBEEF; +} + +/** + * Report a failed assertion and exit the program + * + * @param file the file where the error occured + * @param line the line number where the error occured + * @param desc an error message + */ +void _AssertionFailed(const char* file, unsigned long line, const char* desc) +{ + _FATALERROR("Assertion failed in %s (%d): %s", file, line, desc); + + IErrors_Halt(); +} + +/** + * Report a failed assertion and exit the program + * + * @param file the file where the error occured + * @param line the line number where the error occured + * @param desc an error message + * @param code the error code + */ +void _AssertionFailed_ErrCode(const char* file, unsigned long line, const char* desc, unsigned long long code) +{ + if (code & 0xFFFFFFFF00000000) + _FATALERROR("Assertion failed in %s (%d): %s (code = %16I64X (%I64d))", file, line, desc, code, code); + else + { + UInt32 code32 = code; + _FATALERROR("Assertion failed in %s (%d): %s (code = %08X (%d))", file, line, desc, code32, code32); + } + + IErrors_Halt(); +} + +/** + * Report a failed assertion and exit the program + * + * @param file the file where the error occured + * @param line the line number where the error occured + * @param desc an error message + * @param code the error code + */ +void _AssertionFailed_ErrCode(const char* file, unsigned long line, const char* desc, const char* code) +{ + _FATALERROR("Assertion failed in %s (%d): %s (code = %s)", file, line, desc, code); + + IErrors_Halt(); +} \ No newline at end of file diff --git a/common/IErrors.h b/common/IErrors.h new file mode 100644 index 0000000..300104d --- /dev/null +++ b/common/IErrors.h @@ -0,0 +1,33 @@ +#pragma once + +void _AssertionFailed(const char* file, unsigned long line, const char* desc); +void _AssertionFailed_ErrCode(const char* file, unsigned long line, const char* desc, unsigned long long code); +void _AssertionFailed_ErrCode(const char* file, unsigned long line, const char* desc, const char* code); + +//! Exit the program if the condition is not true +#define ASSERT(a) do { if(!(a)) _AssertionFailed(__FILE__, __LINE__, #a); } while(0) +//! Exit the program if the condition is not true, with an error message +#define ASSERT_STR(a, b) do { if(!(a)) _AssertionFailed(__FILE__, __LINE__, b); } while(0) +//! Exit the program if the condition is not true, reporting an error code +#define ASSERT_CODE(a, b) do { if(!(a)) _AssertionFailed_ErrCode(__FILE__, __LINE__, #a, b); } while(0) +//! Exit the program if the condition is not true, reporting an error code and message +#define ASSERT_STR_CODE(a, b, c) do { if(!(a)) _AssertionFailed_ErrCode(__FILE__, __LINE__, b, c); } while(0) +//! Exit the program with an error message +#define HALT(a) do { _AssertionFailed(__FILE__, __LINE__, a); } while(0) +//! Exit the program with and error code and message +#define HALT_CODE(a, b) do { _AssertionFailed_ErrCode(__FILE__, __LINE__, a, b); } while(0) + +// based on the boost implementation of static asserts +template struct StaticAssertFailure; +template <> struct StaticAssertFailure { enum { a = 1 }; }; +template struct static_assert_test { }; + +#define __MACRO_JOIN__(a, b) __MACRO_JOIN_2__(a, b) +#define __MACRO_JOIN_2__(a, b) __MACRO_JOIN_3__(a, b) +#define __MACRO_JOIN_3__(a, b) a##b +#define __PREPRO_TOKEN_STR2__(a) #a +#define __PREPRO_TOKEN_STR__(a) __PREPRO_TOKEN_STR2__(a) +#define __LOC__ __FILE__ "("__PREPRO_TOKEN_STR__(__LINE__)") : " + +//#define STATIC_ASSERT(a) typedef static_assert_test )> __MACRO_JOIN__(static_assert_typedef_, __COUNTER__) +#define STATIC_ASSERT(a) static_assert(a) diff --git a/common/IPrefix.cpp b/common/IPrefix.cpp new file mode 100644 index 0000000..6231a06 --- /dev/null +++ b/common/IPrefix.cpp @@ -0,0 +1 @@ +#include "IPrefix.h" diff --git a/common/IPrefix.h b/common/IPrefix.h new file mode 100644 index 0000000..5e7cc86 --- /dev/null +++ b/common/IPrefix.h @@ -0,0 +1,21 @@ +#pragma once + +// 4018 - signed/unsigned mismatch +// 4244 - loss of data by assignment +// 4267 - possible loss of data (truncation) +// 4305 - truncation by assignment +// 4288 - disable warning for crap microsoft extension screwing up the scope of variables defined in for loops +// 4311 - pointer truncation +// 4312 - pointer extension +#pragma warning(disable: 4018 4244 4267 4305 4288 4312 4311) + +#include +#include +#include +#include "common/ITypes.h" +#include "common/IErrors.h" +#include "common/IDynamicCreate.h" +#include "common/IDebugLog.h" +#include "common/ISingleton.h" +#include +#include diff --git a/common/ISingleton.cpp b/common/ISingleton.cpp new file mode 100644 index 0000000..00818f4 --- /dev/null +++ b/common/ISingleton.cpp @@ -0,0 +1,3 @@ +#include "common/ISingleton.h" + +//template T * Singleton ::ms_Singleton = 0; \ No newline at end of file diff --git a/common/ISingleton.h b/common/ISingleton.h new file mode 100644 index 0000000..2cbe87a --- /dev/null +++ b/common/ISingleton.h @@ -0,0 +1,53 @@ +#pragma once + +#include "common/IErrors.h" + +#pragma warning(push) +#pragma warning(disable: 4311 4312) + +/** + * A singleton base class + * + * Singletons are useful when you have a class that will be instantiated once, + * like a global manager. + */ +template +class ISingleton +{ + static T * ms_Singleton; + + public: + ISingleton() + { + ASSERT(!ms_Singleton); + int offset = (int)(T *)1 - (int)(ISingleton *)(T *)1; + ms_Singleton = (T *)((int)this + offset); + } + + virtual ~ISingleton() + { + ASSERT(ms_Singleton); + ms_Singleton = 0; + } + + /** + * Returns the single instance of the derived class + */ + static T& GetSingleton(void) + { + ASSERT(ms_Singleton); + return *ms_Singleton; + } + + /** + * Returns a pointer to the single instance of the derived class + */ + static T * GetSingletonPtr(void) + { + return ms_Singleton; + } +}; + +template T * ISingleton ::ms_Singleton = 0; + +#pragma warning(pop) diff --git a/common/ITypes.cpp b/common/ITypes.cpp new file mode 100644 index 0000000..b9ad168 --- /dev/null +++ b/common/ITypes.cpp @@ -0,0 +1,65 @@ +#include "ITypes.h" + +Bitstring::Bitstring() + :data(NULL) +{ +} + +Bitstring::Bitstring(UInt32 inLength) + :data(NULL) +{ + Alloc(inLength); +} + +Bitstring::~Bitstring() +{ + Dispose(); +} + +void Bitstring::Alloc(UInt32 inLength) +{ + Dispose(); + + inLength = (inLength + 7) & ~7; + length = inLength >> 3; + + data = new UInt8[length]; +} + +void Bitstring::Dispose(void) +{ + delete[] data; +} + +void Bitstring::Clear(void) +{ + std::memset(data, 0, length); +} + +void Bitstring::Clear(UInt32 idx) +{ + ASSERT_STR(idx < (length << 3), "Bitstring::Clear: out of range"); + + data[idx >> 3] &= ~(1 << (idx & 7)); +} + +void Bitstring::Set(UInt32 idx) +{ + ASSERT_STR(idx < (length << 3), "Bitstring::Set: out of range"); + + data[idx >> 3] |= (1 << (idx & 7)); +} + +bool Bitstring::IsSet(UInt32 idx) +{ + ASSERT_STR(idx < (length << 3), "Bitstring::IsSet: out of range"); + + return (data[idx >> 3] & (1 << (idx & 7))) ? true : false; +} + +bool Bitstring::IsClear(UInt32 idx) +{ + ASSERT_STR(idx < (length << 3), "Bitstring::IsClear: out of range"); + + return (data[idx >> 3] & (1 << (idx & 7))) ? false : true; +} \ No newline at end of file diff --git a/common/ITypes.h b/common/ITypes.h new file mode 100644 index 0000000..f5dc5d9 --- /dev/null +++ b/common/ITypes.h @@ -0,0 +1,357 @@ +#pragma once + +#include "common/IErrors.h" + +#pragma warning(disable: 4221) +#include + +typedef unsigned char UInt8; //!< An unsigned 8-bit integer value +typedef unsigned short UInt16; //!< An unsigned 16-bit integer value +typedef unsigned long UInt32; //!< An unsigned 32-bit integer value +typedef unsigned long long UInt64; //!< An unsigned 64-bit integer value +typedef signed char SInt8; //!< A signed 8-bit integer value +typedef signed short SInt16; //!< A signed 16-bit integer value +typedef signed long SInt32; //!< A signed 32-bit integer value +typedef signed long long SInt64; //!< A signed 64-bit integer value +typedef float Float32; //!< A 32-bit floating point value +typedef double Float64; //!< A 64-bit floating point value + +inline UInt32 Extend16(UInt32 in) +{ + return (in & 0x8000) ? (0xFFFF0000 | in) : in; +} + +inline UInt32 Extend8(UInt32 in) +{ + return (in & 0x80) ? (0xFFFFFF00 | in) : in; +} + +inline UInt16 Swap16(UInt16 in) +{ + return ((in >> 8) & 0x00FF) | + ((in << 8) & 0xFF00); +} + +inline UInt32 Swap32(UInt32 in) +{ + return ((in >> 24) & 0x000000FF) | + ((in >> 8) & 0x0000FF00) | + ((in << 8) & 0x00FF0000) | + ((in << 24) & 0xFF000000); +} + +inline UInt64 Swap64(UInt64 in) +{ + UInt64 temp; + + temp = Swap32(in); + temp <<= 32; + temp |= Swap32(in >> 32); + + return temp; +} + +inline void SwapFloat(float* in) +{ + UInt32* temp = (UInt32*)in; + + *temp = Swap32(*temp); +} + +inline void SwapDouble(double* in) +{ + UInt64* temp = (UInt64*)in; + + *temp = Swap64(*temp); +} + +inline bool IsBigEndian(void) +{ + union + { + UInt16 u16; + UInt8 u8[2]; + } temp; + + temp.u16 = 0x1234; + + return temp.u8[0] == 0x12; +} + +inline bool IsLittleEndian(void) +{ + return !IsBigEndian(); +} + +#define CHAR_CODE(a, b, c, d) (((a & 0xFF) << 0) | ((b & 0xFF) << 8) | ((c & 0xFF) << 16) | ((d & 0xFF) << 24)) +#define MACRO_SWAP16(a) ((((a) & 0x00FF) << 8) | (((a) & 0xFF00) >> 8)) +#define MACRO_SWAP32(a) ((((a) & 0x000000FF) << 24) | (((a) & 0x0000FF00) << 8) | (((a) & 0x00FF0000) >> 8) | (((a) & 0xFF000000) >> 24)) + +#define VERSION_CODE(primary, secondary, sub) (((primary & 0xFFF) << 20) | ((secondary & 0xFFF) << 8) | ((sub & 0xFF) << 0)) +#define VERSION_CODE_PRIMARY(in) ((in >> 20) & 0xFFF) +#define VERSION_CODE_SECONDARY(in) ((in >> 8) & 0xFFF) +#define VERSION_CODE_SUB(in) ((in >> 0) & 0xFF) + +#define MAKE_COLOR(a, r, g, b) (((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0)) +#define COLOR_ALPHA(in) ((in >> 24) & 0xFF) +#define COLOR_RED(in) ((in >> 16) & 0xFF) +#define COLOR_GREEN(in) ((in >> 8) & 0xFF) +#define COLOR_BLUE(in) ((in >> 0) & 0xFF) + +/** + * A 64-bit variable combiner + * + * Useful for endian-independent value extraction. + */ +union VarCombiner +{ + UInt64 u64; + SInt64 s64; + double f64; + struct { UInt32 b; UInt32 a; } u32; + struct { SInt32 b; SInt32 a; } s32; + struct { float b; float a; } f32; + struct { UInt16 d; UInt16 c; UInt16 b; UInt16 a; } u16; + struct { SInt16 d; SInt16 c; SInt16 b; SInt16 a; } s16; + struct { + UInt8 h; UInt8 g; UInt8 f; UInt8 e; + UInt8 d; UInt8 c; UInt8 b; UInt8 a; + } u8; + struct { + SInt8 h; SInt8 g; SInt8 f; SInt8 e; + SInt8 d; SInt8 c; SInt8 b; SInt8 a; + } s8; +}; + +/** + * A bitfield. + */ +template +class Bitfield +{ +public: + Bitfield() { } + ~Bitfield() { } + + void Clear(void) { field = 0; } //!< Clears all bits + void RawSet(UInt32 data) { field = data; } //!< Modifies all bits + + void Set(UInt32 data) { field |= data; } //!< Sets individual bits + void Clear(UInt32 data) { field &= ~data; } //!< Clears individual bits + void Unset(UInt32 data) { Clear(data); } //!< Clears individual bits + void Mask(UInt32 data) { field &= data; } //!< Masks individual bits + void Toggle(UInt32 data) { field ^= data; } //!< Toggles individual bits + void SetBit(UInt32 data, bool state) + { + if (state) Set(data); else Clear(data); + } + + void SetField(T data, T mask, T pos) { + field = (field & ~mask) | (data << pos); + } + + T Get(void) const { return field; } //!< Gets all bits + T GetBit(UInt32 data) const { return field & data; } //!< Gets individual bits + T Extract(UInt32 bit) const { return (field >> bit) & 1; } //!< Extracts a bit + T ExtractField(UInt32 shift, UInt32 length) //!< Extracts a series of bits + { + return (field >> shift) & (0xFFFFFFFF >> (32 - length)); + } + + bool IsSet(UInt32 data) const { return ((field & data) == data) ? true : false; } //!< Are all these bits set? + bool IsUnSet(UInt32 data) const { return (field & data) ? false : true; } //!< Are all these bits clear? + bool IsClear(UInt32 data) const { return IsUnSet(data); } //!< Are all these bits clear? + + T field; //!< bitfield data +}; + +typedef Bitfield Bitfield8; //!< An 8-bit bitfield +typedef Bitfield Bitfield16; //!< A 16-bit bitfield +typedef Bitfield Bitfield32; //!< A 32-bit bitfield + +STATIC_ASSERT(sizeof(Bitfield8) == 1); +STATIC_ASSERT(sizeof(Bitfield16) == 2); +STATIC_ASSERT(sizeof(Bitfield32) == 4); + +/** + * A bitstring + * + * Essentially a long bitvector. + */ +class Bitstring +{ +public: + Bitstring(); + Bitstring(UInt32 inLength); + ~Bitstring(); + + void Alloc(UInt32 inLength); + void Dispose(void); + + void Clear(void); + void Clear(UInt32 idx); + void Set(UInt32 idx); + + bool IsSet(UInt32 idx); + bool IsClear(UInt32 idx); + +private: + UInt8* data; + UInt32 length; //!< length in bytes +}; + +/** + * Time storage + */ +class Time +{ +public: + Time() { Clear(); } + ~Time() { } + + //! Deinitialize the class + void Clear(void) { seconds = minutes = hours = 0; hasData = false; } + //! Sets the class to the current time + //! @todo implement this + void SetToNow(void) { Set(1, 2, 3); } + + //! Sets the class to the specified time + void Set(UInt8 inS, UInt8 inM, UInt8 inH) + { + seconds = inS; minutes = inM; hours = inH; hasData = true; + } + + //! Gets whether the class has been initialized or not + bool IsSet(void) { return hasData; } + + UInt8 GetSeconds(void) { return seconds; } //!< return the seconds portion of the time + UInt8 GetMinutes(void) { return minutes; } //!< return the minutes portion of the time + UInt8 GetHours(void) { return hours; } //!< return the hours portion of the time + +private: + UInt8 seconds, minutes, hours; + bool hasData; +}; + +const float kFloatEpsilon = 0.0001f; + +inline bool FloatEqual(float a, float b) { float magnitude = a - b; if (magnitude < 0) magnitude = -magnitude; return magnitude < kFloatEpsilon; } + +class Vector2 +{ +public: + Vector2() { } + Vector2(const Vector2& in) { x = in.x; y = in.y; } + Vector2(float inX, float inY) { x = inX; y = inY; } + ~Vector2() { } + + void Set(float inX, float inY) { x = inX; y = inY; } + void SetX(float inX) { x = inX; } + void SetY(float inY) { y = inY; } + void Get(float* outX, float* outY) { *outX = x; *outY = y; } + float GetX(void) { return x; } + float GetY(void) { return y; } + + void Normalize(void) { float mag = Magnitude(); x /= mag; y /= mag; } + float Magnitude(void) { return sqrt(x * x + y * y); } + + void Reverse(void) { float temp = -x; x = -y; y = temp; } + + void Scale(float scale) { x *= scale; y *= scale; } + + void SwapBytes(void) { SwapFloat(&x); SwapFloat(&y); } + + Vector2& operator+=(const Vector2& rhs) { x += rhs.x; y += rhs.y; return *this; } + Vector2& operator-=(const Vector2& rhs) { x -= rhs.x; y -= rhs.y; return *this; } + Vector2& operator*=(float rhs) { x *= rhs; y *= rhs; return *this; } + Vector2& operator/=(float rhs) { x /= rhs; y /= rhs; return *this; } + + float x; + float y; +}; + +inline Vector2 operator+(const Vector2& lhs, const Vector2& rhs) +{ + return Vector2(lhs.x + rhs.x, lhs.y + rhs.y); +}; + +inline Vector2 operator-(const Vector2& lhs, const Vector2& rhs) +{ + return Vector2(lhs.x - rhs.x, lhs.y - rhs.y); +}; + +inline Vector2 operator*(const Vector2& lhs, float rhs) +{ + return Vector2(lhs.x * rhs, lhs.y * rhs); +}; + +inline Vector2 operator/(const Vector2& lhs, float rhs) +{ + return Vector2(lhs.x / rhs, lhs.y / rhs); +}; + +inline bool MaskCompare(void* lhs, void* rhs, void* mask, UInt32 size) +{ + UInt8* lhs8 = (UInt8*)lhs; + UInt8* rhs8 = (UInt8*)rhs; + UInt8* mask8 = (UInt8*)mask; + + for (UInt32 i = 0; i < size; i++) + if ((lhs8[i] & mask8[i]) != (rhs8[i] & mask8[i])) + return false; + + return true; +} + +class Vector3 +{ +public: + Vector3() { } + Vector3(const Vector3& in) { x = in.x; y = in.y; z = in.z; } + Vector3(float inX, float inY, float inZ) { x = inX; y = inY; z = inZ; } + ~Vector3() { } + + void Set(float inX, float inY, float inZ) { x = inX; y = inY; z = inZ; } + void Get(float* outX, float* outY, float* outZ) { *outX = x; *outY = y; *outZ = z; } + + void Normalize(void) { float mag = Magnitude(); x /= mag; y /= mag; z /= mag; } + float Magnitude(void) { return sqrt(x * x + y * y + z * z); } + + void Scale(float scale) { x *= scale; y *= scale; z *= scale; } + + void SwapBytes(void) { SwapFloat(&x); SwapFloat(&y); SwapFloat(&z); } + + Vector3& operator+=(const Vector3& rhs) { x += rhs.x; y += rhs.y; z += rhs.z; return *this; } + Vector3& operator-=(const Vector3& rhs) { x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this; } + Vector3& operator*=(const Vector3& rhs) { x *= rhs.x; y *= rhs.y; z *= rhs.z; return *this; } + Vector3& operator/=(const Vector3& rhs) { x /= rhs.x; y /= rhs.y; z /= rhs.z; return *this; } + + union + { + struct + { + float x, y, z; + }; + float d[3]; + }; +}; + +inline Vector3 operator+(const Vector3& lhs, const Vector3& rhs) +{ + return Vector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); +} + +inline Vector3 operator-(const Vector3& lhs, const Vector3& rhs) +{ + return Vector3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); +} + +inline Vector3 operator*(const Vector3& lhs, const Vector3& rhs) +{ + return Vector3(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z); +} + +inline Vector3 operator/(const Vector3& lhs, const Vector3& rhs) +{ + return Vector3(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z); +} diff --git a/common/PluginAPI.h b/common/PluginAPI.h new file mode 100644 index 0000000..9c740fa --- /dev/null +++ b/common/PluginAPI.h @@ -0,0 +1,48 @@ +#pragma once + +struct CommandInfo; +struct ParamInfo; +class TESObjectREFR; +class Script; +struct ScriptEventList; +struct PluginInfo; + +typedef UInt32 PluginHandle; // treat this as an opaque type + +typedef UInt32 CommandReturnType; + +struct FOSEInterface +{ + UInt32 foseVersion; + UInt32 runtimeVersion; + UInt32 editorVersion; + UInt32 isEditor; + bool (*RegisterCommand)(CommandInfo* info); // returns true for success, false for failure + void (*SetOpcodeBase)(UInt32 opcode); + void* (*QueryInterface)(UInt32 id); + + // call during your Query or Load functions to get a PluginHandle uniquely identifying your plugin + // invalid if called at any other time, so call it once and save the result + PluginHandle(*GetPluginHandle)(void); + + // CommandReturnType enum defined in CommandTable.h + // does the same as RegisterCommand but includes return type; *required* for commands returning arrays + bool (*RegisterTypedCommand)(CommandInfo* info, CommandReturnType retnType); + // returns a full path the the game directory + const char* (*GetRuntimeDirectory)(); + + // Allows checking for nogore edition + UInt32 isNogore; +}; + +struct PluginInfo +{ + enum + { + kInfoVersion = 1 + }; + + UInt32 infoVersion; + const char* name; + UInt32 version; +}; \ No newline at end of file diff --git a/common/SafeWrite.cpp b/common/SafeWrite.cpp new file mode 100644 index 0000000..3b03747 --- /dev/null +++ b/common/SafeWrite.cpp @@ -0,0 +1,99 @@ +#include "SafeWrite.h" + +void SafeWrite8(UInt32 addr, UInt32 data) +{ + UInt32 oldProtect; + + VirtualProtect((void *)addr, 4, PAGE_EXECUTE_READWRITE, &oldProtect); + *((UInt8 *)addr) = data; + VirtualProtect((void *)addr, 4, oldProtect, &oldProtect); +} + +void SafeWrite16(UInt32 addr, UInt32 data) +{ + UInt32 oldProtect; + + VirtualProtect((void *)addr, 4, PAGE_EXECUTE_READWRITE, &oldProtect); + *((UInt16 *)addr) = data; + VirtualProtect((void *)addr, 4, oldProtect, &oldProtect); +} + +void SafeWrite32(UInt32 addr, UInt32 data) +{ + UInt32 oldProtect; + + VirtualProtect((void *)addr, 4, PAGE_EXECUTE_READWRITE, &oldProtect); + *((UInt32 *)addr) = data; + VirtualProtect((void *)addr, 4, oldProtect, &oldProtect); +} + +void SafeWriteBuf(UInt32 addr, void *data, UInt32 len) +{ + UInt32 oldProtect; + + VirtualProtect((void *)addr, len, PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy((void *)addr, data, len); + VirtualProtect((void *)addr, len, oldProtect, &oldProtect); +} + +void WriteRelJump(UInt32 jumpSrc, UInt32 jumpTgt) +{ + // jmp rel32 + SafeWrite8(jumpSrc, 0xE9); + SafeWrite32(jumpSrc + 1, jumpTgt - jumpSrc - 1 - 4); +} + +void WriteRelCall(UInt32 jumpSrc, UInt32 jumpTgt) +{ + // call rel32 + SafeWrite8(jumpSrc, 0xE8); + SafeWrite32(jumpSrc + 1, jumpTgt - jumpSrc - 1 - 4); +} + +void ReplaceCall(UInt32 jumpSrc, UInt32 jumpTgt) +{ + SafeWrite32(jumpSrc + 1, jumpTgt - jumpSrc - 1 - 4); +} + +void ReplaceVirtualFunc(UInt32 jumpSrc, void* jumpTgt) { + SafeWrite32(jumpSrc, (UInt32)jumpTgt); +} + +void WriteRelJnz(UInt32 jumpSrc, UInt32 jumpTgt) +{ + // jnz rel32 + SafeWrite16(jumpSrc, 0x850F); + SafeWrite32(jumpSrc + 2, jumpTgt - jumpSrc - 2 - 4); +} + +void WriteRelJle(UInt32 jumpSrc, UInt32 jumpTgt) +{ + // jle rel32 + SafeWrite16(jumpSrc, 0x8E0F); + SafeWrite32(jumpSrc + 2, jumpTgt - jumpSrc - 2 - 4); +} + +void PatchMemoryNop(ULONG_PTR Address, SIZE_T Size) +{ + DWORD d = 0; + VirtualProtect((LPVOID)Address, Size, PAGE_EXECUTE_READWRITE, &d); + + for (SIZE_T i = 0; i < Size; i++) + *(volatile BYTE*)(Address + i) = 0x90; //0x90 == opcode for NOP + + VirtualProtect((LPVOID)Address, Size, d, &d); + + FlushInstructionCache(GetCurrentProcess(), (LPVOID)Address, Size); +} + +void PatchMemoryNopRange(ULONG_PTR StartAddress, ULONG_PTR EndAddress) { + PatchMemoryNop(StartAddress, EndAddress - StartAddress); +} + +void WriteRelLibCall(UInt32 jumpSrc, UInt32 jumpTgt) +{ + // call rel32 + SafeWrite8(jumpSrc, 0xE8); + SafeWrite32(jumpSrc + 1, jumpTgt - jumpSrc - 1 - 4); + SafeWrite8(jumpSrc + 5, 0x90); +} \ No newline at end of file diff --git a/common/SafeWrite.h b/common/SafeWrite.h new file mode 100644 index 0000000..e24ab1a --- /dev/null +++ b/common/SafeWrite.h @@ -0,0 +1,41 @@ +#pragma once +#include "common/IPrefix.h" + +DECLSPEC_NOINLINE void SafeWrite8(UInt32 addr, UInt32 data); +DECLSPEC_NOINLINE void SafeWrite16(UInt32 addr, UInt32 data); +DECLSPEC_NOINLINE void SafeWrite32(UInt32 addr, UInt32 data); +DECLSPEC_NOINLINE void SafeWriteBuf(UInt32 addr, void * data, UInt32 len); + +// 5 bytes +DECLSPEC_NOINLINE void WriteRelJump(UInt32 jumpSrc, UInt32 jumpTgt); +DECLSPEC_NOINLINE void WriteRelCall(UInt32 jumpSrc, UInt32 jumpTgt); + + +// 6 bytes +DECLSPEC_NOINLINE void WriteRelJnz(UInt32 jumpSrc, UInt32 jumpTgt); +DECLSPEC_NOINLINE void WriteRelJle(UInt32 jumpSrc, UInt32 jumpTgt); + +DECLSPEC_NOINLINE void PatchMemoryNop(ULONG_PTR Address, SIZE_T Size); +void PatchMemoryNopRange(ULONG_PTR StartAddress, ULONG_PTR EndAddress); + +template +DECLSPEC_NOINLINE void WriteRelCall(UInt32 jumpSrc, T jumpTgt) +{ + WriteRelCall(jumpSrc, (UInt32)jumpTgt); +} + +template +DECLSPEC_NOINLINE void WriteRelJump(UInt32 jumpSrc, T jumpTgt) +{ + WriteRelJump(jumpSrc, (UInt32)jumpTgt); +} + +DECLSPEC_NOINLINE void ReplaceCall(UInt32 jumpSrc, UInt32 jumpTgt); + +template +DECLSPEC_NOINLINE void ReplaceCall(UInt32 jumpSrc, T jumpTgt) +{ + ReplaceCall(jumpSrc, (UInt32)jumpTgt); +} + +void ReplaceVirtualFunc(UInt32 jumpSrc, void* jumpTgt); \ No newline at end of file diff --git a/common/Utilities.cpp b/common/Utilities.cpp new file mode 100644 index 0000000..0e56daf --- /dev/null +++ b/common/Utilities.cpp @@ -0,0 +1,1162 @@ +#include "Utilities.h" +#include "SafeWrite.h" +#include +#include +#include +#include +#include "containers.h" +#include "GameData.h" +#include "Hooks_Gameplay.h" +#include "LambdaManager.h" +#include "PluginAPI.h" +#include "PluginManager.h" + +#if RUNTIME +#include "GameAPI.h" +#include "GameForms.h" +#endif + +void DumpClass(void * theClassPtr, UInt32 nIntsToDump) +{ + _MESSAGE("DumpClass:"); + UInt32* basePtr = (UInt32*)theClassPtr; + + gLog.Indent(); + + if (!theClassPtr) return; + for (UInt32 ix = 0; ix < nIntsToDump; ix++ ) { + UInt32* curPtr = basePtr+ix; + const char* curPtrName = NULL; + UInt32 otherPtr = 0; + float otherFloat = 0.0; + const char* otherPtrName = NULL; + if (curPtr) { + curPtrName = GetObjectClassName((void*)curPtr); + + __try + { + otherPtr = *curPtr; + otherFloat = *(float*)(curPtr); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + // + } + + if (otherPtr) { + otherPtrName = GetObjectClassName((void*)otherPtr); + } + } + + _MESSAGE("%3d +%03X ptr: 0x%08X: %32s *ptr: 0x%08x | %f: %32s", ix, ix*4, curPtr, curPtrName, otherPtr, otherFloat, otherPtrName); + } + + gLog.Outdent(); +} + +#pragma warning (push) +#pragma warning (disable : 4200) +struct RTTIType +{ + void * typeInfo; + UInt32 pad; + char name[0]; +}; + +struct RTTILocator +{ + UInt32 sig, offset, cdOffset; + RTTIType * type; +}; +#pragma warning (pop) + +// use the RTTI information to return an object's class name +const char * GetObjectClassName(void * objBase) +{ + const char * result = ""; + + __try + { + void ** obj = (void **)objBase; + RTTILocator ** vtbl = (RTTILocator **)obj[0]; + RTTILocator * rtti = vtbl[-1]; + RTTIType * type = rtti->type; + + // starts with ,? + if((type->name[0] == '.') && (type->name[1] == '?')) + { + // is at most 100 chars long + for(UInt32 i = 0; i < 100; i++) + { + if(type->name[i] == 0) + { + // remove the .?AV + result = type->name + 4; + break; + } + } + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + // return the default + } + + return result; +} + +const std::string & GetFalloutDirectory(void) +{ + static std::string s_falloutDirectory; + + if(s_falloutDirectory.empty()) + { + // can't determine how many bytes we'll need, hope it's not more than MAX_PATH + char falloutPathBuf[MAX_PATH]; + UInt32 falloutPathLength = GetModuleFileName(GetModuleHandle(NULL), falloutPathBuf, sizeof(falloutPathBuf)); + + if(falloutPathLength && (falloutPathLength < sizeof(falloutPathBuf))) + { + std::string falloutPath(falloutPathBuf, falloutPathLength); + + // truncate at last slash + std::string::size_type lastSlash = falloutPath.rfind('\\'); + if(lastSlash != std::string::npos) // if we don't find a slash something is VERY WRONG + { + s_falloutDirectory = falloutPath.substr(0, lastSlash + 1); + + _DMESSAGE("fallout root = %s", s_falloutDirectory.c_str()); + } + else + { + _WARNING("no slash in fallout path? (%s)", falloutPath.c_str()); + } + } + else + { + _WARNING("couldn't find fallout path (len = %d, err = %08X)", falloutPathLength, GetLastError()); + } + } + + return s_falloutDirectory; +} + +static const std::string & GetNVSEConfigPath(void) +{ + static std::string s_configPath; + + if(s_configPath.empty()) + { + std::string falloutPath = GetFalloutDirectory(); + if(!falloutPath.empty()) + { + s_configPath = falloutPath + "Data\\NVSE\\nvse_config.ini"; + + _MESSAGE("config path = %s", s_configPath.c_str()); + } + } + + return s_configPath; +} + +std::string GetNVSEConfigOption(const char * section, const char * key) +{ + std::string result; + + const std::string & configPath = GetNVSEConfigPath(); + if(!configPath.empty()) + { + char resultBuf[256]; + resultBuf[0] = 0; + + UInt32 resultLen = GetPrivateProfileString(section, key, NULL, resultBuf, 255, configPath.c_str()); + + result = resultBuf; + } + + return result; +} + +bool GetNVSEConfigOption_UInt32(const char * section, const char * key, UInt32 * dataOut) +{ + std::string data = GetNVSEConfigOption(section, key); + if(data.empty()) + return false; + + return (sscanf(data.c_str(), "%lu", dataOut) == 1); +} + +namespace MersenneTwister +{ + +/* + A C-program for MT19937, with initialization improved 2002/1/26. + Coded by Takuji Nishimura and Makoto Matsumoto. + + Before using, initialize the state by using init_genrand(seed) + or init_by_array(init_key, key_length). + + Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Any feedback is very welcome. + http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) +*/ + +/* Period parameters */ +#define N 624 +#define M 397 +#define MATRIX_A 0x9908b0dfUL /* constant vector a */ +#define UPPER_MASK 0x80000000UL /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffffUL /* least significant r bits */ + +static unsigned long mt[N]; /* the array for the state vector */ +static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */ + +/* initializes mt[N] with a seed */ +void init_genrand(unsigned long s) +{ + mt[0]= s & 0xffffffffUL; + for (mti=1; mti> 30)) + mti); + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + mt[mti] &= 0xffffffffUL; + /* for >32 bit machines */ + } +} + +/* initialize by an array with array-length */ +/* init_key is the array for initializing keys */ +/* key_length is its length */ +/* slight change for C++, 2004/2/26 */ +void init_by_array(unsigned long init_key[], int key_length) +{ + int i, j, k; + init_genrand(19650218UL); + i=1; j=0; + k = (N>key_length ? N : key_length); + for (; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL)) + + init_key[j] + j; /* non linear */ + mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ + i++; j++; + if (i>=N) { mt[0] = mt[N-1]; i=1; } + if (j>=key_length) j=0; + } + for (k=N-1; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL)) + - i; /* non linear */ + mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ + i++; + if (i>=N) { mt[0] = mt[N-1]; i=1; } + } + + mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ +} + +/* generates a random number on [0,0xffffffff]-interval */ +unsigned long genrand_int32(void) +{ + unsigned long y; + static unsigned long mag01[2]={0x0UL, MATRIX_A}; + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + if (mti >= N) { /* generate N words at one time */ + int kk; + + if (mti == N+1) /* if init_genrand() has not been called, */ + init_genrand(5489UL); /* a default initial seed is used */ + + for (kk=0;kk> 1) ^ mag01[y & 0x1UL]; + } + for (;kk> 1) ^ mag01[y & 0x1UL]; + } + y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; + + mti = 0; + } + + y = mt[mti++]; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return y; +} + +/* generates a random number on [0,0x7fffffff]-interval */ +long genrand_int31(void) +{ + return (long)(genrand_int32()>>1); +} + +/* generates a random number on [0,1]-real-interval */ +double genrand_real1(void) +{ + return genrand_int32()*(1.0/4294967295.0); + /* divided by 2^32-1 */ +} + +/* generates a random number on [0,1)-real-interval */ +double genrand_real2(void) +{ + return genrand_int32()*(1.0/4294967296.0); + /* divided by 2^32 */ +} + +/* generates a random number on (0,1)-real-interval */ +double genrand_real3(void) +{ + return (((double)genrand_int32()) + 0.5)*(1.0/4294967296.0); + /* divided by 2^32 */ +} + +/* generates a random number on [0,1) with 53-bit resolution*/ +double genrand_res53(void) +{ + unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6; + return(a*67108864.0+b)*(1.0/9007199254740992.0); +} +/* These real versions are due to Isaku Wada, 2002/01/09 added */ + +#undef N +#undef M +#undef MATRIX_A +#undef UPPER_MASK +#undef LOWER_MASK + +}; + +Tokenizer::Tokenizer(const char* src, const char* delims) +: m_offset(0), m_delims(delims), m_data(src) +{ + // +} + +UInt32 Tokenizer::NextToken(std::string& outStr) +{ + if (m_offset == m_data.length()) + return -1; + + size_t const start = m_data.find_first_not_of(m_delims, m_offset); + if (start != -1) + { + size_t end = m_data.find_first_of(m_delims, start); + if (end == -1) + end = m_data.length(); + + m_offset = end; + outStr = m_data.substr(start, end - start); + return start; + } + + return -1; +} + +std::string Tokenizer::ToNewLine() +{ + if (m_offset == m_data.length()) + return ""; + + size_t const start = m_data.find_first_not_of(m_delims, m_offset); + if (start != -1) + { + size_t end = m_data.find_first_of('\n', start); + if (end == -1) + end = m_data.length(); + + m_offset = end; + return m_data.substr(start, end - start); + } + + return ""; +} + +UInt32 Tokenizer::PrevToken(std::string& outStr) +{ + if (m_offset == 0) + return -1; + + size_t searchStart = m_data.find_last_of(m_delims, m_offset - 1); + if (searchStart == -1) + return -1; + + size_t end = m_data.find_last_not_of(m_delims, searchStart); + if (end == -1) + return -1; + + size_t start = m_data.find_last_of(m_delims, end); // okay if start == -1 here + + m_offset = end + 1; + outStr = m_data.substr(start + 1, end - start); + return start + 1; +} + +ScriptTokenizer::ScriptTokenizer(std::string_view scriptText) : m_scriptText(scriptText) +{ + // +} + +bool ScriptTokenizer::TryLoadNextLine() +{ + m_loadedLineTokens = {}; + m_tokenOffset = 0; + if (m_scriptOffset == m_scriptText.length()) + return false; + +#if _DEBUG + ASSERT(m_scriptOffset <= m_scriptText.length()); +#endif + + auto GetLineEndPos = [this](size_t startPos) -> size_t + { + auto result = m_scriptText.find_first_of("\n\r", startPos); + if (result == std::string_view::npos) + result = m_scriptText.length(); + return result; + }; + + // Finds the pos of the the start of a valid token on a new line, if any. + auto GetNextLineStartPos = [this](size_t startPos) -> size_t + { + auto result = m_scriptText.find_first_of("\n\r", startPos); + if (result == std::string_view::npos) + result = m_scriptText.length(); + else + { + // Skip over consecutive empty lines. + result = m_scriptText.find_first_not_of(" \t\n\r", result + 1); + if (result == std::string_view::npos) + result = m_scriptText.length(); + } + return result; + }; + + // Ignore spaces and newlines at the start - find the start of a REAL line. + // Line pos is relative to the entire script text. + if (auto linePos = m_scriptText.find_first_not_of(" \t\n\r", m_scriptOffset); + linePos != std::string_view::npos) + { + if (m_inMultilineComment) + { + auto const multilineCommentEndStartPos = m_scriptText.find("*/", linePos); + if (multilineCommentEndStartPos == std::string_view::npos) + { + m_scriptOffset = m_scriptText.size(); + return false; // unable to find an end to the multiline comment (xEdit shenanigans?) + } + m_inMultilineComment = false; + if (multilineCommentEndStartPos + 2 == m_scriptText.length()) + { + m_scriptOffset = m_scriptText.size(); + return false; + } + + // If there was a ';' comment inside of the multiline comment on the line where it ended, + // .. it will still comment out the rest of the line. + if (std::string_view const insideOfMultilineCommentOnThisLine(&m_scriptText.at(linePos), multilineCommentEndStartPos - linePos); + insideOfMultilineCommentOnThisLine.find_first_of(';') != std::string_view::npos) + { + // Line is commented out; ignore it and try loading the next one. + m_scriptOffset = GetNextLineStartPos(multilineCommentEndStartPos + 2); + return TryLoadNextLine(); + } + //else, there might be something left in this line. + linePos = multilineCommentEndStartPos + 2; + } + + // If line is empty, skip to a new line. + linePos = m_scriptText.find_first_not_of(" \t\n\r", linePos); + if (linePos == std::string_view::npos) + { + m_scriptOffset = m_scriptText.size(); + return false; // rest of file is empty + } + + // Handle comments and try loading tokens that are on the same line. + for (auto const lineEndPos = GetLineEndPos(linePos); true; ) + { + // Check if the rest of the line is commented out via ';'. + if (m_scriptText.at(linePos) == ';') + { + m_scriptOffset = GetNextLineStartPos(linePos + 1); + if (m_loadedLineTokens.empty()) + { + // Line is fully commented out; ignore it and try loading the next one. + return TryLoadNextLine(); + } + //else, there were some tokens in the line before the comment. + return true; + } + + // Handle possible back-to-back multiline comments on the same line. + // Ex: "/* *//* */ /* */ Finally,_I_Am_A_Real_Token. /* \n */\n" + for (std::string_view lineView(&m_scriptText.at(linePos), lineEndPos - linePos); + lineView.size() >= 2 && lineView.front() == '/' && lineView.at(1) == '*'; + lineView = { &m_scriptText.at(linePos), lineEndPos - linePos }) + { + auto HandleCommentSpanningMultipleLines = [this, lineEndPos]() -> bool + { + // Line ended; assume multiline comment that spans multiple lines. + m_inMultilineComment = true; + m_scriptOffset = m_scriptText.find_first_not_of(" \t\n\r", lineEndPos); + if (m_scriptOffset == std::string_view::npos) + m_scriptOffset = m_scriptText.size(); + if (!m_loadedLineTokens.empty()) + return true; + return TryLoadNextLine(); + }; + + // ignore the "/*" chars + if (linePos + 2 == lineEndPos) + return HandleCommentSpanningMultipleLines(); + + // Check if the multiline comment ends on this line. + if (auto endMultilineCommentStartPos = lineView.find("*/"); + endMultilineCommentStartPos == std::string_view::npos) + { + return HandleCommentSpanningMultipleLines(); + } + else + { + // else, multiline comment ends in this line. + // There might be tokens left in this line; if not, skip to the next if this line was empty. + + // Make pos relative to the entire script text. + endMultilineCommentStartPos += linePos; + if (endMultilineCommentStartPos + 2 == m_scriptText.size()) + { + m_scriptOffset = m_scriptText.size(); + return false; + } + + // If there was a ';' comment inside of the multiline comment on the line where it ended, + // .. it will still comment out the rest of the line. + if (std::string_view const insideOfMultilineComment(&m_scriptText.at(linePos + 2), endMultilineCommentStartPos - linePos); + insideOfMultilineComment.find_first_of(';') != std::string_view::npos) + { + // Line is commented out; IF we didn't get tokens, ignore it and try loading the next one. + m_scriptOffset = GetNextLineStartPos(endMultilineCommentStartPos + 2); + if (!m_loadedLineTokens.empty()) + return true; + return TryLoadNextLine(); + } + + // Handle multiline comment that ends in-line, but is followed by end-of-line. + m_scriptOffset = m_scriptText.find_first_not_of(" \t", endMultilineCommentStartPos + 2); + if (m_scriptOffset == std::string_view::npos) + { + m_scriptOffset = m_scriptText.size(); + return !m_loadedLineTokens.empty(); + } + if (m_scriptOffset == lineEndPos) + { + if (!m_loadedLineTokens.empty()) + return true; + // Line is commented out; ignore it and try loading the next one. + return TryLoadNextLine(); + } + + // Else, line goes on. + linePos = m_scriptText.find_first_not_of(" \t\n\r", endMultilineCommentStartPos + 2); + if (linePos == std::string_view::npos) + { + m_scriptOffset = m_scriptText.size(); + return !m_loadedLineTokens.empty(); + } + } + } + + // Handle ';' comment right after 1-line /* */ comment(s) + if (m_scriptText.at(linePos) == ';') + { + m_scriptOffset = GetNextLineStartPos(linePos + 1); + if (m_loadedLineTokens.empty()) + { + // Line is fully commented out; ignore it and try loading the next one. + return TryLoadNextLine(); + } + //else, there were some tokens in the line before the comment. + return true; + } + + // Line pos should now point to the start of a token. + // Get the post-the-end character position for the token. + size_t endOfTokenPos; + if (m_scriptText.at(linePos) == '"') + { + // Get the full string as a single token. + endOfTokenPos = m_scriptText.find_first_of('"', linePos + 1); + if (endOfTokenPos == std::string_view::npos) + endOfTokenPos = m_scriptText.size(); + else + ++endOfTokenPos; // include '"' char at the end. + } + else + { + endOfTokenPos = m_scriptText.find_first_of(" \t\n\r", linePos); + if (endOfTokenPos == std::string_view::npos) + endOfTokenPos = m_scriptText.size(); + } + + auto tokenView = m_scriptText.substr(linePos, endOfTokenPos - linePos); + + // trim comments off of the end of the token + if (tokenView.back() == ';') + { + --endOfTokenPos; + } + else if (tokenView.size() > 2 && + tokenView.at(tokenView.size() - 2) == '/' && tokenView.back() == '*') + { + endOfTokenPos -= 2; + } + tokenView = m_scriptText.substr(linePos, endOfTokenPos - linePos); + + m_loadedLineTokens.push_back(tokenView); + if (endOfTokenPos == m_scriptText.size()) + break; + + linePos = m_scriptText.find_first_not_of(" \t", endOfTokenPos); + if (linePos == std::string_view::npos) + { + linePos = m_scriptText.size(); + break; + } + if (linePos == lineEndPos) + break; + } + + m_scriptOffset = GetNextLineStartPos(linePos); + return !m_loadedLineTokens.empty(); + } + // else, rest of script is just spaces + m_scriptOffset = m_scriptText.size(); + return false; +} + +std::string_view ScriptTokenizer::GetNextLineToken() +{ + if (m_loadedLineTokens.empty() || m_tokenOffset >= m_loadedLineTokens.size()) + return ""; + return m_loadedLineTokens.at(m_tokenOffset++); +} + +std::string_view ScriptTokenizer::GetLineText() +{ + if (!m_loadedLineTokens.empty()) + { + if (m_loadedLineTokens.size() > 1) + { + const char* startAddr = m_loadedLineTokens[0].data(); + std::string_view lastToken = m_loadedLineTokens[m_loadedLineTokens.size() - 1]; + // assume lastToken isn't empty + const char* endAddr = &lastToken.at(lastToken.size() - 1); + + size_t count = endAddr - startAddr + 1; + size_t startPos = startAddr - m_scriptText.data(); + return m_scriptText.substr(startPos, count); + } + else // only 1 token + { + return m_loadedLineTokens[0]; + } + } + return ""; +} + +#if RUNTIME + +const char GetSeparatorChar(Script * script) +{ + if(IsConsoleMode()) + { + if(script && script->GetModIndex() != 0xFF) + return '|'; + else + return '@'; + } + else + return '|'; +} + +const char * GetSeparatorChars(Script * script) +{ + if(IsConsoleMode()) + { + if(script && script->GetModIndex() != 0xFF) + return "|"; + else + return "@"; + } + else + return "|"; +} + +void Console_Print_Long(const std::string& str) +{ + UInt32 numLines = str.length() / 500; + for (UInt32 i = 0; i < numLines; i++) + Console_Print("%s ...", str.substr(i*500, 500).c_str()); + + Console_Print("%s", str.substr(numLines*500, str.length() - numLines*500).c_str()); +} + +void Console_Print_Str(const std::string& str) +{ + if (str.size() < 512) + Console_Print("%s", str.c_str()); + else + Console_Print_Long(str); +} + +#endif + +struct ControlName +{ + UInt32 unk0; + const char * name; + UInt32 unkC; +}; + +ControlName ** g_keyNames = (ControlName **)0x011D52F0; +ControlName ** g_mouseButtonNames = (ControlName **)0x011D5240; +ControlName ** g_joystickNames = (ControlName **)0x011D51B0; + +const char * GetDXDescription(UInt32 keycode) +{ + const char * keyName = ""; + + if(keycode <= 220) + { + if(g_keyNames[keycode]) + keyName = g_keyNames[keycode]->name; + } + else if(255 <= keycode && keycode <= 263) + { + if(keycode == 255) + keycode = 256; + if(g_mouseButtonNames[keycode - 256]) + keyName = g_mouseButtonNames[keycode - 256]->name; + } + else if (keycode == 264) + keyName = "WheelUp"; + else if (keycode == 265) + keyName = "WheelDown"; + + return keyName; +} + +bool ci_equal(char ch1, char ch2) +{ + return game_tolower((unsigned char)ch1) == game_tolower((unsigned char)ch2); +} + +bool ci_less(const char* lh, const char* rh) +{ + ASSERT(lh && rh); + while (*lh && *rh) { + char l = game_toupper(*lh); + char r = game_toupper(*rh); + if (l < r) { + return true; + } + else if (l > r) { + return false; + } + lh++; + rh++; + } + + return game_toupper(*lh) < game_toupper(*rh); +} + +void MakeUpper(std::string& str) +{ + std::transform(str.begin(), str.end(), str.begin(), game_toupper); +} + +void MakeLower(std::string& str) +{ + std::transform(str.begin(), str.end(), str.begin(), game_tolower); +} + +#if RUNTIME + +char* CopyCString(const char* src) +{ + UInt32 size = src ? strlen(src) : 0; + char* result = (char*)FormHeap_Allocate(size+1); + result[size] = 0; + if (size) { + strcpy_s(result, size+1, src); + } + + return result; +} + +#endif + +#pragma warning(push) +#pragma warning(disable: 4996) // warning about std::transform() + +void MakeUpper(char* str) +{ + if (str) { + UInt32 len = strlen(str); + std::transform(str, str + len, str, game_toupper); + } +} + +void MakeLower(char* str) +{ + if (str) { + UInt32 len = strlen(str); + std::transform(str, str + len, str, game_tolower); + } +} + +#pragma warning(pop) + +// ErrOutput +ErrOutput::ErrOutput(_ShowError errorFunc, _ShowWarning warningFunc) +{ + ShowWarning = warningFunc; + ShowError = errorFunc; +} + +void ErrOutput::vShow(ErrOutput::Message& msg, va_list args) +{ + char msgText[0x400]; + vsprintf_s(msgText, sizeof(msgText), msg.fmt, args); + if (msg.bCanDisable) + { + if (!msg.bDisabled) + if (ShowWarning(msgText)) + msg.bDisabled = true; + } + else + ShowError(msgText); +} + +void ErrOutput::Show(ErrOutput::Message msg, ...) +{ + va_list args; + va_start(args, msg); + + vShow(msg, args); +} + +void ErrOutput::Show(const char* msg, ...) +{ + va_list args; + va_start(args, msg); + + vShow(msg, args); +} + +void ErrOutput::vShow(const char* msg, va_list args) +{ + Message tempMsg; + tempMsg.fmt = msg; + tempMsg.bCanDisable = false; + tempMsg.bDisabled = false; + + vShow(tempMsg, args); +} + +void ShowErrorMessageBox(const char* message) +{ + int msgboxID = MessageBox( + NULL, + message, + "Error", + MB_ICONWARNING | MB_OK + ); +} + +#if RUNTIME + +const char* GetModName(TESForm* form) +{ + if (!form) + return "Unknown or deleted script"; + const char* modName = IS_ID(form, Script) ? "In-game console" : "Dynamic form"; + if (form->mods.Head() && form->mods.Head()->data) + return form->mods.Head()->Data()->name; + if (form->GetModIndex() != 0xFF) + { + modName = DataHandler::Get()->GetNthModName(form->GetModIndex()); + if (!modName || !modName[0]) + modName = "Unknown"; + } + return modName; +} +#if NVSE_CORE +UnorderedSet g_warnedScripts; + +void ShowRuntimeError(Script* script, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + + char errorMsg[0x800]; + vsprintf_s(errorMsg, sizeof(errorMsg), fmt, args); + + char errorHeader[0x900]; + const auto* modName = GetModName(script); + + const auto* scriptName = script ? script->GetName() : nullptr; // JohnnyGuitarNVSE allows this + auto refId = script ? script->refID : 0; + const auto modIdx = script ? script->GetModIndex() : 0; + if (script && LambdaManager::IsScriptLambda(script)) + { + Script* parentScript; + if (auto* parentEventList = LambdaManager::GetParentEventList(script); + parentEventList && ((parentScript = parentEventList->m_script))) + { + refId = parentScript->refID; + scriptName = parentScript->GetName(); + } + } + if (scriptName && strlen(scriptName) != 0) + { + sprintf_s(errorHeader, sizeof(errorHeader), "Error in script %08X (%s) in mod %s\n%s", refId, scriptName, modName, errorMsg); + } + else + { + sprintf_s(errorHeader, sizeof(errorHeader), "Error in script %08X in mod %s\n%s", refId, modName, errorMsg); + } + + if (g_warnScriptErrors && g_myMods.contains(modIdx) && g_warnedScripts.Insert(refId)) + { + char message[512]; + snprintf(message, sizeof(message), "%s: Script error (see console print)", GetModName(script)); + if (!IsConsoleMode()) + QueueUIMessage(message, 0, reinterpret_cast(0x1049638), nullptr, 2.5F, false); + } + + if (strlen(errorHeader) < 512) + Console_Print("%s", errorHeader); + else + Console_Print_Long(errorHeader); + _MESSAGE("%s", errorHeader); + + PluginManager::Dispatch_Message(0, NVSEMessagingInterface::kMessage_RuntimeScriptError, errorMsg, 4, NULL); + + va_end(args); +} +#endif +#endif + +std::string FormatString(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + + char msg[0x800]; + vsprintf_s(msg, 0x800, fmt, args); + return msg; +} + +std::vector GetCallStack(int i) +{ + std::vector vecTrace(i, nullptr); + CaptureStackBackTrace(1, i, reinterpret_cast(vecTrace.data()), nullptr); + return vecTrace; +} + +bool FindStringCI(const std::string& strHaystack, const std::string& strNeedle) +{ + auto it = std::search( + strHaystack.begin(), strHaystack.end(), + strNeedle.begin(), strNeedle.end(), + [](char ch1, char ch2) { return game_toupper(ch1) == game_toupper(ch2); } + ); + return (it != strHaystack.end()); +} + +void ReplaceAll(std::string &str, const std::string& from, const std::string& to) +{ + size_t startPos = 0; + while((startPos = str.find(from, startPos)) != std::string::npos) + { + str.replace(startPos, from.length(), to); + startPos += to.length(); // Handles case where 'to' is a substring of 'from' + } + +} + +void GeckExtenderMessageLog(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + + auto* window = FindWindow("RTEDITLOG", nullptr); + if (!window) + { + _MESSAGE("Failed to find GECK Extender Message Log window"); + return; + } + + char buffer[0x400]; + vsprintf_s(buffer, 0x400, fmt, args); + strcat_s(buffer, "\n"); + + SendMessage(window, 0x8004, 0, reinterpret_cast(buffer)); +} + +bool Cmd_Default_Execute(COMMAND_ARGS) +{ + return true; +} + +bool Cmd_Default_Eval(COMMAND_ARGS_EVAL) +{ + return true; +} + +void ToWChar(wchar_t* ws, const char* c) +{ + swprintf(ws, 100, L"%hs", c); +} + +bool IsProcessRunning(const char* executableName) +{ + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + + const auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + if (!Process32First(snapshot, &entry)) { + CloseHandle(snapshot); + return false; + } + + do { + if (!_stricmp(entry.szExeFile, executableName)) { + CloseHandle(snapshot); + return true; + } + } while (Process32Next(snapshot, &entry)); + + CloseHandle(snapshot); + return false; +} +#if NVSE_CORE && RUNTIME +void DisplayMessage(const char* msg) +{ + ShowMessageBox(msg, 0, 0, ShowMessageBox_Callback, 0, 0x17, 0.0, 0.0, "Ok", 0); +} +#endif + +std::string GetCurPath() +{ + char buffer[MAX_PATH] = { 0 }; + GetModuleFileName(NULL, buffer, MAX_PATH); + std::string::size_type pos = std::string(buffer).find_last_of("\\/"); + return std::string(buffer).substr(0, pos); +} + +bool ValidString(const char* str) +{ + return str && strlen(str); +} + +#if _DEBUG +// debugger can't call unused member functions +const char* GetFormName(TESForm* form) +{ + return form ? form->GetName() : ""; +} + +const char* GetFormName(UInt32 formId) +{ + return GetFormName(LookupFormByID(formId)); +} + +#endif + +std::string& ToLower(std::string&& data) +{ + ra::transform(data, data.begin(), [](const unsigned char c) { return game_tolower(c); }); + return data; +} + +std::string& StripSpace(std::string&& data) +{ + std::erase_if(data, isspace); + return data; +} + +bool StartsWith(std::string left, std::string right) +{ + return ToLower(std::move(left)).starts_with(ToLower(std::move(right))); +} + +std::vector SplitString(std::string s, std::string delimiter) +{ + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::string token; + std::vector res; + + while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + res.push_back(token); + } + + res.push_back(s.substr(pos_start)); + return res; +} + +UInt8* GetParentBasePtr(void* addressOfReturnAddress, bool lambda) +{ + auto* basePtr = static_cast(addressOfReturnAddress) - 4; +#if _DEBUG + if (lambda) // in debug mode, lambdas are wrapped inside a closure wrapper function, so one more step needed + basePtr = *reinterpret_cast(basePtr); +#endif + return *reinterpret_cast(basePtr); +} \ No newline at end of file diff --git a/common/Utilities.h b/common/Utilities.h new file mode 100644 index 0000000..50033b3 --- /dev/null +++ b/common/Utilities.h @@ -0,0 +1,351 @@ +#pragma once +#include +#include +#include +#include +#include + +class Script; + +void DumpClass(void * theClassPtr, UInt32 nIntsToDump = 512); +const char * GetObjectClassName(void * obj); +const std::string & GetFalloutDirectory(void); +std::string GetNVSEConfigOption(const char * section, const char * key); +bool GetNVSEConfigOption_UInt32(const char * section, const char * key, UInt32 * dataOut); + +// this has been tested to work for non-varargs functions +// varargs functions end up with 'this' passed as the last parameter (ie. probably broken) +// do NOT use with classes that have multiple inheritance + +// if many member functions are to be declared, use MEMBER_FN_PREFIX to create a type with a known name +// so it doesn't need to be restated throughout the member list + +// all of the weirdness with the _GetType function is because you can't declare a static const pointer +// inside the class definition. inlining automatically makes the function call go away since it's a const + +#define MEMBER_FN_PREFIX(className) \ + typedef className _MEMBER_FN_BASE_TYPE + +#define DEFINE_MEMBER_FN_LONG(className, functionName, retnType, address, ...) \ + typedef retnType (className::* _##functionName##_type)(__VA_ARGS__); \ + \ + inline _##functionName##_type * _##functionName##_GetPtr(void) \ + { \ + static const UInt32 _address = address; \ + return (_##functionName##_type *)&_address; \ + } + +#define DEFINE_MEMBER_FN(functionName, retnType, address, ...) \ + DEFINE_MEMBER_FN_LONG(_MEMBER_FN_BASE_TYPE, functionName, retnType, address, __VA_ARGS__) + +#define CALL_MEMBER_FN(obj, fn) \ + ((*(obj)).*(*((obj)->_##fn##_GetPtr()))) + + +// ConsolePrint() limited to 512 chars; use this to print longer strings to console +void Console_Print_Long(const std::string& str); + +// Calls Print_Long or Print depending on the size of the string. +void Console_Print_Str(const std::string& str); + +// Macro for debug output to console at runtime +#if RUNTIME +#ifdef _DEBUG +#define DEBUG_PRINT(x, ...) { Console_Print((x), __VA_ARGS__); } +#define DEBUG_MESSAGE(x, ...) { _MESSAGE((x), __VA_ARGS__); } +#else +#define DEBUG_PRINT(x, ...) { } +#define DEBUG_MESSAGE(x, ...) { } +#endif //_DEBUG +#else +#define DEBUG_PRINT(x, ...) { } +#define DEBUG_MESSAGE(x, ...) { } +// This is so we don't have to handle size change with EditorData :) +#undef STATIC_ASSERT +#define STATIC_ASSERT(a) +#endif // RUNTIME + +#define SIZEOF_ARRAY(arrayName, elementType) (sizeof(arrayName) / sizeof(elementType)) + +class TESForm; + +class FormMatcher +{ +public: + virtual bool Matches(TESForm* pForm) const = 0; +}; + +namespace MersenneTwister +{ + + /* initializes mt[N] with a seed */ + void init_genrand(unsigned long s); + + /* initialize by an array with array-length */ + void init_by_array(unsigned long init_key[], int key_length); + + /* generates a random number on [0,0xffffffff]-interval */ + unsigned long genrand_int32(void); + + /* generates a random number on [0,0x7fffffff]-interval */ + long genrand_int31(void); + + /* generates a random number on [0,1]-real-interval */ + double genrand_real1(void); + + /* generates a random number on [0,1)-real-interval */ + double genrand_real2(void); + + /* generates a random number on (0,1)-real-interval */ + double genrand_real3(void); + + /* generates a random number on [0,1) with 53-bit resolution*/ + double genrand_res53(void); + +}; + +// alternative to strtok; doesn't modify src string, supports forward/backward iteration +class Tokenizer +{ +public: + Tokenizer(const char* src, const char* delims); + ~Tokenizer() = default; + + // these return the offset of token in src, or -1 if no token + UInt32 NextToken(std::string& outStr); + std::string ToNewLine(); + UInt32 PrevToken(std::string& outStr); + +private: + std::string m_delims; + size_t m_offset; + std::string m_data; +}; + + +// For parsing lexical tokens inside script text line-by-line, while skipping over those inside comments. +// Strings are passed as a single token (including the '"' characters). +// Everything else will have to be manually handled. +class ScriptTokenizer +{ +public: + ScriptTokenizer(std::string_view scriptText); + ~ScriptTokenizer() = default; + + // Returns true if a new line could be read, false at the end of the script. + // Skips over commented-out lines and empty lines. + [[nodiscard]] bool TryLoadNextLine(); + + // Gets the next space-separated token from the loaded line, ignoring tokens placed inside comments. + // Returns an empty string_view if no line is loaded / end-of-line is reached. + std::string_view GetNextLineToken(); + + // Gets the entire line, for manual tokenizing. + // Returns an empty string_view if no line is loaded. + std::string_view GetLineText(); + +private: + std::string_view m_scriptText; + size_t m_scriptOffset = 0; + std::vector m_loadedLineTokens; + size_t m_tokenOffset = 0; + bool m_inMultilineComment = false; +}; + +#if RUNTIME + +const char GetSeparatorChar(Script * script); +const char * GetSeparatorChars(Script * script); + +#endif + +const char * GetDXDescription(UInt32 keycode); + +bool ci_equal(char ch1, char ch2); +bool ci_less(const char* lh, const char* rh); +void MakeUpper(std::string& str); +void MakeUpper(char* str); +void MakeLower(std::string& str); + +// this copies the string onto the FormHeap - used to work around alloc/dealloc mismatch when passing +// data between nvse and plugins +char* CopyCString(const char* src); + +// Generic error/warning output +// provides a common way to output errors and warnings +class ErrOutput +{ + typedef void (* _ShowError)(const char* msg); + typedef bool (* _ShowWarning)(const char* msg); // returns true if user requests to disable warning + + _ShowError ShowError; + _ShowWarning ShowWarning; +public: + ErrOutput(_ShowError errorFunc, _ShowWarning warningFunc); + + struct Message + { + const char* fmt; + bool bCanDisable; + bool bDisabled; + }; + + void Show(Message msg, ...); + void Show(const char* msg, ...); + void vShow(Message& msg, va_list args); + void vShow(const char* msg, va_list args); +}; + +// thread-safe template versions of ThisStdCall() + +template +__forceinline T_Ret ThisStdCall(UInt32 _addr, const void *_this, Args ...args) +{ + return ((T_Ret (__thiscall *)(const void*, Args...))_addr)(_this, std::forward(args)...); +} + +template +__forceinline T_Ret StdCall(UInt32 _addr, Args ...args) +{ + return ((T_Ret (__stdcall *)(Args...))_addr)(std::forward(args)...); +} + +template +__forceinline T_Ret CdeclCall(UInt32 _addr, Args ...args) +{ + return ((T_Ret (__cdecl *)(Args...))_addr)(std::forward(args)...); +} + +void ShowErrorMessageBox(const char* message); + +#if RUNTIME + +const char* GetModName(TESForm* form); + +void ShowRuntimeError(Script* script, const char* fmt, ...); + +#endif + +std::string FormatString(const char* fmt, ...); + +#if EDITOR +void GeckExtenderMessageLog(const char* fmt, ...); +#endif + +std::vector GetCallStack(int i); + +bool FindStringCI(const std::string& strHaystack, const std::string& strNeedle); +void ReplaceAll(std::string &str, const std::string& from, const std::string& to); + +extern bool g_warnScriptErrors; + +template +bool Contains(const L& list, T t) +{ + for (auto i : list) + { + if (i == t) + return true; + } + return false; +} + +template +bool Contains(std::initializer_list list, const T& t) +{ + for (auto i : list) + { + if (i == t) + return true; + } + return false; +} + +#define _L(x, y) [&](x) {return y;} +namespace ra = std::ranges; + +bool IsProcessRunning(const char* processName); + +void DisplayMessage(const char* msg); + +std::string GetCurPath(); + +bool ValidString(const char* str); + +#if _DEBUG + + +const char* GetFormName(TESForm* form); +const char* GetFormName(UInt32 formId); + + +#endif + +typedef void* (*_FormHeap_Allocate)(UInt32 size); +extern const _FormHeap_Allocate FormHeap_Allocate; + +template +T* New(Args &&... args) +{ + auto* alloc = FormHeap_Allocate(sizeof(T)); + if constexpr (ConstructorPtr) + { + ThisStdCall(ConstructorPtr, alloc, std::forward(args)...); + } + else + { + memset(alloc, 0, sizeof(T)); + } + return static_cast(alloc); +} + +typedef void (*_FormHeap_Free)(void* ptr); +extern const _FormHeap_Free FormHeap_Free; + +template +void Delete(T* t, Args &&... args) +{ + if constexpr (DestructorPtr) + { + ThisStdCall(DestructorPtr, t, std::forward(args)...); + } + FormHeap_Free(t); +} + +template +using game_unique_ptr = std::unique_ptr>; + +template +game_unique_ptr MakeUnique(T* t) +{ + return game_unique_ptr(t, [](T* t2) { Delete(t2); }); +} + +template +game_unique_ptr MakeUnique(ConstructorArgs &&... args) +{ + auto* obj = New(std::forward(args)...); + return MakeUnique(obj); +} + +bool StartsWith(std::string left, std::string right); +std::string& ToLower(std::string&& data); +std::string& StripSpace(std::string&& data); +std::vector SplitString(std::string s, std::string delimiter); + +#define INLINE_HOOK(retnType, callingConv, ...) static_cast([](__VA_ARGS__) [[msvc::forceinline]] -> retnType + +UInt8* GetParentBasePtr(void* addressOfReturnAddress, bool lambda = false); + +//Example in https://en.cppreference.com/w/cpp/utility/variant/visit +//Allows function overloading with c++ lambdas. +template struct overloaded : Ts... { using Ts::operator()...; }; + +#if RUNTIME +inline int __cdecl game_toupper(int _C) { return CdeclCall(0xECA7F4, _C); } +inline int __cdecl game_tolower(int _C) { return CdeclCall(0xEC67AA, _C); } +#else +// GECK and other non-runtime code (ex: steam_loader) probably don't need localized stuff...? +inline int __cdecl game_toupper(int _C) { return toupper(_C); } +inline int __cdecl game_tolower(int _C) { return tolower(_C); } +#endif \ No newline at end of file diff --git a/common/utility.cpp b/common/utility.cpp new file mode 100644 index 0000000..ad47d71 --- /dev/null +++ b/common/utility.cpp @@ -0,0 +1,637 @@ +#include "nvse/utility.h" + +memcpy_t _memcpy = memcpy, _memmove = memmove; + +__declspec(naked) PrimitiveCS *PrimitiveCS::Enter() +{ + __asm + { + push ebx + mov ebx, ecx + call GetCurrentThreadId + cmp [ebx], eax + jnz doSpin + done: + mov eax, ebx + pop ebx + retn + NOP_0xA + doSpin: + mov ecx, eax + xor eax, eax + lock cmpxchg [ebx], ecx + test eax, eax + jz done + push esi + push edi + mov esi, ecx + mov edi, 0x2710 + spinHead: + dec edi + mov edx, edi + shr edx, 0x1F + push edx + call Sleep + xor eax, eax + lock cmpxchg [ebx], esi + test eax, eax + jnz spinHead + pop edi + pop esi + mov eax, ebx + pop ebx + retn + } +} + +__declspec(naked) TESForm* __stdcall LookupFormByRefID(UInt32 refID) +{ + __asm + { + mov ecx, ds:[0x11C54C0] + mov eax, [esp+4] + xor edx, edx + div dword ptr [ecx+4] + mov eax, [ecx+8] + mov eax, [eax+edx*4] + test eax, eax + jz done + mov edx, [esp+4] + ALIGN 16 + iterHead: + cmp [eax+4], edx + jz found + mov eax, [eax] + test eax, eax + jnz iterHead + retn 4 + found: + mov eax, [eax+8] + done: + retn 4 + } +} + +__declspec(naked) int __vectorcall ifloor(float value) +{ + __asm + { + movd eax, xmm0 + test eax, eax + jns isPos + push 0x3FA0 + ldmxcsr [esp] + cvtss2si eax, xmm0 + mov dword ptr [esp], 0x1FA0 + ldmxcsr [esp] + pop ecx + retn + isPos: + cvttss2si eax, xmm0 + retn + } +} + +__declspec(naked) int __vectorcall iceil(float value) +{ + __asm + { + movd eax, xmm0 + test eax, eax + js isNeg + push 0x5FA0 + ldmxcsr [esp] + cvtss2si eax, xmm0 + mov dword ptr [esp], 0x1FA0 + ldmxcsr [esp] + pop ecx + retn + isNeg: + cvttss2si eax, xmm0 + retn + } +} + +__declspec(naked) UInt32 __fastcall StrLen(const char *str) +{ + __asm + { + test ecx, ecx + jnz proceed + xor eax, eax + retn + proceed: + push ecx + test ecx, 3 + jz iter4 + iter1: + mov al, [ecx] + inc ecx + test al, al + jz done1 + test ecx, 3 + jnz iter1 + ALIGN 16 + iter4: + mov eax, [ecx] + mov edx, 0x7EFEFEFF + add edx, eax + not eax + xor eax, edx + add ecx, 4 + test eax, 0x81010100 + jz iter4 + mov eax, [ecx-4] + test al, al + jz done4 + test ah, ah + jz done3 + test eax, 0xFF0000 + jz done2 + test eax, 0xFF000000 + jnz iter4 + done1: + lea eax, [ecx-1] + pop ecx + sub eax, ecx + retn + done2: + lea eax, [ecx-2] + pop ecx + sub eax, ecx + retn + done3: + lea eax, [ecx-3] + pop ecx + sub eax, ecx + retn + done4: + lea eax, [ecx-4] + pop ecx + sub eax, ecx + retn + } +} + +__declspec(naked) void __fastcall MemZero(void *dest, UInt32 bsize) +{ + __asm + { + push edi + test ecx, ecx + jz done + mov edi, ecx + xor eax, eax + mov ecx, edx + shr ecx, 2 + jz write1 + rep stosd + write1: + and edx, 3 + jz done + mov ecx, edx + rep stosb + done: + pop edi + retn + } +} + +__declspec(naked) char* __fastcall StrCopy(char *dest, const char *src) +{ + __asm + { + mov eax, ecx + test ecx, ecx + jz done + test edx, edx + jnz proceed + mov [eax], 0 + done: + retn + proceed: + push ecx + mov ecx, edx + call StrLen + pop edx + push eax + inc eax + push eax + push ecx + push edx + call _memmove + add esp, 0xC + pop ecx + add eax, ecx + retn + } +} + +__declspec(naked) char* __fastcall StrNCopy(char *dest, const char *src, UInt32 length) +{ + __asm + { + mov eax, ecx + test ecx, ecx + jz done + test edx, edx + jz nullTerm + cmp dword ptr [esp+4], 0 + jz nullTerm + push esi + mov esi, ecx + mov ecx, edx + call StrLen + mov edx, [esp+8] + cmp edx, eax + cmova edx, eax + push edx + push ecx + push esi + add esi, edx + call _memmove + add esp, 0xC + mov eax, esi + pop esi + nullTerm: + mov [eax], 0 + done: + retn 4 + } +} + +__declspec(naked) char* __fastcall StrCat(char *dest, const char *src) +{ + __asm + { + test ecx, ecx + jnz proceed + mov eax, ecx + retn + proceed: + push edx + call StrLen + pop edx + add ecx, eax + jmp StrCopy + } +} + +alignas(16) const char +kLwrCaseConverter[] = +{ + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', + '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F', + '\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2A', '\x2B', '\x2C', '\x2D', '\x2E', '\x2F', + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39', '\x3A', '\x3B', '\x3C', '\x3D', '\x3E', '\x3F', + '\x40', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6A', '\x6B', '\x6C', '\x6D', '\x6E', '\x6F', + '\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', '\x78', '\x79', '\x7A', '\x5B', '\x5C', '\x5D', '\x5E', '\x5F', + '\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6A', '\x6B', '\x6C', '\x6D', '\x6E', '\x6F', + '\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', '\x78', '\x79', '\x7A', '\x7B', '\x7C', '\x7D', '\x7E', '\x7F', + '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', '\x89', '\x8A', '\x8B', '\x8C', '\x8D', '\x8E', '\x8F', + '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', '\x99', '\x9A', '\x9B', '\x9C', '\x9D', '\x9E', '\x9F', + '\xA0', '\xA1', '\xA2', '\xA3', '\xA4', '\xA5', '\xA6', '\xA7', '\xA8', '\xA9', '\xAA', '\xAB', '\xAC', '\xAD', '\xAE', '\xAF', + '\xB0', '\xB1', '\xB2', '\xB3', '\xB4', '\xB5', '\xB6', '\xB7', '\xB8', '\xB9', '\xBA', '\xBB', '\xBC', '\xBD', '\xBE', '\xBF', + '\xC0', '\xC1', '\xC2', '\xC3', '\xC4', '\xC5', '\xC6', '\xC7', '\xC8', '\xC9', '\xCA', '\xCB', '\xCC', '\xCD', '\xCE', '\xCF', + '\xD0', '\xD1', '\xD2', '\xD3', '\xD4', '\xD5', '\xD6', '\xD7', '\xD8', '\xD9', '\xDA', '\xDB', '\xDC', '\xDD', '\xDE', '\xDF', + '\xE0', '\xE1', '\xE2', '\xE3', '\xE4', '\xE5', '\xE6', '\xE7', '\xE8', '\xE9', '\xEA', '\xEB', '\xEC', '\xED', '\xEE', '\xEF', + '\xF0', '\xF1', '\xF2', '\xF3', '\xF4', '\xF5', '\xF6', '\xF7', '\xF8', '\xF9', '\xFA', '\xFB', '\xFC', '\xFD', '\xFE', '\xFF' +}; + +// Returns 0 if both strings are equal. +char __fastcall StrCompare(const char *lstr, const char *rstr) +{ + if (!lstr) return rstr ? -1 : 0; + if (!rstr) return 1; + UInt8 lchr, rchr; + while (*lstr) + { + lchr = game_toupper(*(UInt8*)lstr); + rchr = game_toupper(*(UInt8*)rstr); + if (lchr == rchr) + { + lstr++; + rstr++; + continue; + } + return (lchr < rchr) ? -1 : 1; + } + return *rstr ? -1 : 0; +} + +void __fastcall StrToLower(char *str) +{ + if (!str) return; + UInt8 curr; + while (curr = *str) + { + *str = game_tolower(curr); + str++; + } +} + +char* __fastcall SubStrCI(const char *srcStr, const char *subStr) +{ + int srcLen = StrLen(srcStr); + if (!srcLen) return NULL; + int subLen = StrLen(subStr); + if (!subLen) return NULL; + srcLen -= subLen; + if (srcLen < 0) return NULL; + int index; + do + { + index = 0; + while (true) + { + if (game_tolower(*(UInt8*)(srcStr + index)) != game_tolower(*(UInt8*)(subStr + index))) + break; + if (++index == subLen) + return const_cast(srcStr); + } + srcStr++; + } + while (--srcLen >= 0); + return NULL; +} + +char* __fastcall SlashPos(const char *str) +{ + if (!str) return NULL; + char curr; + while (curr = *str) + { + if ((curr == '/') || (curr == '\\')) + return const_cast(str); + str++; + } + return NULL; +} + +__declspec(naked) char* __fastcall CopyString(const char *key) +{ + __asm + { + call StrLen + inc eax + push eax + push ecx + push eax +#if !_DEBUG + call _malloc_base +#else + call malloc +#endif + pop ecx + push eax + call _memcpy + add esp, 0xC + retn + } +} + +__declspec(naked) char* __fastcall CopyString(const char* key, UInt32 length) +{ + __asm + { + mov eax, edx // length + inc eax // length++ to account for null terminator + push eax + push ecx + push eax +#if !_DEBUG + call _malloc_base +#else + call malloc +#endif + pop ecx // equivalent to "add esp, 4" here + // stack is now: + // [esp+4]: length + 1 + // [esp]: "key" arg passed to CopyString + + // length -= 1, to avoid copying null terminator or past-the-end part of string (if it had no null terminator) + dec [esp+4] + + push eax // dest = malloc's new ptr + call _memcpy + // eax is now memcpy's new string + add esp, 8 + pop edx // get pushed eax back (length) + // add null terminator to copied string in case string arg didn't have one + add edx, eax // advance to last char of string (where null terminator should be) + mov byte ptr[edx], 0 + retn + } +} + +__declspec(naked) char* __fastcall IntToStr(char *str, int num) +{ + __asm + { + push esi + push edi + test edx, edx + jns skipNeg + neg edx + mov [ecx], '-' + inc ecx + skipNeg: + mov esi, ecx + mov edi, ecx + mov eax, edx + mov ecx, 0xA + workIter: + xor edx, edx + div ecx + add dl, '0' + mov [esi], dl + inc esi + test eax, eax + jnz workIter + mov [esi], 0 + mov eax, esi + swapIter: + dec esi + cmp esi, edi + jbe done + mov dl, [esi] + mov cl, [edi] + mov [esi], cl + mov [edi], dl + inc edi + jmp swapIter + done: + pop edi + pop esi + retn + } +} + +// By JazzIsParis +__declspec(naked) UInt32 __fastcall StrHashCS(const char *inKey) +{ + __asm + { + mov eax, 0x6B49D20B + test ecx, ecx + jz done + ALIGN 16 + iterHead: + movzx edx, byte ptr[ecx] + test dl, dl + jz done + shl edx, 4 + sub eax, edx + mov edx, eax + shl eax, 5 + sub eax, edx + movzx edx, byte ptr[ecx + 1] + test dl, dl + jz done + shl edx, 0xC + sub eax, edx + mov edx, eax + shl eax, 5 + sub eax, edx + movzx edx, byte ptr[ecx + 2] + test dl, dl + jz done + shl edx, 0x14 + sub eax, edx + mov edx, eax + shl eax, 5 + sub eax, edx + movzx edx, byte ptr[ecx + 3] + test dl, dl + jz done + sub eax, edx + mov edx, eax + shl eax, 5 + sub eax, edx + add ecx, 4 + jmp iterHead + done : + ret + } +} + +// By JazzIsParis +// "only < 0.008% collisions" +__declspec(naked) UInt32 __fastcall StrHashCI(const char* inKey) +{ + __asm + { + push esi + mov eax, 0x6B49D20B + test ecx, ecx + jz done + mov esi, ecx + xor ecx, ecx + ALIGN 16 + iterHead: + mov cl, [esi] + test cl, cl + jz done + movzx edx, kLwrCaseConverter[ecx] + shl edx, 4 + sub eax, edx + mov edx, eax + shl eax, 5 + sub eax, edx + mov cl, [esi + 1] + test cl, cl + jz done + movzx edx, kLwrCaseConverter[ecx] + shl edx, 0xC + sub eax, edx + mov edx, eax + shl eax, 5 + sub eax, edx + mov cl, [esi + 2] + test cl, cl + jz done + movzx edx, kLwrCaseConverter[ecx] + shl edx, 0x14 + sub eax, edx + mov edx, eax + shl eax, 5 + sub eax, edx + mov cl, [esi + 3] + test cl, cl + jz done + movzx edx, kLwrCaseConverter[ecx] + sub eax, edx + mov edx, eax + shl eax, 5 + sub eax, edx + add esi, 4 + jmp iterHead + done : + pop esi + retn + } +} + +void SpinLock::Enter() +{ + UInt32 threadID = GetCurrentThreadId(); + if (owningThread == threadID) + { + enterCount++; + return; + } + while (InterlockedCompareExchange(&owningThread, threadID, 0)); + enterCount = 1; +} + +#define FAST_SLEEP_COUNT 10000UL + +void SpinLock::EnterSleep() +{ + UInt32 threadID = GetCurrentThreadId(); + if (owningThread == threadID) + { + enterCount++; + return; + } + UInt32 fastIdx = FAST_SLEEP_COUNT; + while (InterlockedCompareExchange(&owningThread, threadID, 0)) + { + if (fastIdx) + { + fastIdx--; + Sleep(0); + } + else Sleep(1); + } + enterCount = 1; +} + +void SpinLock::Leave() +{ + if (owningThread && !--enterCount) + owningThread = 0; +} + +// From JIP +alignas(16) const UInt32 kPackedValues[] = +{ + PS_DUP_4(0x7FFFFFFF), + PS_DUP_1(0x7FFFFFFF), + PS_DUP_4(0x80000000), + PS_DUP_1(0x80000000), + PS_DUP_3(0xFFFFFFFF), + 0xFFFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF, + 0, 0x80000000, 0, 0x80000000, + PS_DUP_4(HEX(FLT_EPSILON)), + PS_DUP_3(HEX(FltPId180)), + PS_DUP_3(HEX(Flt180dPI)), + PS_DUP_3(HEX(FltPId2)), + PS_DUP_3(HEX(FltPI)), + PS_DUP_3(HEX(FltPIx2)), + PS_DUP_3(HEX(0.5F)), + PS_DUP_3(HEX(1.0F)), + PS_DUP_3(0x40DFF8D6), + HEX(0.001F), HEX(0.01F), HEX(0.1F), HEX(0.25F), + HEX(3.0F), HEX(10.0F), HEX(100.0F), 0 +}; \ No newline at end of file diff --git a/common/utility.h b/common/utility.h new file mode 100644 index 0000000..edb7608 --- /dev/null +++ b/common/utility.h @@ -0,0 +1,201 @@ +#pragma once + +#include +#include "Utilities.h" // for ThisStdCall +#include + +typedef void* (*memcpy_t)(void*, const void*, size_t); +extern memcpy_t _memcpy, _memmove; + +// Workaround for bypassing the compiler calling the d'tor on function-scope objects. +template class TempObject +{ + friend T; + + struct Buffer + { + UInt8 bytes[sizeof(T)]; + } + objData; + +public: + TempObject() + { + if constexpr (InitConstructor) + Reset(); + } + TempObject(const T &src) {objData = *(Buffer*)&src;} + + void Reset() {new ((T*)&objData) T();} + + T& operator()() {return *(T*)&objData;} + + TempObject& operator=(const T &rhs) {objData = *(Buffer*)&rhs;} + TempObject& operator=(const TempObject &rhs) {objData = rhs.objData;} +}; + +// Assign rhs to lhs, bypassing operator= +template __forceinline void RawAssign(const T &lhs, const T &rhs) +{ + struct Helper + { + UInt8 bytes[sizeof(T)]; + }; + *(Helper*)&lhs = *(Helper*)&rhs; +} + +// Swap lhs and rhs, bypassing operator= +template __forceinline void RawSwap(const T &lhs, const T &rhs) +{ + struct Helper + { + UInt8 bytes[sizeof(T)]; + } + temp = *(Helper*)&lhs; + *(Helper*)&lhs = *(Helper*)&rhs; + *(Helper*)&rhs = temp; +} + +// These are used for 10h aligning segments in ASM code (massive performance gain, particularly with loops). +#define EMIT(bt) __asm _emit bt +#define NOP_0x1 EMIT(0x90) +#define NOP_0x2 EMIT(0x66) EMIT(0x90) +#define NOP_0x3 EMIT(0x0F) EMIT(0x1F) EMIT(0x00) +#define NOP_0x4 EMIT(0x0F) EMIT(0x1F) EMIT(0x40) EMIT(0x00) +#define NOP_0x5 EMIT(0x0F) EMIT(0x1F) EMIT(0x44) EMIT(0x00) EMIT(0x00) +#define NOP_0x6 EMIT(0x66) EMIT(0x0F) EMIT(0x1F) EMIT(0x44) EMIT(0x00) EMIT(0x00) +#define NOP_0x7 EMIT(0x0F) EMIT(0x1F) EMIT(0x80) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) +#define NOP_0x8 EMIT(0x0F) EMIT(0x1F) EMIT(0x84) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) +#define NOP_0x9 EMIT(0x66) EMIT(0x0F) EMIT(0x1F) EMIT(0x84) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) EMIT(0x00) +#define NOP_0xA NOP_0x5 NOP_0x5 +#define NOP_0xB NOP_0x5 NOP_0x6 +#define NOP_0xC NOP_0x6 NOP_0x6 +#define NOP_0xD NOP_0x6 NOP_0x7 +#define NOP_0xE NOP_0x7 NOP_0x7 +#define NOP_0xF NOP_0x7 NOP_0x8 + +class PrimitiveCS +{ + DWORD m_owningThread; + +public: + PrimitiveCS() : m_owningThread(0) {} + + PrimitiveCS *Enter(); + __forceinline void Leave() {m_owningThread = 0;} +}; + +class PrimitiveScopedLock +{ + PrimitiveCS *m_cs; + +public: + PrimitiveScopedLock(PrimitiveCS &cs) : m_cs(&cs) {cs.Enter();} + ~PrimitiveScopedLock() {m_cs->Leave();} +}; + +class TESForm; +TESForm* __stdcall LookupFormByRefID(UInt32 refID); + +int __vectorcall ifloor(float value); + +int __vectorcall iceil(float value); + +UInt32 __fastcall StrLen(const char* str); + +void __fastcall MemZero(void* dest, UInt32 bsize); + +char* __fastcall StrCopy(char* dest, const char* src); + +char* __fastcall StrNCopy(char* dest, const char* src, UInt32 length); + +char* __fastcall StrCat(char* dest, const char* src); + +char __fastcall StrCompare(const char* lstr, const char* rstr); + +void __fastcall StrToLower(char* str); + +char* __fastcall SubStrCI(const char *srcStr, const char *subStr); + +char* __fastcall SlashPos(const char *str); + +char* __fastcall CopyString(const char* key); +char* __fastcall CopyString(const char* key, UInt32 length); + +char* __fastcall IntToStr(char *str, int num); + +UInt32 __fastcall StrHashCS(const char* inKey); + +UInt32 __fastcall StrHashCI(const char* inKey); + +class SpinLock +{ + UInt32 owningThread; + UInt32 enterCount; + +public: + SpinLock() : owningThread(0), enterCount(0) {} + + void Enter(); + void EnterSleep(); + void Leave(); +}; + +#define GetRandomUInt(n) ThisStdCall(0xAA5230, (void*)0x11C4180, n) + +// From JIP +#define PS_DUP_1(a) a, 0UL, 0UL, 0UL +#define PS_DUP_2(a) a, a, 0UL, 0UL +#define PS_DUP_3(a) a, a, a, 0UL +#define PS_DUP_4(a) a, a, a, a + +// From JIP +#define HEX(a) std::bit_cast(a) +#define UBYT(a) *((UInt8*)&a) +#define USHT(a) *((UInt16*)&a) +#define ULNG(a) *((UInt32*)&a) + +// From JIP +extern const UInt32 kPackedValues[]; + +// From JIP +#define GET_PS(i) ((const __m128*)kPackedValues)[i] +#define GET_SS(i) ((const float*)kPackedValues)[i << 2] + +// From JIP +#define PS_AbsMask kPackedValues +#define PS_AbsMask0 kPackedValues+0x10 +#define PS_FlipSignMask kPackedValues+0x20 +#define PS_FlipSignMask0 kPackedValues+0x30 +#define PS_XYZ0Mask kPackedValues+0x40 +#define PD_AbsMask kPackedValues+0x50 +#define PD_FlipSignMask kPackedValues+0x60 + +// From JIP +#define PS_Epsilon kPackedValues+0x70 +#define PS_V3_PId180 kPackedValues+0x80 +#define PS_V3_180dPI kPackedValues+0x90 +#define PS_V3_PId2 kPackedValues+0xA0 +#define PS_V3_PI kPackedValues+0xB0 +#define PS_V3_PIx2 kPackedValues+0xC0 +#define PS_V3_Half kPackedValues+0xD0 +#define PS_V3_One kPackedValues+0xE0 +#define PS_HKUnitCnvrt kPackedValues+0xF0 + +// From JIP +#define SS_1d1K kPackedValues+0x100 +#define SS_1d100 kPackedValues+0x104 +#define SS_1d10 kPackedValues+0x108 +#define SS_1d4 kPackedValues+0x10C +#define SS_3 kPackedValues+0x110 +#define SS_10 kPackedValues+0x114 +#define SS_100 kPackedValues+0x118 + +// From JIP +#define FltPId2 1.570796371F +#define FltPI 3.141592741F +#define FltPIx2 6.283185482F +#define FltPId180 0.01745329238F +#define Flt180dPI 57.29578018F +#define DblPId180 0.017453292519943295 +#define Dbl180dPI 57.29577951308232 \ No newline at end of file diff --git a/dllmain.c b/dllmain.c new file mode 100644 index 0000000..8bf8e8b --- /dev/null +++ b/dllmain.c @@ -0,0 +1,10 @@ +#include + +BOOL WINAPI DllMain( + HANDLE hDllHandle, + DWORD dwReason, + LPVOID lpreserved + ) +{ + return TRUE; +} diff --git a/fps.h b/fps.h new file mode 100644 index 0000000..33f6c17 --- /dev/null +++ b/fps.h @@ -0,0 +1,124 @@ +#pragma once +#include + +double g_iMaxFPSTolerance = 300; +double g_iMinFPSTolerance = 5; +double fDesiredMin = 1; +double fDesiredMax = 1000; +float fLowerMaxTimeBoundary = 0.016; +float fMaxTimeDefault = 0; +constexpr float fTimerOffsetMult = 0.9875; + +float* g_FPSGlobal = (float*)0x1090BA4; +float* fMaxTime = (float*)0x10F2BE8; + +DWORD** InterfSingleton = (DWORD**)0x1075B24; +DWORD** BGSLoadGameSingleton = (DWORD**)0x1079858; + +bool* g_DialogMenu = (bool*)0x107613C; +bool* g_DialogMenu2 = (bool*)0x107A0F4; +bool* g_bIsMenuMode = (bool*)0x107A0F3; +bool* g_bIsInPauseFade = (bool*)0x107A0F5; +bool* g_bIsLoadingNewGame = (bool*)0x1075A07; + +namespace QPC { + + double lastCount = 0; + signed int tickCountBias = 0; + using namespace std::chrono; + + void StartCounter() + { + auto now = duration_cast(duration_cast(time_point_cast(steady_clock::now()).time_since_epoch())); + long duration = now.count(); + + + QPC::tickCountBias = duration - long(GetTickCount()); //this will fail if your system was started 53 or more days ago + QPC::lastCount = duration; + + } + DWORD ReturnCounter() + { + auto now = duration_cast(duration_cast(time_point_cast(steady_clock::now()).time_since_epoch())); + return unsigned long(now.count() + tickCountBias); + } + + double UpdateCounterMS() + { + + auto now = duration_cast(duration_cast(time_point_cast(steady_clock::now()).time_since_epoch())); + double currentCount = double(now.count()) / 1000000; + double toReturn = currentCount - lastCount; + lastCount = currentCount; + return toReturn; + + } + +} + + +void* __stdcall TimeGlobalHook() { + + double delta = QPC::UpdateCounterMS(); + + if (delta <= FLT_EPSILON) { + *fMaxTime = fLowerMaxTimeBoundary; + } + else if (delta >= fDesiredMax) { + *fMaxTime = fDesiredMax / 1000; + } + else if (delta <= fDesiredMin) { + *fMaxTime = fDesiredMin / 1000; + } + else { + *fMaxTime = delta / 1000; + } + + if ((*BGSLoadGameSingleton && (*(*BGSLoadGameSingleton + 0x91) & 2) != 0) || *g_bIsLoadingNewGame || delta <= 0) { + *g_FPSGlobal = 0; + } + else if (delta >= fDesiredMax) { + *g_FPSGlobal = fDesiredMax; + } + else if (delta <= fDesiredMin) { + *g_FPSGlobal = fDesiredMin; + } + else { + *g_FPSGlobal = delta; + } + + if (*g_FPSGlobal > FLT_EPSILON) + { + *g_FPSGlobal = 1000 / ((1000 / *g_FPSGlobal) * fTimerOffsetMult); + *fMaxTime = 1000 / ((1000 / *fMaxTime) * fTimerOffsetMult); + + if (*fMaxTime < FLT_EPSILON) { + *fMaxTime = FLT_EPSILON; + } + + } + if (*g_bIsInPauseFade || *g_FPSGlobal < FLT_EPSILON) { + *fMaxTime = fMaxTimeDefault; + } + else if (*fMaxTime > fLowerMaxTimeBoundary) { + *fMaxTime = fLowerMaxTimeBoundary; + } + + return ThisStdCall(0x7F92A0, nullptr); +} + + +void WritePatches() { + + + QPC::StartCounter(); + SafeWrite32(0xD9B090, (uintptr_t)QPC::ReturnCounter); + + fDesiredMin = 1000.0 / g_iMaxFPSTolerance; + fDesiredMax = 1000.0 / g_iMinFPSTolerance; + fMaxTimeDefault = *fMaxTime; + + WriteRelCall(0x6EDC02, (uintptr_t)TimeGlobalHook); + + +} \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..1037a20 --- /dev/null +++ b/main.cpp @@ -0,0 +1,20 @@ +#include "common/IPrefix.h" +#include "common/PluginAPI.h" +#include "SafeWrite.h" +#include "calls.h" +#include "fps.h" +extern "C" { + __declspec(dllexport) bool FOSEPlugin_Query(const FOSEInterface* fose, PluginInfo* info) { + info->infoVersion = PluginInfo::kInfoVersion; + info->name = "HighFpsFix"; + info->version = 100; + return true; + } + + __declspec(dllexport) bool FOSEPlugin_Load(FOSEInterface* fose) { + if (!fose->isEditor) { + WritePatches(); + } + return true; + } +}