diff --git a/.gitignore b/.gitignore index f1e3d20..c9d2c8e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,15 +17,13 @@ [Rr]eleases/ x64/ x86/ +build/ bld/ [Bb]in/ [Oo]bj/ -[Ll]og/ -# Visual Studio 2015 cache/options directory +# Visual Studo 2015 cache/options directory .vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ @@ -40,10 +38,6 @@ TestResult.xml [Rr]eleasePS/ dlldata.c -# DNX -project.lock.json -artifacts/ - *_i.c *_p.c *_i.h @@ -76,18 +70,14 @@ _Chutzpah* ipch/ *.aps *.ncb -*.opendb *.opensdf *.sdf *.cachefile -*.VC.db -*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx -*.sap # TFS 2012 Local Workspace $tf/ @@ -100,7 +90,7 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JustCode is a .NET coding add-in +# JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in @@ -112,7 +102,6 @@ _TeamCity* # NCrunch _NCrunch_* .*crunch*.local.xml -nCrunchTemp_* # MightyMoose *.mm.* @@ -140,16 +129,11 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# TODO: 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 # The packages folder can be ignored because of Package Restore @@ -158,32 +142,18 @@ PublishScripts/ !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -# NuGet v3's project.json files produces more ignoreable files -*.nuget.props -*.nuget.targets -# Microsoft Azure Build Output +# Windows Azure Build Output csx/ *.build.csdef -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files +# Windows Store app package directory AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ # Others +*.[Cc]ache ClientBin/ +[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl @@ -191,11 +161,7 @@ ClientBin/ *.pfx *.publishsettings node_modules/ -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ +bower_components/ # RIA/Silverlight projects Generated_Code/ @@ -220,9 +186,6 @@ UpgradeLog*.htm # Microsoft Fakes FakesAssemblies/ -# GhostDoc plugin setting file -*.GhostDoc.xml - # Node.js Tools for Visual Studio .ntvs_analysis.dat @@ -231,22 +194,5 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt - -# 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/ - -# JetBrains Rider -.idea/ -*.sln.iml +/tzf_dsound.VC.VC.opendb +/tzf_dsound.VC.db diff --git a/LICENSE b/LICENSE index 23cb790..8cdb845 100644 --- a/LICENSE +++ b/LICENSE @@ -337,3 +337,4 @@ proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. + diff --git a/README.md b/README.md index 1348341..cb9fe79 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,89 @@ # TBF -Tales of Berseria "Fix" +Tales of Berseria "Fix" - Community Patch + + Copyright (c) 2017 + + Andon "Kaldaien" Coleman + + 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. + +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 HOLDER +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. + + +Portions Copyright (c) 2009-2015 Tsuda Kageyu +--- + +MinHook - The Minimalistic API Hooking Library for x64/x86 +Copyright (C) 2009-2015 Tsuda Kageyu. +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. + +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 HOLDER +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. + +================================================================================ +Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov. +================================================================================ +Hacker Disassembler Engine 32 C +Copyright (c) 2008-2009, Vyacheslav Patkov. +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. + +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 REGENTS 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. diff --git a/include/DLL_VERSION.H b/include/DLL_VERSION.H new file mode 100644 index 0000000..4c92ee6 --- /dev/null +++ b/include/DLL_VERSION.H @@ -0,0 +1,29 @@ +#pragma once + + +#define TBF_MAJOR 0 +#define TBF_MINOR 0 +#define TBF_BUILD 1 +#define TBF_REV 0 + + + + + +#define _A2(a) #a +#define _A(a) _A2(a) +#define _L2(w) L ## w +#define _L(w) _L2(w) + + +#if (TBF_REV > 0) +#define TBF_VERSION_STR_A _A(TBF_MAJOR) "." _A(TBF_MINOR) "." _A(TBF_BUILD) "." _A(TBF_REV) +#else +#define TBF_VERSION_STR_A _A(TBF_MAJOR) "." _A(TBF_MINOR) "." _A(TBF_BUILD) +#endif + +#define TBF_VERSION_STR_W _L(TBF_VERSION_STR_A) + + +#define TBF_FILE_VERSION TBF_MAJOR,TBF_MINOR,TBF_BUILD,TBF_REV +#define TBF_PRODUCT_VERSION TBF_MAJOR,TBF_MINOR,TBF_BUILD,TBF_REV diff --git a/include/command.h b/include/command.h new file mode 100644 index 0000000..66b428c --- /dev/null +++ b/include/command.h @@ -0,0 +1,150 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#ifndef __TBF__COMMAND_H__ +#define __TBF__COMMAND_H__ + +#include + +# include + +#include // tolower (...) + +interface SK_IVariable; +interface SK_ICommand; + +interface SK_ICommandResult +{ + SK_ICommandResult ( const char* word, + const char* arguments = "", + const char* result = "", + int status = false, + const SK_IVariable* var = NULL, + const SK_ICommand* cmd = NULL ) : word_ (word), + args_ (arguments), + result_ (result) { + var_ = var; + cmd_ = cmd; + status_ = status; + } + + const char* getWord (void) const { return word_.c_str (); } + const char* getArgs (void) const { return args_.c_str (); } + const char* getResult (void) const { return result_.c_str (); } + + const SK_IVariable* getVariable (void) const { return var_; } + const SK_ICommand* getCommand (void) const { return cmd_; } + + int getStatus (void) const { return status_; } + +protected: + +private: + const SK_IVariable* var_; + const SK_ICommand* cmd_; + std::string word_; + std::string args_; + std::string result_; + int status_; +}; + +interface SK_ICommand +{ + virtual SK_ICommandResult execute (const char* szArgs) = 0; + + virtual const char* getHelp (void) { return "No Help Available"; } + + virtual int getNumArgs (void) { return 0; } + virtual int getNumOptionalArgs (void) { return 0; } + virtual int getNumRequiredArgs (void) { + return getNumArgs () - getNumOptionalArgs (); + } +}; + +interface SK_IVariable +{ + friend interface SK_IVariableListener; + + enum VariableType { + Float, + Double, + Boolean, + Byte, + Short, + Int, + LongInt, + String, + + NUM_VAR_TYPES_, + + Unknown + } VariableTypes; + + virtual VariableType getType (void) const = 0; + virtual void getValueString ( _Out_opt_ char* szOut, + _Inout_ uint32_t* dwLen ) const = 0; + virtual void* getValuePointer (void) const = 0; + +protected: + VariableType type_; +}; + +interface SK_IVariableListener +{ + virtual bool OnVarChange (SK_IVariable* var, void* val = NULL) = 0; +}; + +interface SK_ICommandProcessor +{ + SK_ICommandProcessor (void); + + virtual ~SK_ICommandProcessor (void) + { + } + + virtual SK_ICommand* FindCommand (const char* szCommand) const = 0; + + virtual const SK_ICommand* AddCommand ( const char* szCommand, + SK_ICommand* pCommand ) = 0; + virtual bool RemoveCommand ( const char* szCommand ) = 0; + + + virtual const SK_IVariable* FindVariable (const char* szVariable) const = 0; + + virtual const SK_IVariable* AddVariable ( const char* szVariable, + SK_IVariable* pVariable ) = 0; + virtual bool RemoveVariable ( const char* szVariable ) = 0; + + + virtual SK_ICommandResult ProcessCommandLine (const char* szCommandLine) = 0; + virtual SK_ICommandResult ProcessCommandFormatted (const char* szCommandFormat, ...) = 0; +}; + +typedef SK_ICommandProcessor* (__stdcall *SK_GetCommandProcessor_pfn)(void); +extern SK_GetCommandProcessor_pfn SK_GetCommandProcessor; + +SK_IVariable* +TBF_CreateVar ( SK_IVariable::VariableType type, + void* var, + SK_IVariableListener *pListener = nullptr ); + +#endif /* __TBF__COMMAND_H */ diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..ee65595 --- /dev/null +++ b/include/config.h @@ -0,0 +1,110 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#ifndef __TBF__CONFIG_H__ +#define __TBF__CONFIG_H__ + +#include +#include + +extern std::wstring TBF_VER_STR; +extern std::wstring DEFAULT_BK2; + +struct tbf_config_t +{ + struct { + uint32_t sample_hz = 48000; + uint32_t channels = 6; // OBSOLETE + bool compatibility = false; + bool enable_fix = true; + } audio; + + struct { + bool yield_processor = true; + bool allow_fake_sleep = false; + bool minimize_latency = false; + DWORD speedresetcode_addr = 0x0046C0F9; //0x0046C529; + DWORD speedresetcode2_addr = 0x0056EB41; //0x0056E441; 0x217B464 + DWORD speedresetcode3_addr = 0x0056E03F; //0x0056D93F; + DWORD limiter_branch_addr = 0x00990F53; //0x00990873; + bool disable_limiter = true; + bool auto_adjust = false; + int target = 60; + int battle_target = 60; + bool battle_adaptive = false; + int cutscene_target = 30; + bool reshade_fix = false; + } framerate; + + struct { + bool capture = false; + } file_io; + + struct { + bool allow_broadcasts = false; + } steam; + + struct { + bool fix_priest = true; + } lua; + + struct { + float fovy = 0.785398f; + float aspect_ratio = 1.777778f; + DWORD aspect_addr = 0x00D56494;//0x00D52398; + DWORD fovy_addr = 0x00D56498;//0x00D5239C; + bool blackbar_videos = true; // OBSOLETE + bool aspect_correction = true; + int32_t shadow_rescale = -2; + float postproc_ratio = 1.0f; + bool clear_blackbars = true; + int32_t env_shadow_rescale = 0; + } render; + + struct { + bool dump = false; + bool remaster = false; + bool cache = true; + int32_t max_cache_in_mib = 2048L; + int32_t worker_threads = 6; + } textures; + + struct { + std::wstring swap_keys = L""; + } keyboard; + + struct { + std::wstring + version = TBF_VER_STR; + std::wstring + intro_video = DEFAULT_BK2; + std::wstring + injector = L"d3d9.dll"; + } system; +}; + +extern tbf_config_t config; + +bool TBF_LoadConfig (std::wstring name = L"tbfix"); +void TBF_SaveConfig (std::wstring name = L"tbfix", + bool close_config = false); + +#endif /* __TBF__CONFIG_H__ */ \ No newline at end of file diff --git a/include/framerate.h b/include/framerate.h new file mode 100644 index 0000000..46d29db --- /dev/null +++ b/include/framerate.h @@ -0,0 +1,107 @@ +/** + * This file is part of Tales of Zestiria "Fix". + * + * Tales of Zestiria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Zestiria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Zestiria "Fix". + * + * If not, see . + * +**/ +#ifndef __TZF__FRAMERATE_H__ +#define __TZF__FRAMERATE_H__ + +#include +#include + +#include "command.h" + +namespace tzf +{ + namespace FrameRateFix + { + void Init (void); + void Shutdown (void); + + + // Called whenever the engine finishes a frame + void RenderTick (void); + + + // Determine the appropriate value for TickScale + long CalcTickScale (double elapsed_ms); + + + // + // At key points during the game, we need to disable the code that + // cuts timing in half. These places will be wrapped by calls to + // these. + // + void Begin30FPSEvent (void); + void End30FPSEvent (void); + + void SetFPS (int fps); + + class CommandProcessor : public SK_IVariableListener { + public: + CommandProcessor (void); + + virtual bool OnVarChange (SK_IVariable* var, void* val = NULL); + + static CommandProcessor* getInstance (void) + { + if (pCommProc == NULL) + pCommProc = new CommandProcessor (); + + return pCommProc; + } + + protected: + SK_IVariable* tick_scale_; + + private: + static CommandProcessor* pCommProc; + }; + + + // True if the game is running in fullscreen + extern bool fullscreen; + + // True if the game is being framerate limited by the DRIVER + extern bool driver_limit_setup; + + // True if the executable has been modified (at run-time) to allow 60 FPS + extern bool variable_speed_installed; + + // This is actually setup in the SK DLL that loads this one + extern uint32_t target_fps; + + // Store the original unmodifed game instructions + extern uint8_t old_speed_reset_code2 [7]; + extern uint8_t old_limiter_instruction [6]; + + // Cache the game's tick scale for timing -- this can be changed without + // our knowledge, so this is more or less a hint rather than a rule + extern int32_t tick_scale; + + // Prevent mult-threaded shenanigans + extern CRITICAL_SECTION alter_speed_cs; + + // Hold references to these DLLs + extern HMODULE bink_dll; + extern HMODULE kernel32_dll; + } +} + +#endif /* __TZF__FRAMERATE_H__ */ \ No newline at end of file diff --git a/include/general_io.h b/include/general_io.h new file mode 100644 index 0000000..82a0290 --- /dev/null +++ b/include/general_io.h @@ -0,0 +1,35 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#ifndef __TBF__GENERAL_IO_H__ +#define __TBF__GENERAL_IO_H__ + +namespace tbf +{ + namespace FileIO + { + void Init (void); + void Shutdown (void); + } +} + +#endif /* __TBF__GENERAL_IO_H__ */ diff --git a/include/hook.h b/include/hook.h new file mode 100644 index 0000000..a0303f2 --- /dev/null +++ b/include/hook.h @@ -0,0 +1,121 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#ifndef __TBF__HOOK_H__ +#define __TBF__HOOK_H__ + +// MinHook Error Codes. +typedef enum MH_STATUS +{ + // Unknown error. Should not be returned. + MH_UNKNOWN = -1, + + // Successful. + MH_OK = 0, + + // MinHook is already initialized. + MH_ERROR_ALREADY_INITIALIZED, + + // MinHook is not initialized yet, or already uninitialized. + MH_ERROR_NOT_INITIALIZED, + + // The hook for the specified target function is already created. + MH_ERROR_ALREADY_CREATED, + + // The hook for the specified target function is not created yet. + MH_ERROR_NOT_CREATED, + + // The hook for the specified target function is already enabled. + MH_ERROR_ENABLED, + + // The hook for the specified target function is not enabled yet, or already + // disabled. + MH_ERROR_DISABLED, + + // The specified pointer is invalid. It points the address of non-allocated + // and/or non-executable region. + MH_ERROR_NOT_EXECUTABLE, + + // The specified target function cannot be hooked. + MH_ERROR_UNSUPPORTED_FUNCTION, + + // Failed to allocate memory. + MH_ERROR_MEMORY_ALLOC, + + // Failed to change the memory protection. + MH_ERROR_MEMORY_PROTECT, + + // The specified module is not loaded. + MH_ERROR_MODULE_NOT_FOUND, + + // The specified function is not found. + MH_ERROR_FUNCTION_NOT_FOUND +} +MH_STATUS; + +typedef const char* LPCSTR; +typedef const wchar_t* LPCWSTR; +typedef void* LPVOID; + +MH_STATUS +__stdcall +TBF_CreateFuncHook ( LPCWSTR pwszFuncName, + LPVOID pTarget, + LPVOID pDetour, + LPVOID *ppOriginal ); + +MH_STATUS +__stdcall +TBF_CreateDLLHook ( LPCWSTR pwszModule, LPCSTR pszProcName, + LPVOID pDetour, LPVOID *ppOriginal, + LPVOID* ppFuncAddr = nullptr ); + +MH_STATUS +__stdcall +TBF_CreateDLLHook2 ( LPCWSTR pwszModule, LPCSTR pszProcName, + LPVOID pDetour, LPVOID *ppOriginal, + LPVOID* ppFuncAddr = nullptr ); + +MH_STATUS +__stdcall +TBF_EnableHook (LPVOID pTarget); + +MH_STATUS +__stdcall +TBF_DisableHook (LPVOID pTarget); + +MH_STATUS +__stdcall +TBF_RemoveHook (LPVOID pTarget); + +MH_STATUS +__stdcall +TBF_ApplyQueuedHooks (void); + +MH_STATUS +__stdcall +TBF_Init_MinHook (void); + +MH_STATUS +__stdcall +TBF_UnInit_MinHook (void); + +#endif /* __TBF__HOOK_H__ */ \ No newline at end of file diff --git a/include/ini.h b/include/ini.h new file mode 100644 index 0000000..5a21f58 --- /dev/null +++ b/include/ini.h @@ -0,0 +1,100 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#ifndef __TBF__INI_H__ +#define __TBF__INI_H__ + +#include +#include +#include + +#include + +// {B526D074-2F4D-4BAE-B6EC-11CB3779B199} +static const GUID IID_SK_INISection = +{ 0xb526d074, 0x2f4d, 0x4bae, { 0xb6, 0xec, 0x11, 0xcb, 0x37, 0x79, 0xb1, 0x99 } }; + +interface iSK_INISection : public IUnknown +{ +public: + iSK_INISection (void) { + AddRef (); + } + + iSK_INISection (const wchar_t* section_name) { + AddRef (); + } + + ~iSK_INISection (void) { + Release (); + } + + /*** IUnknown methods ***/ + STDMETHOD ( QueryInterface)(THIS_ REFIID riid, void** ppvObj) = 0; + STDMETHOD_ (ULONG, AddRef) (THIS) = 0; + STDMETHOD_ (ULONG, Release) (THIS) = 0; + + STDMETHOD_ (std::wstring&, get_value) (const wchar_t* key) = 0; + STDMETHOD_ (void, set_name) (const wchar_t* name) = 0; + STDMETHOD_ (bool, contains_key) (const wchar_t* key) = 0; + STDMETHOD_ (void, add_key_value)(const wchar_t* key, const wchar_t* value) = 0; + STDMETHOD_ (bool, remove_key) (const wchar_t* key) = 0; +}; + +// {DD2B1E00-6C14-4659-8B45-FCEF1BC2C724} +static const GUID IID_SK_INI = +{ 0xdd2b1e00, 0x6c14, 0x4659, { 0x8b, 0x45, 0xfc, 0xef, 0x1b, 0xc2, 0xc7, 0x24 } }; + +interface iSK_INI : public IUnknown +{ + typedef const std::map _TSectionMap; + + iSK_INI (const wchar_t* filename) { + AddRef (); + }; + + virtual ~iSK_INI (void) { + Release (); + } + + /*** IUnknown methods ***/ + STDMETHOD ( QueryInterface)(THIS_ REFIID riid, void** ppvObj) = 0; + STDMETHOD_ (ULONG, AddRef) (THIS) = 0; + STDMETHOD_ (ULONG, Release) (THIS) = 0; + + STDMETHOD_ (void, parse) (THIS) = 0; + STDMETHOD_ (void, import) (THIS_ const wchar_t* import_data) = 0; + STDMETHOD_ (void, write) (THIS_ const wchar_t* fname) = 0; + + STDMETHOD_ (_TSectionMap&, get_sections) (THIS) = 0; + STDMETHOD_ (iSK_INISection&, get_section) (const wchar_t* section) = 0; + STDMETHOD_ (bool, contains_section)(const wchar_t* section) = 0; + STDMETHOD_ (bool, remove_section) (const wchar_t* section) = 0; + + STDMETHOD_ (iSK_INISection&, get_section_f) ( THIS_ _In_z_ _Printf_format_string_ + wchar_t const* const _Format, + ... ) = 0; +}; + +iSK_INI* +TBF_CreateINI (const wchar_t* const wszName); + +#endif diff --git a/include/keyboard.h b/include/keyboard.h new file mode 100644 index 0000000..8d36b43 --- /dev/null +++ b/include/keyboard.h @@ -0,0 +1,39 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#ifndef __TBF__KEYBOARD_H__ +#define __TBF__KEYBOARD_H__ + +#include +#include + +namespace tbf { + namespace KeyboardFix { + void Init (void); + void Shutdown (void); + + extern std::set remapped_scancodes; + extern std::vector > swapped_keys; + } +} + +#endif /* __TBF__KEYBOARD_H__ */ \ No newline at end of file diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..0d50185 --- /dev/null +++ b/include/log.h @@ -0,0 +1,90 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#ifndef __TBF__LOG_H__ +#define __TBF__LOG_H__ + +#include + +// {A4BF1773-CAAB-48F3-AD88-C2AB5C23BD6F} +static const GUID IID_SK_Logger = +{ 0xa4bf1773, 0xcaab, 0x48f3, { 0xad, 0x88, 0xc2, 0xab, 0x5c, 0x23, 0xbd, 0x6f } }; + +interface iSK_Logger : public IUnknown +{ + class AutoClose + { + friend interface iSK_Logger; + public: + ~AutoClose (void) + { + if (log_ != nullptr) + log_->close (); + + log_ = nullptr; + } + + protected: + AutoClose (iSK_Logger* log) : log_ (log) { } + + private: + iSK_Logger* log_; + }; + + AutoClose auto_close (void) { + return AutoClose (this); + } + + iSK_Logger (void) { + AddRef (); + } + + virtual ~iSK_Logger (void) { + Release (); + } + + /*** IUnknown methods ***/ + STDMETHOD ( QueryInterface)(THIS_ REFIID riid, void** ppvObj) = 0; + STDMETHOD_ (ULONG, AddRef) (THIS) = 0; + STDMETHOD_ (ULONG, Release) (THIS) = 0; + + STDMETHOD_ (bool, init)(THIS_ const wchar_t* const wszFilename, + const wchar_t* const wszMode ) = 0; + STDMETHOD_ (void, close)(THIS) = 0; + + STDMETHOD_ (void, LogEx)(THIS_ bool _Timestamp, + _In_z_ _Printf_format_string_ + wchar_t const* const _Format, + ... ) = 0; + STDMETHOD_ (void, Log) (THIS_ _In_z_ _Printf_format_string_ + wchar_t const* const _Format, + ... ) = 0; + STDMETHOD_ (void, Log) (THIS_ _In_z_ _Printf_format_string_ + char const* const _Format, + ... ) = 0; +}; + +extern iSK_Logger* dll_log; + +iSK_Logger* +TBF_CreateLog (const wchar_t* const wszName); + +#endif /* __TBF__LOG_H__ */ diff --git a/include/parameter.h b/include/parameter.h new file mode 100644 index 0000000..889ef51 --- /dev/null +++ b/include/parameter.h @@ -0,0 +1,216 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#ifndef __TBF__PARAMETER_H__ +#define __TBF__PARAMETER_H__ + +#include "ini.h" + +#include +#include + +namespace tbf +{ +class iParameter +{ +public: + iParameter (void) { + ini = nullptr; + } + + virtual std::wstring get_value_str (void) = 0; + virtual void set_value_str (std::wstring str) = 0; + + // Read value from INI + bool load (void) + { + if (ini != nullptr) { + iSK_INISection& section = ini->get_section (ini_section.c_str ()); + + if (section.contains_key (ini_key.c_str ())) { + set_value_str (section.get_value (ini_key.c_str ())); + return true; + } + } + + return false; + } + + // Store value in INI and/or XML + bool store (void) + { + bool ret = false; + + if (ini != nullptr) { + iSK_INISection& section = ini->get_section (ini_section.c_str ()); + + // If this operation actually creates a section, we need to make sure + // that section has a name! + section.set_name (ini_section.c_str ()); + + if (section.contains_key (ini_key.c_str ())) { + section.get_value (ini_key.c_str ()) = get_value_str ().c_str (); + ret = true; + } + + // Add this key/value if it doesn't already exist. + else { + section.add_key_value (ini_key.c_str (), get_value_str ().c_str ()); + ret = true;// +1; + } + } + + return ret; + } + + void register_to_ini (iSK_INI* file, std::wstring section, std::wstring key) + { + ini = file; + ini_section = section; + ini_key = key; + } + +protected: +private: + iSK_INI* ini; + std::wstring ini_section; + std::wstring ini_key; +}; + +template +class Parameter : public iParameter { +public: + virtual std::wstring get_value_str (void) = 0; + virtual _T get_value (void) = 0; + + virtual void set_value (_T val) = 0; + virtual void set_value_str (std::wstring str) = 0; + + virtual void store (_T val) = 0; + virtual void store_str (std::wstring str) = 0; + + virtual bool load (_T& ref) = 0; + +protected: + _T value; +}; + +class ParameterInt : public Parameter +{ +public: + std::wstring get_value_str (void); + int get_value (void); + + void set_value (int val); + void set_value_str (std::wstring str); + + void store (int val); + void store_str (std::wstring str); + + bool load (int& ref); + +protected: + int value; +}; + +class ParameterInt64 : public Parameter +{ +public: + std::wstring get_value_str (void); + int64_t get_value (void); + + void set_value (int64_t val); + void set_value_str (std::wstring str); + + void store (int64_t val); + void store_str (std::wstring str); + + bool load (int64_t& ref); + +protected: + int64_t value; +}; + +class ParameterBool : public Parameter +{ +public: + std::wstring get_value_str (void); + bool get_value (void); + + void set_value (bool val); + void set_value_str (std::wstring str); + + void store (bool val); + void store_str (std::wstring str); + + bool load (bool& ref); + +protected: + bool value; +}; + +class ParameterFloat : public Parameter +{ +public: + std::wstring get_value_str (void); + float get_value (void); + + void set_value (float val); + void set_value_str (std::wstring str); + + void store (float val); + void store_str (std::wstring str); + + bool load (float& ref); + +protected: + float value; +}; + +class ParameterStringW : public Parameter +{ +public: + std::wstring get_value_str (void); + std::wstring get_value (void); + + void set_value (std::wstring str); + void set_value_str (std::wstring str); + + void store (std::wstring val); + void store_str (std::wstring str); + + bool load (std::wstring& ref); + + +protected: + std::wstring value; +}; + +class ParameterFactory { +public: + template iParameter* create_parameter (const wchar_t* name); +protected: +private: + std::vector params; +}; +} + +#endif /* __TBF__PARAMETER_H__ */ diff --git a/include/render.h b/include/render.h new file mode 100644 index 0000000..f5638c6 --- /dev/null +++ b/include/render.h @@ -0,0 +1,221 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#ifndef __TBF__RENDER_H__ +#define __TBF__RENDER_H__ + +#include "command.h" + +struct IDirect3DDevice9; +struct IDirect3DSurface9; + +#include + +namespace tbf +{ + namespace RenderFix + { + void Init (); + void Shutdown (); + + class CommandProcessor : public SK_IVariableListener { + public: + CommandProcessor (void); + + virtual bool OnVarChange (SK_IVariable* var, void* val = NULL); + + static CommandProcessor* getInstance (void) + { + if (pCommProc == NULL) + pCommProc = new CommandProcessor (); + + return pCommProc; + } + + protected: + SK_IVariable* fovy_; + SK_IVariable* aspect_ratio_; + + private: + static CommandProcessor* pCommProc; + }; + + extern uint32_t width; + extern uint32_t height; + + extern IDirect3DSurface9* pPostProcessSurface; + extern IDirect3DDevice9* pDevice; + extern HWND hWndDevice; + + extern uint32_t dwRenderThreadID; + extern bool bink; // True if a bink video is playing + + extern HMODULE d3dx9_43_dll; + extern HMODULE user32_dll; + } +} + +struct game_state_t { + BYTE* base_addr = (BYTE *)0x2130309; + bool in_skit = false; + + struct data_t { + /* 0 */ BYTE Title; + /* 1 */ BYTE OpeningMovie; + /* 2 */ BYTE Game; + /* 3 */ BYTE GamePause; + /* 4 */ BYTE Loading0; + /* 5 */ BYTE Loading1; + /* 6 */ BYTE Explanation; + /* 7 */ BYTE Menu; + /* 8 */ BYTE Unknown0; + /* 9 */ BYTE Unknown1; + /* 10 */ BYTE Unknown2; + /* 11 */ BYTE Battle; + /* 12 */ BYTE BattlePause; + /* 13 */ BYTE BattleSummary; + /* 14 */ BYTE Unknown3; + /* 15 */ BYTE Unknown4; + /* 16 */ BYTE Unknown5; + /* 17 */ BYTE Unknown6; + /* 18 */ BYTE Unknown7; + /* 19 */ BYTE Cutscene; + /* 20 */ BYTE Unknown8; + /* 21 */ BYTE Unknown9; + /* 22 */ BYTE Unknown10; + /* 23 */ BYTE Unknown11; + /* 24 */ BYTE Unknown12; + /* 25 */ BYTE Unknown13; + /* 26 */ BYTE Unknown14; + /* 27 */ BYTE Unknown15; + /* 28 */ BYTE Unknown16; + /* 29 */ BYTE Unknown17; + /* 30 */ BYTE GameMenu; + }; + +#if 0 + BYTE* Title = (BYTE *) base_addr; // Title + BYTE* OpeningMovie = (BYTE *)(base_addr + 1); // Opening Movie + + BYTE* Game = (BYTE *)(base_addr + 2); // Game + BYTE* GamePause = (BYTE *)(base_addr + 3); // Game Pause + + SHORT* Loading = (SHORT *)(base_addr + 4); // Why are there 2 states for this? + + BYTE* Explanation = (BYTE *)(base_addr + 6); // Explanation (+ Bink)? + BYTE* Menu = (BYTE *)(base_addr + 7); // Menu + + BYTE* Unknown0 = (BYTE *)(base_addr + 8); // Unknown + BYTE* Unknown1 = (BYTE *)(base_addr + 9); // Unknown - Appears to be battle related + BYTE* Unknown2 = (BYTE *)(base_addr + 10); // Unknown + + BYTE* Battle = (BYTE *)(base_addr + 11); // Battle + BYTE* BattlePause = (BYTE *)(base_addr + 12); // Battle Pause + + BYTE* Unknown3 = (BYTE *)(base_addr + 13); // Unknown + BYTE* Unknown4 = (BYTE *)(base_addr + 14); // Unknown + BYTE* Unknown5 = (BYTE *)(base_addr + 15); // Unknown + BYTE* Unknown6 = (BYTE *)(base_addr + 16); // Unknown + BYTE* Unknown7 = (BYTE *)(base_addr + 17); // Unknown + BYTE* Unknown8 = (BYTE *)(base_addr + 18); // Unknown + + BYTE* Cutscene = (BYTE *)(base_addr + 19); // Cutscene +#endif + + bool inBattle (void) { return ((data_t *)base_addr)->Battle & 0x1; } + bool inCutscene (void) { return ((data_t *)base_addr)->Cutscene & 0x1; } + bool inExplanation (void) { return ((data_t *)base_addr)->Explanation & 0x1; } + bool inSkit (void) { return in_skit; } + bool inMenu (void) { return ((data_t *)base_addr)->Menu & 0x1; } + bool isLoading (void) { return (((data_t *)base_addr)->Loading0 & 0x1) || + (((data_t *)base_addr)->Loading1 & 0x1); } + + bool hasFixedAspect (void) { + if (((data_t *)base_addr)->OpeningMovie || + ((data_t *)base_addr)->Title || + ((data_t *)base_addr)->Menu || + in_skit) + return true; + return false; + } + bool needsFixedMouseCoords(void) { + return (((data_t *)base_addr)->GamePause || + ((data_t *)base_addr)->Menu || + ((data_t *)base_addr)->BattlePause || + ((data_t *)base_addr)->Title); + } +} static game_state; + +typedef HRESULT (STDMETHODCALLTYPE *BeginScene_pfn)( + IDirect3DDevice9 *This +); + +typedef HRESULT (STDMETHODCALLTYPE *EndScene_pfn)( + IDirect3DDevice9 *This +); + +#include + +typedef HRESULT (STDMETHODCALLTYPE *DrawPrimitive_pfn) +( + IDirect3DDevice9 *This, + D3DPRIMITIVETYPE PrimitiveType, + UINT StartVertex, + UINT PrimitiveCount +); + +typedef HRESULT (STDMETHODCALLTYPE *DrawIndexedPrimitive_pfn) +( + IDirect3DDevice9 *This, + D3DPRIMITIVETYPE Type, + INT BaseVertexIndex, + UINT MinVertexIndex, + UINT NumVertices, + UINT startIndex, + UINT primCount +); + +typedef HRESULT (STDMETHODCALLTYPE *DrawPrimitiveUP_pfn) +( + IDirect3DDevice9 *This, + D3DPRIMITIVETYPE PrimitiveType, + UINT PrimitiveCount, + const void *pVertexStreamZeroData, + UINT VertexStreamZeroStride +); + +typedef HRESULT (STDMETHODCALLTYPE *DrawIndexedPrimitiveUP_pfn) +( + IDirect3DDevice9 *This, + D3DPRIMITIVETYPE PrimitiveType, + UINT MinVertexIndex, + UINT NumVertices, + UINT PrimitiveCount, + const void *pIndexData, + D3DFORMAT IndexDataFormat, + const void *pVertexStreamZeroData, + UINT VertexStreamZeroStride +); + + + +#endif /* __TZF__RENDER_H__ */ \ No newline at end of file diff --git a/include/scanner.h b/include/scanner.h new file mode 100644 index 0000000..1db517b --- /dev/null +++ b/include/scanner.h @@ -0,0 +1,30 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#ifndef __TZF__SCANNER_H__ +#define __TZF__SCANNER_H__ + +#include + +void* TBF_Scan (uint8_t* pattern, size_t len, uint8_t* mask = nullptr); + +#endif /* __TZF__SCANNER_H__ */ \ No newline at end of file diff --git a/include/sound.h b/include/sound.h new file mode 100644 index 0000000..3968e4b --- /dev/null +++ b/include/sound.h @@ -0,0 +1,73 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#ifndef __TBF__SOUND_H__ +#define __TBF__SOUND_H__ + +#include + +#include "command.h" + +namespace tbf +{ + namespace SoundFix + { + void Init (void); + void Shutdown (void); + + class CommandProcessor : public SK_IVariableListener { + public: + CommandProcessor (void); + + virtual bool OnVarChange (SK_IVariable* var, void* val = NULL); + + static CommandProcessor* getInstance (void) + { + if (pCommProc == NULL) + pCommProc = new CommandProcessor (); + + return pCommProc; + } + + protected: + SK_IVariable* sample_rate_; + SK_IVariable* channels_; + SK_IVariable* enable_; + SK_IVariable* compatibility_; + + private: + static CommandProcessor* pCommProc; + }; + + // True once the game has initialized sound + extern bool wasapi_init; + + extern WAVEFORMATEX snd_core_fmt; + extern WAVEFORMATEX snd_bink_fmt; + extern WAVEFORMATEX snd_device_fmt; + + // Hold references to these DLLs + extern HMODULE dsound_dll; + extern HMODULE ole32_dll; + } +} + +#endif /* __TBF__SOUND_H__ */ \ No newline at end of file diff --git a/include/steam.h b/include/steam.h new file mode 100644 index 0000000..4f6ea61 --- /dev/null +++ b/include/steam.h @@ -0,0 +1,63 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#ifndef __TBF__STEAM_H__ +#define __TBF__STEAM_H__ + +#include "command.h" + +namespace tbf +{ + namespace SteamFix + { + void Init (void); + void Shutdown (void); + + void SetOverlayState (bool active); + + class CommandProcessor : public SK_IVariableListener { + public: + CommandProcessor (void); + + virtual bool OnVarChange (SK_IVariable* var, void* val = NULL); + + static CommandProcessor* getInstance (void) + { + if (pCommProc == NULL) + pCommProc = new CommandProcessor (); + + return pCommProc; + } + + protected: + SK_IVariable* allow_broadcasts_; + + private: + static CommandProcessor* pCommProc; + }; + + // Hold a reference to the Steam DLL + extern HMODULE steam_dll; + } +} + + +#endif /* __TBF__STEAM_H__ */ \ No newline at end of file diff --git a/include/textures.h b/include/textures.h new file mode 100644 index 0000000..d13f557 --- /dev/null +++ b/include/textures.h @@ -0,0 +1,470 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#ifndef __TBFIX__TEXTURES_H__ +#define __TBFIX__TEXTURES_H__ + +#include "log.h" +extern iSK_Logger* tex_log; + +#include "render.h" +#include + +#include +#include + +interface ISKTextureD3D9; + +namespace tbf { +namespace RenderFix { +#if 0 + class Sampler { + int id; + IDirect3DTexture9* pTex; + }; +#endif + + struct tbf_draw_states_s { + bool has_aniso = false; // Has he game even once set anisotropy?! + int max_aniso = 4; + bool has_msaa = false; + bool use_msaa = true; // Allow MSAA toggle via console + // without changing the swapchain. + D3DVIEWPORT9 vp = { 0 }; + bool postprocessing = false; + bool fullscreen = false; + + DWORD srcblend = 0; + DWORD dstblend = 0; + DWORD srcalpha = 0; // Separate Alpha Blend Eq: Src + DWORD dstalpha = 0; // Separate Alpha Blend Eq: Dst + bool alpha_test = false; // Test Alpha? + DWORD alpha_ref = 0; // Value to test. + bool zwrite = false; // Depth Mask + + int last_vs_vec4 = 0; // Number of vectors in the last call to + // set vertex shader constant... + + int draws = 0; // Number of draw calls + int frames = 0; + bool cegui_active = false; + } extern draw_state; + + + extern std::set active_samplers; + + class Texture { + public: + Texture (void) { + crc32 = 0; + size = 0; + refs = 0; + load_time = 0.0f; + d3d9_tex = nullptr; + } + + uint32_t crc32; + size_t size; + int refs; + float load_time; + ISKTextureD3D9* d3d9_tex; + }; + + struct frame_texture_t { + const uint32_t crc32_corner = 0x6465f296; + const uint32_t crc32_side = 0xace25896; + + IDirect3DBaseTexture9* tex_corner = (IDirect3DBaseTexture9 *)1; + IDirect3DBaseTexture9* tex_side = (IDirect3DBaseTexture9 *)1; + + bool in_use = false; + } extern cutscene_frame; + + struct pad_buttons_t { + const uint32_t crc32_ps3 = 0x8D7A5256; + const uint32_t crc32_xbox = 0x74F5352D; + + IDirect3DTexture9* tex_ps3 = nullptr; + IDirect3DTexture9* tex_xbox = nullptr; + } extern pad_buttons; + + class TextureManager { + public: + void Init (void); + void Shutdown (void); + + void removeTexture (ISKTextureD3D9* pTexD3D9); + + tbf::RenderFix::Texture* getTexture (uint32_t crc32); + void addTexture (uint32_t crc32, tbf::RenderFix::Texture* pTex, size_t size); + + // Record a cached reference + void refTexture (tbf::RenderFix::Texture* pTex); + + void reset (void); + void purge (void); // WIP + + int numTextures (void) { + return textures.size (); + } + int numInjectedTextures (void); + + int64_t cacheSizeTotal (void); + int64_t cacheSizeBasic (void); + int64_t cacheSizeInjected (void); + + int numMSAASurfs (void); + + void addInjected (size_t size) { + InterlockedIncrement (&injected_count); + InterlockedAdd64 (&injected_size, size); + } + + std::string osdStats (void) { return osd_stats; } + void updateOSD (void); + + private: + std::unordered_map textures; + float time_saved = 0.0f; + ULONG hits = 0UL; + + LONG64 basic_size = 0LL; + LONG64 injected_size = 0LL; + ULONG injected_count = 0UL; + + std::string osd_stats = ""; + + CRITICAL_SECTION cs_cache; + } extern tex_mgr; +} +} + +#pragma comment (lib, "dxguid.lib") + +const GUID IID_SKTextureD3D9 = { 0xace1f81b, 0x5f3f, 0x45f4, 0xbf, 0x9f, 0x1b, 0xaf, 0xdf, 0xba, 0x11, 0x9b }; + +interface ISKTextureD3D9 : public IDirect3DTexture9 +{ +public: + ISKTextureD3D9 (IDirect3DTexture9 **ppTex, SIZE_T size, uint32_t crc32) { + pTexOverride = nullptr; + can_free = true; + override_size = 0; + last_used.QuadPart + = 0ULL; + pTex = *ppTex; + *ppTex = this; + tex_size = size; + tex_crc32 = crc32; + must_block = false; + refs = 1; + }; + + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) { + if (IsEqualGUID (riid, IID_SKTextureD3D9)) { + return S_OK; + } + +#if 1 + if ( IsEqualGUID (riid, IID_IUnknown) || + IsEqualGUID (riid, IID_IDirect3DTexture9) || + IsEqualGUID (riid, IID_IDirect3DBaseTexture9) ) + { + AddRef (); + *ppvObj = this; + return S_OK; + } + + return E_FAIL; +#else + return pTex->QueryInterface (riid, ppvObj); +#endif + } + STDMETHOD_(ULONG,AddRef)(THIS) { + ULONG ret = InterlockedIncrement (&refs); + + can_free = false; + + return ret; + } + STDMETHOD_(ULONG,Release)(THIS) { + ULONG ret = InterlockedDecrement (&refs); + + if (ret == 1) { + can_free = true; + } + + if (ret == 0) { + // Does not delete this immediately; defers the + // process until the next cached texture load. + tbf::RenderFix::tex_mgr.removeTexture (this); + } + + return ret; + } + + /*** IDirect3DBaseTexture9 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice9** ppDevice) { + tex_log->Log (L"[ Tex. Mgr ] ISKTextureD3D9::GetDevice (%ph)", ppDevice); + return pTex->GetDevice (ppDevice); + } + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::SetPrivateData (%x, %ph, %lu, %x)", + refguid, + pData, + SizeOfData, + Flags ); + return pTex->SetPrivateData (refguid, pData, SizeOfData, Flags); + } + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GetPrivateData (%x, %ph, %lu)", + refguid, + pData, + *pSizeOfData ); + + return pTex->GetPrivateData (refguid, pData, pSizeOfData); + } + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::FreePrivateData (%x)", + refguid ); + + return pTex->FreePrivateData (refguid); + } + STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::SetPriority (%lu)", + PriorityNew ); + + return pTex->SetPriority (PriorityNew); + } + STDMETHOD_(DWORD, GetPriority)(THIS) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GetPriority ()" ); + + return pTex->GetPriority (); + } + STDMETHOD_(void, PreLoad)(THIS) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::PreLoad ()" ); + + pTex->PreLoad (); + } + STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GetType ()" ); + + return pTex->GetType (); + } + STDMETHOD_(DWORD, SetLOD)(THIS_ DWORD LODNew) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::SetLOD (%lu)", + LODNew ); + + return pTex->SetLOD (LODNew); + } + STDMETHOD_(DWORD, GetLOD)(THIS) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GetLOD ()" ); + + return pTex->GetLOD (); + } + STDMETHOD_(DWORD, GetLevelCount)(THIS) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GetLevelCount ()" ); + + return pTex->GetLevelCount (); + } + STDMETHOD(SetAutoGenFilterType)(THIS_ D3DTEXTUREFILTERTYPE FilterType) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::SetAutoGenFilterType (%x)", + FilterType ); + + return pTex->SetAutoGenFilterType (FilterType); + } + STDMETHOD_(D3DTEXTUREFILTERTYPE, GetAutoGenFilterType)(THIS) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GetAutoGenFilterType ()" ); + + return pTex->GetAutoGenFilterType (); + } + STDMETHOD_(void, GenerateMipSubLevels)(THIS) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GenerateMipSubLevels ()" ); + + pTex->GenerateMipSubLevels (); + } + STDMETHOD(GetLevelDesc)(THIS_ UINT Level,D3DSURFACE_DESC *pDesc) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GetLevelDesc (%lu, %ph)", + Level, + pDesc ); + return pTex->GetLevelDesc (Level, pDesc); + } + STDMETHOD(GetSurfaceLevel)(THIS_ UINT Level,IDirect3DSurface9** ppSurfaceLevel) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::GetSurfaceLevel (%lu, %ph)", + Level, + ppSurfaceLevel ); + + return pTex->GetSurfaceLevel (Level, ppSurfaceLevel); + } + STDMETHOD(LockRect)(THIS_ UINT Level,D3DLOCKED_RECT* pLockedRect,CONST RECT* pRect,DWORD Flags) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::LockRect (%lu, %ph, %ph, %x)", + Level, + pLockedRect, + pRect, + Flags ); + + return pTex->LockRect (Level, pLockedRect, pRect, Flags); + } + STDMETHOD(UnlockRect)(THIS_ UINT Level) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::UnlockRect (%lu)", Level ); + + return pTex->UnlockRect (Level); + } + STDMETHOD(AddDirtyRect)(THIS_ CONST RECT* pDirtyRect) { + tex_log->Log ( L"[ Tex. Mgr ] ISKTextureD3D9::SetDirtyRect (...)" ); + + return pTex->AddDirtyRect (pDirtyRect); + } + + bool can_free; // Whether or not we can free this texture + bool must_block; // Whether or not to draw using this texture before its + // override finishes streaming + + IDirect3DTexture9* pTex; // The original texture data + SSIZE_T tex_size; // Original data size + uint32_t tex_crc32; // Original data checksum + + IDirect3DTexture9* pTexOverride; // The overridden texture data (nullptr if unchanged) + SSIZE_T override_size; // Override data size + + ULONG refs; + LARGE_INTEGER last_used; // The last time this texture was used (for rendering) + // different from the last time referenced, this is + // set when SetTexture (...) is called. +}; + +typedef enum D3DXIMAGE_FILEFORMAT { + D3DXIFF_BMP = 0, + D3DXIFF_JPG = 1, + D3DXIFF_TGA = 2, + D3DXIFF_PNG = 3, + D3DXIFF_DDS = 4, + D3DXIFF_PPM = 5, + D3DXIFF_DIB = 6, + D3DXIFF_HDR = 7, + D3DXIFF_PFM = 8, + D3DXIFF_FORCE_DWORD = 0x7fffffff +} D3DXIMAGE_FILEFORMAT, *LPD3DXIMAGE_FILEFORMAT; + +#define D3DX_DEFAULT ((UINT) -1) +typedef struct D3DXIMAGE_INFO { + UINT Width; + UINT Height; + UINT Depth; + UINT MipLevels; + D3DFORMAT Format; + D3DRESOURCETYPE ResourceType; + D3DXIMAGE_FILEFORMAT ImageFileFormat; +} D3DXIMAGE_INFO, *LPD3DXIMAGE_INFO; +typedef HRESULT (STDMETHODCALLTYPE *D3DXCreateTextureFromFileInMemoryEx_pfn) +( + _In_ LPDIRECT3DDEVICE9 pDevice, + _In_ LPCVOID pSrcData, + _In_ UINT SrcDataSize, + _In_ UINT Width, + _In_ UINT Height, + _In_ UINT MipLevels, + _In_ DWORD Usage, + _In_ D3DFORMAT Format, + _In_ D3DPOOL Pool, + _In_ DWORD Filter, + _In_ DWORD MipFilter, + _In_ D3DCOLOR ColorKey, + _Inout_opt_ D3DXIMAGE_INFO *pSrcInfo, + _Out_opt_ PALETTEENTRY *pPalette, + _Out_ LPDIRECT3DTEXTURE9 *ppTexture +); + +typedef HRESULT (STDMETHODCALLTYPE *D3DXSaveTextureToFile_pfn)( + _In_ LPCWSTR pDestFile, + _In_ D3DXIMAGE_FILEFORMAT DestFormat, + _In_ LPDIRECT3DBASETEXTURE9 pSrcTexture, + _In_opt_ const PALETTEENTRY *pSrcPalette +); + +typedef HRESULT (WINAPI *D3DXSaveSurfaceToFile_pfn) +( + _In_ LPCWSTR pDestFile, + _In_ D3DXIMAGE_FILEFORMAT DestFormat, + _In_ LPDIRECT3DSURFACE9 pSrcSurface, + _In_opt_ const PALETTEENTRY *pSrcPalette, + _In_opt_ const RECT *pSrcRect +); + +typedef HRESULT (STDMETHODCALLTYPE *CreateTexture_pfn) +( + IDirect3DDevice9 *This, + UINT Width, + UINT Height, + UINT Levels, + DWORD Usage, + D3DFORMAT Format, + D3DPOOL Pool, + IDirect3DTexture9 **ppTexture, + HANDLE *pSharedHandle +); + +typedef HRESULT (STDMETHODCALLTYPE *CreateRenderTarget_pfn) +( + IDirect3DDevice9 *This, + UINT Width, + UINT Height, + D3DFORMAT Format, + D3DMULTISAMPLE_TYPE MultiSample, + DWORD MultisampleQuality, + BOOL Lockable, + IDirect3DSurface9 **ppSurface, + HANDLE *pSharedHandle +); + +typedef HRESULT (STDMETHODCALLTYPE *CreateDepthStencilSurface_pfn) +( + IDirect3DDevice9 *This, + UINT Width, + UINT Height, + D3DFORMAT Format, + D3DMULTISAMPLE_TYPE MultiSample, + DWORD MultisampleQuality, + BOOL Discard, + IDirect3DSurface9 **ppSurface, + HANDLE *pSharedHandle +); + +typedef HRESULT (STDMETHODCALLTYPE *SetTexture_pfn)( + _In_ IDirect3DDevice9 *This, + _In_ DWORD Sampler, + _In_ IDirect3DBaseTexture9 *pTexture +); + +typedef HRESULT (STDMETHODCALLTYPE *SetRenderTarget_pfn)( + _In_ IDirect3DDevice9 *This, + _In_ DWORD RenderTargetIndex, + _In_ IDirect3DSurface9 *pRenderTarget +); + +typedef HRESULT (STDMETHODCALLTYPE *SetDepthStencilSurface_pfn)( + _In_ IDirect3DDevice9 *This, + _In_ IDirect3DSurface9 *pNewZStencil +); + + +#endif /* __TBFIX__TEXTURES_H__ */ \ No newline at end of file diff --git a/resource/resource.h b/resource/resource.h new file mode 100644 index 0000000..28f5b8c --- /dev/null +++ b/resource/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by version.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/resource/symbols.def b/resource/symbols.def new file mode 100644 index 0000000..9119050 --- /dev/null +++ b/resource/symbols.def @@ -0,0 +1,7 @@ +LIBRARY "tbfix" + + + +EXPORTS + +SKPlugIn_Init = SKPlugIn_Init @1 \ No newline at end of file diff --git a/resource/version.rc b/resource/version.rc new file mode 100644 index 0000000..79e948b Binary files /dev/null and b/resource/version.rc differ diff --git a/src/command.cpp b/src/command.cpp new file mode 100644 index 0000000..c95711a --- /dev/null +++ b/src/command.cpp @@ -0,0 +1,44 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#include "command.h" + +SK_GetCommandProcessor_pfn SK_GetCommandProcessor = nullptr; + +typedef SK_IVariable* (__stdcall *SK_CreateVar_pfn)( SK_IVariable::VariableType type, + void* var, + SK_IVariableListener *pListener ); +SK_CreateVar_pfn SK_CreateVar = nullptr; + +SK_IVariable* +TBF_CreateVar ( SK_IVariable::VariableType type, + void* var, + SK_IVariableListener *pListener ) +{ + extern HMODULE hInjectorDLL; + + if (SK_CreateVar == nullptr) { + SK_CreateVar = + (SK_CreateVar_pfn)GetProcAddress (hInjectorDLL, "SK_CreateVar"); + } + + return SK_CreateVar (type, var, pListener); +} \ No newline at end of file diff --git a/src/compatibility.cpp b/src/compatibility.cpp new file mode 100644 index 0000000..4f1b71a --- /dev/null +++ b/src/compatibility.cpp @@ -0,0 +1,189 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#include + +#include "hook.h" +#include "log.h" + +typedef HMODULE (WINAPI *LoadLibraryA_pfn)(LPCSTR lpFileName); +typedef HMODULE (WINAPI *LoadLibraryW_pfn)(LPCWSTR lpFileName); + +LoadLibraryA_pfn LoadLibraryA_Original = nullptr; +LoadLibraryW_pfn LoadLibraryW_Original = nullptr; + +typedef HMODULE (WINAPI *LoadLibraryExA_pfn) +( _In_ LPCSTR lpFileName, + _Reserved_ HANDLE hFile, + _In_ DWORD dwFlags +); + +typedef HMODULE (WINAPI *LoadLibraryExW_pfn) +( _In_ LPCWSTR lpFileName, + _Reserved_ HANDLE hFile, + _In_ DWORD dwFlags +); + +LoadLibraryExA_pfn LoadLibraryExA_Original = nullptr; +LoadLibraryExW_pfn LoadLibraryExW_Original = nullptr; + +extern HMODULE hModSelf; + +#include +#pragma comment (lib, "Shlwapi.lib") + +BOOL +BlacklistLibraryW (LPCWSTR lpFileName) +{ +#if 0 + if (StrStrIW (lpFileName, L"ltc_help32") || + StrStrIW (lpFileName, L"ltc_game32")) { + dll_log->Log (L"[Black List] Preventing Raptr's overlay, evil little thing must die!"); + return TRUE; + } + + if (StrStrIW (lpFileName, L"PlayClaw")) { + dll_log->Log (L"[Black List] Incompatible software: PlayClaw disabled"); + return TRUE; + } +#endif + + return FALSE; +} + +BOOL +BlacklistLibraryA (LPCSTR lpFileName) +{ + wchar_t wszWideLibName [MAX_PATH]; + + MultiByteToWideChar (CP_OEMCP, 0x00, lpFileName, -1, wszWideLibName, MAX_PATH); + + return BlacklistLibraryW (wszWideLibName); +} + +HMODULE +WINAPI +LoadLibraryA_Detour (LPCSTR lpFileName) +{ + if (lpFileName == nullptr) + return NULL; + + HMODULE hModEarly = GetModuleHandleA (lpFileName); + + if (hModEarly == NULL && BlacklistLibraryA (lpFileName)) + return NULL; + + HMODULE hMod = LoadLibraryA_Original (lpFileName); + + if (hModEarly != hMod) + dll_log->Log (L"[DLL Loader] Game loaded '%#64hs' ", lpFileName); + + return hMod; +} + +HMODULE +WINAPI +LoadLibraryW_Detour (LPCWSTR lpFileName) +{ + if (lpFileName == nullptr) + return NULL; + + HMODULE hModEarly = GetModuleHandleW (lpFileName); + + if (hModEarly == NULL && BlacklistLibraryW (lpFileName)) + return NULL; + + HMODULE hMod = LoadLibraryW_Original (lpFileName); + + if (hModEarly != hMod) + dll_log->Log (L"[DLL Loader] Game loaded '%#64s' ", lpFileName); + + return hMod; +} + +HMODULE +WINAPI +LoadLibraryExA_Detour ( + _In_ LPCSTR lpFileName, + _Reserved_ HANDLE hFile, + _In_ DWORD dwFlags ) +{ + if (lpFileName == nullptr) + return NULL; + + HMODULE hModEarly = GetModuleHandleA (lpFileName); + + if (hModEarly == NULL && BlacklistLibraryA (lpFileName)) + return NULL; + + HMODULE hMod = LoadLibraryExA_Original (lpFileName, hFile, dwFlags); + + if (hModEarly != hMod && (! ((dwFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE) || + (dwFlags & LOAD_LIBRARY_AS_IMAGE_RESOURCE)))) + dll_log->Log (L"[DLL Loader] Game loaded '%#64hs' ", lpFileName); + + return hMod; +} + +HMODULE +WINAPI +LoadLibraryExW_Detour ( + _In_ LPCWSTR lpFileName, + _Reserved_ HANDLE hFile, + _In_ DWORD dwFlags ) +{ + if (lpFileName == nullptr) + return NULL; + + HMODULE hModEarly = GetModuleHandleW (lpFileName); + + if (hModEarly == NULL && BlacklistLibraryW (lpFileName)) + return NULL; + + HMODULE hMod = LoadLibraryExW_Original (lpFileName, hFile, dwFlags); + + if (hModEarly != hMod && (! ((dwFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE) || + (dwFlags & LOAD_LIBRARY_AS_IMAGE_RESOURCE)))) + dll_log->Log (L"[DLL Loader] Game loaded '%#64s' ", lpFileName); + + return hMod; +} + +void +TBF_InitCompatBlacklist (void) +{ + TBF_CreateDLLHook ( L"kernel32.dll", "LoadLibraryA", + LoadLibraryA_Detour, + (LPVOID*)&LoadLibraryA_Original ); + + TBF_CreateDLLHook ( L"kernel32.dll", "LoadLibraryW", + LoadLibraryW_Detour, + (LPVOID*)&LoadLibraryW_Original ); + + TBF_CreateDLLHook ( L"kernel32.dll", "LoadLibraryExA", + LoadLibraryExA_Detour, + (LPVOID*)&LoadLibraryExA_Original ); + + TBF_CreateDLLHook ( L"kernel32.dll", "LoadLibraryExW", + LoadLibraryExW_Detour, + (LPVOID*)&LoadLibraryExW_Original ); +} \ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..aeb2f7d --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,252 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#include "config.h" +#include "parameter.h" +#include "ini.h" +#include "log.h" + +#include "DLL_VERSION.H" + +extern HMODULE hInjectorDLL; + +std::wstring TBF_VER_STR = TBF_VERSION_STR_W; +std::wstring DEFAULT_BK2 = L"RAW\\MOVIE\\AM_TOZ_OP_001.BK2"; + +static + iSK_INI* dll_ini = nullptr; +tbf_config_t config; + +tbf::ParameterFactory g_ParameterFactory; + +struct { + tbf::ParameterInt* sample_rate; + tbf::ParameterInt* channels; + tbf::ParameterBool* compatibility; + tbf::ParameterBool* enable_fix; +} audio; + +struct { + tbf::ParameterBool* allow_fake_sleep; + tbf::ParameterBool* yield_processor; + tbf::ParameterBool* minimize_latency; + tbf::ParameterInt* speedresetcode_addr; + tbf::ParameterInt* speedresetcode2_addr; + tbf::ParameterInt* speedresetcode3_addr; + tbf::ParameterInt* limiter_branch_addr; + tbf::ParameterBool* disable_limiter; + tbf::ParameterBool* auto_adjust; + tbf::ParameterInt* target; + tbf::ParameterInt* battle_target; + tbf::ParameterBool* battle_adaptive; + tbf::ParameterInt* cutscene_target; +} framerate; + +struct { + tbf::ParameterInt* fovy_addr; + tbf::ParameterInt* aspect_addr; + tbf::ParameterFloat* fovy; + tbf::ParameterFloat* aspect_ratio; + tbf::ParameterBool* aspect_correct_vids; + tbf::ParameterBool* aspect_correction; + tbf::ParameterInt* rescale_shadows; + tbf::ParameterInt* rescale_env_shadows; + tbf::ParameterFloat* postproc_ratio; + tbf::ParameterBool* clear_blackbars; +} render; + +struct { + tbf::ParameterBool* remaster; + tbf::ParameterBool* cache; + tbf::ParameterBool* dump; + tbf::ParameterInt* cache_size; + tbf::ParameterInt* worker_threads; +} textures; + + +struct { + tbf::ParameterBool* allow_broadcasts; +} steam; + + +struct { + tbf::ParameterStringW* swap_keys; +} keyboard; + + +struct { + tbf::ParameterBool* fix_priest; +} lua; + +struct { + tbf::ParameterStringW* intro_video; + tbf::ParameterStringW* version; + tbf::ParameterStringW* injector; +} sys; + +typedef const wchar_t* (__stdcall *SK_GetConfigPath_pfn)(void); +static SK_GetConfigPath_pfn SK_GetConfigPath = nullptr; + +bool +TBF_LoadConfig (std::wstring name) +{ + SK_GetConfigPath = + (SK_GetConfigPath_pfn) + GetProcAddress ( + hInjectorDLL, + "SK_GetConfigPath" + ); + + // Load INI File + wchar_t wszFullName [ MAX_PATH + 2 ] = { L'\0' }; + + lstrcatW (wszFullName, SK_GetConfigPath ()); + lstrcatW (wszFullName, name.c_str ()); + lstrcatW (wszFullName, L".ini"); + dll_ini = TBF_CreateINI (wszFullName); + + + bool empty = dll_ini->get_sections ().empty (); + + // + // Create Parameters + // + + audio.channels = + static_cast + (g_ParameterFactory.create_parameter ( + L"Audio Channels") + ); + audio.channels->register_to_ini ( + dll_ini, + L"TBFIX.Audio", + L"Channels" ); + + audio.sample_rate = + static_cast + (g_ParameterFactory.create_parameter ( + L"Sample Rate") + ); + audio.sample_rate->register_to_ini ( + dll_ini, + L"TBFIX.Audio", + L"SampleRate" ); + + audio.compatibility = + static_cast + (g_ParameterFactory.create_parameter ( + L"Compatibility Mode") + ); + audio.compatibility->register_to_ini ( + dll_ini, + L"TBFIX.Audio", + L"CompatibilityMode" ); + + audio.enable_fix = + static_cast + (g_ParameterFactory.create_parameter ( + L"Enable Fix") + ); + audio.enable_fix->register_to_ini ( + dll_ini, + L"TBFIX.Audio", + L"EnableFix" ); + + + sys.version = + static_cast + (g_ParameterFactory.create_parameter ( + L"Software Version") + ); + sys.version->register_to_ini ( + dll_ini, + L"TBFIX.System", + L"Version" ); + + sys.intro_video = + static_cast + (g_ParameterFactory.create_parameter ( + L"Intro Video") + ); + sys.intro_video->register_to_ini ( + dll_ini, + L"TBFIX.System", + L"IntroVideo" ); + + sys.injector = + static_cast + (g_ParameterFactory.create_parameter ( + L"DLL That Injected Us") + ); + sys.injector->register_to_ini ( + dll_ini, + L"TBFIX.System", + L"Injector" ); + + // + // Load Parameters + // + audio.channels->load ( (int &)config.audio.channels ); + audio.sample_rate->load ( (int &)config.audio.sample_hz ); + audio.compatibility->load ( config.audio.compatibility ); + audio.enable_fix->load ( config.audio.enable_fix ); + + sys.version->load (config.system.version); + sys.intro_video->load (config.system.intro_video); + sys.injector->load (config.system.injector); + + if (empty) + return false; + + return true; +} + +void +TBF_SaveConfig (std::wstring name, bool close_config) +{ + audio.channels->store (config.audio.channels); + audio.sample_rate->store (config.audio.sample_hz); + audio.compatibility->store (config.audio.compatibility); + audio.enable_fix->store (config.audio.enable_fix); + + sys.version->store (TBF_VER_STR); + sys.intro_video->store (config.system.intro_video); + sys.injector->store (config.system.injector); + + wchar_t wszFullName [ MAX_PATH + 2 ] = { L'\0' }; + + lstrcatW ( wszFullName, + SK_GetConfigPath () ); + lstrcatW ( wszFullName, + name.c_str () ); + lstrcatW ( wszFullName, + L".ini" ); + + dll_ini->write (wszFullName); + + if (close_config) { + if (dll_ini != nullptr) { + delete dll_ini; + dll_ini = nullptr; + } + } +} \ No newline at end of file diff --git a/src/dllmain.cpp b/src/dllmain.cpp new file mode 100644 index 0000000..fc38daf --- /dev/null +++ b/src/dllmain.cpp @@ -0,0 +1,217 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#include + +#include "config.h" +#include "log.h" + +#include "sound.h" +#include "framerate.h" +#include "general_io.h" +#include "keyboard.h" +#include "steam.h" +#include "render.h" +#include "scanner.h" + +#include "command.h" + +#include "hook.h" + +#include + +#pragma comment (lib, "kernel32.lib") + +HMODULE hDLLMod = { 0 }; // Handle to SELF +HMODULE hInjectorDLL = { 0 }; // Handle to Special K + +typedef HRESULT (__stdcall *SK_UpdateSoftware_pfn)(const wchar_t* wszProduct); +typedef bool (__stdcall *SK_FetchVersionInfo_pfn)(const wchar_t* wszProduct); + +std::wstring injector_dll; + +typedef void (__stdcall *SK_SetPluginName_pfn)(std::wstring name); +SK_SetPluginName_pfn SK_SetPluginName = nullptr; + +unsigned int +WINAPI +DllThread (LPVOID user) +{ + std::wstring plugin_name = L"Tales of Berseria \"Fix\" v " + TBF_VER_STR; + + dll_log = TBF_CreateLog (L"logs/tbfix.log"); + + dll_log->LogEx ( false, L"------- [Tales of Berseria \"Fix\"] " + L"-------\n" ); + dll_log->Log ( L"tbfix.dll Plug-In\n" + L"=========== (Version: v %s) " + L"===========", + TBF_VER_STR.c_str () ); + + if (! TBF_LoadConfig ()) { + config.audio.channels = 6; + config.audio.sample_hz = 48000; + config.audio.compatibility = false; + config.audio.enable_fix = true; + + config.file_io.capture = false; + + config.steam.allow_broadcasts = false; + + config.lua.fix_priest = true; + + config.render.aspect_ratio = 1.777778f; + config.render.fovy = 0.785398f; + + config.render.postproc_ratio = 1.0f; + config.render.shadow_rescale = -2; + config.render.env_shadow_rescale = 0; + + config.textures.remaster = true; + config.textures.dump = false; + config.textures.cache = true; + + config.system.injector = injector_dll; + + // Save a new config if none exists + TBF_SaveConfig (); + } + + config.system.injector = injector_dll; + + SK_SetPluginName = + (SK_SetPluginName_pfn) + GetProcAddress (hInjectorDLL, "SK_SetPluginName"); + SK_GetCommandProcessor = + (SK_GetCommandProcessor_pfn) + GetProcAddress (hInjectorDLL, "SK_GetCommandProcessor"); + + // + // If this is NULL, the injector system isn't working right!!! + // + if (SK_SetPluginName != nullptr) + SK_SetPluginName (plugin_name); + + if (TBF_Init_MinHook () == MH_OK) { + CoInitializeEx (nullptr, COINIT_MULTITHREADED); + + tbf::SoundFix::Init (); + //tbf::FileIO::Init (); + //tbf::SteamFix::Init (); + //tbf::RenderFix::Init (); + //tbf::FrameRateFix::Init (); + //tbf::KeyboardFix::Init (); + + // Uncomment this when spawning a thread + //CoUninitialize (); + } + + SK_UpdateSoftware_pfn SK_UpdateSoftware = + (SK_UpdateSoftware_pfn) + GetProcAddress ( hInjectorDLL, + "SK_UpdateSoftware" ); + + SK_FetchVersionInfo_pfn SK_FetchVersionInfo = + (SK_FetchVersionInfo_pfn) + GetProcAddress ( hInjectorDLL, + "SK_FetchVersionInfo" ); + + if (! wcsstr (injector_dll.c_str (), L"SpecialK")) { + if ( SK_FetchVersionInfo != nullptr && + SK_UpdateSoftware != nullptr ) { + if (SK_FetchVersionInfo (L"TBF")) { + SK_UpdateSoftware (L"TBF"); + } + } + } + + return 0; +} + +__declspec (dllexport) +BOOL +WINAPI +SKPlugIn_Init (HMODULE hModSpecialK) +{ + wchar_t wszSKFileName [ MAX_PATH + 2] = { L'\0' }; + wszSKFileName [ MAX_PATH ] = L'\0'; + + GetModuleFileName (hModSpecialK, wszSKFileName, MAX_PATH - 1); + + injector_dll = wszSKFileName; + + hInjectorDLL = hModSpecialK; + +#if 1 + DllThread (nullptr); +#else + _beginthreadex ( nullptr, 0, DllThread, nullptr, 0x00, nullptr ); +#endif + + return TRUE; +} + +BOOL +APIENTRY +DllMain (HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID /* lpReserved */) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + { + hDLLMod = hModule; + } break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + { + if (dll_log != nullptr) { + tbf::SoundFix::Shutdown (); + //tbf::FileIO::Shutdown (); + //tbf::SteamFix::Shutdown (); + //tbf::RenderFix::Shutdown (); + //tbf::FrameRateFix::Shutdown (); + //tbf::KeyboardFix::Shutdown (); + + TBF_UnInit_MinHook (); + TBF_SaveConfig (); + + + dll_log->LogEx ( false, L"=========== (Version: v %s) " + L"===========\n", + TBF_VER_STR.c_str () ); + dll_log->LogEx ( true, L"End TBFix Plug-In\n" ); + dll_log->LogEx ( false, L"------- [Tales of Berseria \"Fix\"] " + L"-------\n" ); + + dll_log->close (); + } + } break; + } + + return TRUE; +} \ No newline at end of file diff --git a/src/framerate.cpp b/src/framerate.cpp new file mode 100644 index 0000000..89245b2 --- /dev/null +++ b/src/framerate.cpp @@ -0,0 +1,1002 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#define _CRT_SECURE_NO_WARNINGS + +#include "framerate.h" +#include "config.h" +#include "log.h" +#include "hook.h" +#include "scanner.h" + +#include "priest.lua.h" + +#include "render.h" +#include "textures.h" +#include + +uint32_t TICK_ADDR_BASE = 0x217B464; +// 0x0217B3D4 1.3 + +uint8_t tzf::FrameRateFix::old_speed_reset_code2 [7]; +uint8_t tzf::FrameRateFix::old_limiter_instruction [6]; +int32_t tzf::FrameRateFix::tick_scale = 2; // 30 FPS + +CRITICAL_SECTION tzf::FrameRateFix::alter_speed_cs = { 0 }; + +bool tzf::FrameRateFix::variable_speed_installed = false; +bool tzf::FrameRateFix::fullscreen = false; + +uint32_t tzf::FrameRateFix::target_fps = 30; + +HMODULE tzf::FrameRateFix::bink_dll = 0; +HMODULE tzf::FrameRateFix::kernel32_dll = 0; + +float limiter_tolerance = 0.40f; +int max_latency = 2; +bool wait_for_vblank = true; + +typedef void (WINAPI *Sleep_pfn)(DWORD dwMilliseconds); +Sleep_pfn Sleep_Original = nullptr; + +typedef BOOL(WINAPI *QueryPerformanceCounter_pfn)(_Out_ LARGE_INTEGER *lpPerformanceCount); +QueryPerformanceCounter_pfn QueryPerformanceCounter_Original = nullptr; + +class FramerateLimiter +{ +public: + FramerateLimiter (double target = 60.0) { + init (target); + } + ~FramerateLimiter (void) { + } + + void init (double target) { + ms = 1000.0 / target; + fps = target; + + frames = 0; + + IDirect3DDevice9Ex* d3d9ex = nullptr; + if (tzf::RenderFix::pDevice != nullptr) { + tzf::RenderFix::pDevice->QueryInterface ( + __uuidof (IDirect3DDevice9Ex), + (void **)&d3d9ex ); + } + + QueryPerformanceFrequency (&freq); + + // Align the start to VBlank for minimum input latency + if (d3d9ex != nullptr) { + d3d9ex->SetMaximumFrameLatency (1); + d3d9ex->WaitForVBlank (0); + d3d9ex->SetMaximumFrameLatency (max_latency); + d3d9ex->Release (); + } + + QueryPerformanceCounter_Original (&start); + + next.QuadPart = 0ULL; + time.QuadPart = 0ULL; + last.QuadPart = 0ULL; + + last.QuadPart = start.QuadPart - (ms / 1000.0) * freq.QuadPart; + next.QuadPart = start.QuadPart + (ms / 1000.0) * freq.QuadPart; + } + + void wait (void) { + static bool restart = false; + + frames++; + + QueryPerformanceCounter_Original (&time); + + if ((double)(time.QuadPart - next.QuadPart) / (double)freq.QuadPart / (ms / 1000.0) > (limiter_tolerance * fps)) { + //dll_log->Log ( L" * Frame ran long (%3.01fx expected) - restarting" + //L" limiter...", + //(double)(time.QuadPart - next.QuadPart) / (double)freq.QuadPart / (ms / 1000.0) / fps ); + restart = true; + } + + if (restart) { + frames = 0; + start.QuadPart = time.QuadPart + (ms / 1000.0) * (double)freq.QuadPart; + restart = false; + } + + next.QuadPart = (start.QuadPart + (double)frames * (ms / 1000.0) * (double)freq.QuadPart); + + if (next.QuadPart > 0ULL) { + // If available (Windows 7+), wait on the swapchain + IDirect3DDevice9Ex* d3d9ex = nullptr; + if (tzf::RenderFix::pDevice != nullptr) { + tzf::RenderFix::pDevice->QueryInterface ( + __uuidof (IDirect3DDevice9Ex), + (void **)&d3d9ex ); + } + + while (time.QuadPart < next.QuadPart) { + if (wait_for_vblank && (double)(next.QuadPart - time.QuadPart) > (0.0166667 * (double)freq.QuadPart)) { + if (d3d9ex != nullptr) { + d3d9ex->WaitForVBlank (0); + } + } + + if (GetForegroundWindow () != tzf::RenderFix::hWndDevice && + tzf::FrameRateFix::fullscreen) { + //dll_log->Log (L"[FrameLimit] # Restarting framerate limiter; fullscreen Alt+Tab..."); + restart = true; + break; + } + + QueryPerformanceCounter_Original (&time); + } + + if (d3d9ex != nullptr) + d3d9ex->Release (); + } + + else { + dll_log->Log (L"[FrameLimit] Lost time"); + start.QuadPart += -next.QuadPart; + } + + last.QuadPart = time.QuadPart; + } + + void change_limit (double target) { + init (target); + } + +private: + double ms, fps; + + LARGE_INTEGER start, last, next, time, freq; + + uint32_t frames; +} *limiter = nullptr; + + +typedef D3DPRESENT_PARAMETERS* (__stdcall *SK_SetPresentParamsD3D9_pfn) + (IDirect3DDevice9* device, + D3DPRESENT_PARAMETERS* pparams); +SK_SetPresentParamsD3D9_pfn SK_SetPresentParamsD3D9_Original = nullptr; + +COM_DECLSPEC_NOTHROW +D3DPRESENT_PARAMETERS* +__stdcall +SK_SetPresentParamsD3D9_Detour (IDirect3DDevice9* device, + D3DPRESENT_PARAMETERS* pparams) +{ + D3DPRESENT_PARAMETERS present_params; + + // + // TODO: Figure out what the hell is doing this when RTSS is allowed to use + // custom D3D libs. 1x1@0Hz is obviously NOT for rendering! + // + if ( pparams->BackBufferWidth == 1 && + pparams->BackBufferHeight == 1 && + pparams->FullScreen_RefreshRateInHz == 0 ) { + dll_log->Log (L"[ D3D9 ] * Fake D3D9Ex Device Detected... Ignoring!"); + return SK_SetPresentParamsD3D9_Original (device, pparams); + } + + tzf::RenderFix::pDevice = device; + tzf::RenderFix::pPostProcessSurface = nullptr; + + if (pparams != nullptr) { + memcpy (&present_params, pparams, sizeof D3DPRESENT_PARAMETERS); + + if (device != nullptr) { + dll_log->Log ( L"[ D3D9 ] %% Caught D3D9 Swapchain :: Fullscreen=%s " + L" (%lux%lu@%lu Hz) " + L" [Device Window: 0x%04X]", + pparams->Windowed ? L"False" : + L"True", + pparams->BackBufferWidth, + pparams->BackBufferHeight, + pparams->FullScreen_RefreshRateInHz, + pparams->hDeviceWindow ); + } + + tzf::RenderFix::hWndDevice = pparams->hDeviceWindow; + + tzf::RenderFix::width = present_params.BackBufferWidth; + tzf::RenderFix::height = present_params.BackBufferHeight; + tzf::FrameRateFix::fullscreen = (! pparams->Windowed); + + // Change the Aspect Ratio + char szAspectCommand [64]; + sprintf (szAspectCommand, "AspectRatio %f", (float)tzf::RenderFix::width / (float)tzf::RenderFix::height); + + SK_GetCommandProcessor ()->ProcessCommandLine (szAspectCommand); + } + + return SK_SetPresentParamsD3D9_Original (device, pparams); +} + +bool render_sleep0 = false; +LARGE_INTEGER last_perfCount = { 0 }; + +void +WINAPI +Sleep_Detour (DWORD dwMilliseconds) +{ + if ((! config.framerate.disable_limiter) && GetCurrentThreadId () == tzf::RenderFix::dwRenderThreadID) { + if (dwMilliseconds == 0) + render_sleep0 = true; + else { + render_sleep0 = false; + } + } + + if (config.framerate.yield_processor && dwMilliseconds == 0) + YieldProcessor (); + + if (dwMilliseconds != 0 || config.framerate.allow_fake_sleep) { + Sleep_Original (dwMilliseconds); + } +} + +BOOL +WINAPI +QueryPerformanceCounter_Detour (_Out_ LARGE_INTEGER *lpPerformanceCount) +{ + BOOL ret = QueryPerformanceCounter_Original (lpPerformanceCount); + + DWORD dwThreadId = GetCurrentThreadId (); + + // + // Handle threads that aren't render-related NORMALLY as well as the render + // thread when it DID NOT just voluntarily relinquish its scheduling + // timeslice + // + if (dwThreadId != tzf::RenderFix::dwRenderThreadID || (! render_sleep0) || tzf::RenderFix::bink) { + if (dwThreadId == tzf::RenderFix::dwRenderThreadID) + memcpy (&last_perfCount, lpPerformanceCount, sizeof (LARGE_INTEGER)); + + return ret; + } + + // + // At this point, we're fixing up the thread that throttles the swapchain. + // + render_sleep0 = false; + + LARGE_INTEGER freq; + QueryPerformanceFrequency (&freq); + + // Mess with the numbers slightly to prevent scheduling from wreaking havoc + lpPerformanceCount->QuadPart += (double)freq.QuadPart * ((double)tzf::FrameRateFix::target_fps / 1000.0); + + memcpy (&last_perfCount, lpPerformanceCount, sizeof (LARGE_INTEGER)); + + return ret; +} + +typedef void* (__stdcall *BinkOpen_pfn)(const char* filename, DWORD unknown0); +BinkOpen_pfn BinkOpen_Original = nullptr; + +void* +__stdcall +BinkOpen_Detour ( const char* filename, + DWORD unknown0 ) +{ + // non-null on success + void* bink_ret = nullptr; + static char szBypassName [MAX_PATH] = { '\0' }; + + // Optionally play some other video (or no video)... + if (! _stricmp (filename, "RAW\\MOVIE\\AM_TOZ_OP_001.BK2")) { + dll_log->LogEx (true, L"[IntroVideo] >> Using %ws for Opening Movie ...", + config.system.intro_video.c_str ()); + + sprintf (szBypassName, "%ws", config.system.intro_video.c_str ()); + + bink_ret = BinkOpen_Original (szBypassName, unknown0); + + dll_log->LogEx ( false, + L" %s!\n", + bink_ret != nullptr ? L"Success" : + L"Failed" ); + } else { + bink_ret = BinkOpen_Original (filename, unknown0); + } + + if (bink_ret != nullptr) { + dll_log->Log (L"[FrameLimit] * Disabling TargetFPS -- Bink Video Opened"); + + tzf::RenderFix::bink = true; + tzf::FrameRateFix::Begin30FPSEvent (); + } + + return bink_ret; +} + +typedef void (__stdcall *BinkClose_pfn)(DWORD unknown); +BinkClose_pfn BinkClose_Original = nullptr; + +void +__stdcall +BinkClose_Detour (DWORD unknown) +{ + BinkClose_Original (unknown); + + dll_log->Log (L"[FrameLimit] * Restoring TargetFPS -- Bink Video Closed"); + + tzf::RenderFix::bink = false; + tzf::FrameRateFix::End30FPSEvent (); +} + + + +LPVOID pfnQueryPerformanceCounter = nullptr; +LPVOID pfnSleep = nullptr; +LPVOID pfnSK_SetPresentParamsD3D9 = nullptr; + +// Hook these to properly synch audio subtitles during FMVs +LPVOID pfnBinkOpen = nullptr; +LPVOID pfnBinkClose = nullptr; + +void +TZF_FlushInstructionCache ( LPCVOID base_addr, + size_t code_size ) +{ + FlushInstructionCache ( GetCurrentProcess (), + base_addr, + code_size ); +} + +void +TZF_InjectByteCode ( LPVOID base_addr, + uint8_t* new_code, + size_t code_size, + DWORD permissions, + uint8_t* old_code = nullptr ) +{ + DWORD dwOld; + + VirtualProtect (base_addr, code_size, permissions, &dwOld); + { + if (old_code != nullptr) + memcpy (old_code, base_addr, code_size); + + memcpy (base_addr, new_code, code_size); + } + VirtualProtect (base_addr, code_size, dwOld, &dwOld); + + TZF_FlushInstructionCache (base_addr, code_size); +} + +LPVOID pLuaReturn = nullptr; + +void +__declspec(naked) +TZF_LuaHook (void) +{ + char *name; + size_t *pSz; + char **pBuffer; + + __asm + { + pushad + pushfd + + // save luaL_loadbuffer's stack frame because we need some stuff on it + mov ebx, ebp + + push ebp + mov ebp, esp + sub esp, __LOCAL_SIZE + + // compiler must've optimised away the calling convention here + mov name, eax + + mov eax, ebx + add eax, 0xC + mov pSz, eax + mov eax, ebx + add eax, 0x8 + mov pBuffer, eax + } + +#if 0 + dll_log->Log (L"[ 60 FPS ]Lua script loaded: \"%S\"", name); +#endif + + if (! strcmp (name, "MEP_100_130_010_PF_Script")) { + dll_log->Log (L"[ 60 FPS ] * Replacing priest script..."); + + *pSz = lua_bytecode_priest_size; + *pBuffer = lua_bytecode_priest; + } + + __asm + { + mov esp, ebp + pop ebp + + popfd + popad + + // overwritten instructions from original function + mov ecx, [ebp + 0x8] + mov [esp + 0x4], ecx + + jmp pLuaReturn + } +} + +typedef BOOL (WINAPI *CreateTimerQueueTimer_pfn) +( + _Out_ PHANDLE phNewTimer, + _In_opt_ HANDLE TimerQueue, + _In_ WAITORTIMERCALLBACK Callback, + _In_opt_ PVOID Parameter, + _In_ DWORD DueTime, + _In_ DWORD Period, + _In_ ULONG Flags +); + +CreateTimerQueueTimer_pfn CreateTimerQueueTimer_Original = nullptr; + +BOOL +WINAPI +CreateTimerQueueTimer_Override ( + _Out_ PHANDLE phNewTimer, + _In_opt_ HANDLE TimerQueue, + _In_ WAITORTIMERCALLBACK Callback, + _In_opt_ PVOID Parameter, + _In_ DWORD DueTime, + _In_ DWORD Period, + _In_ ULONG Flags +) +{ + // Fix compliance related issues present in both + // Tales of Symphonia and Zestiria + if (Flags & 0x8) { + Period = 0; + } + + return CreateTimerQueueTimer_Original (phNewTimer, TimerQueue, Callback, Parameter, DueTime, Period, Flags); +} + +void +tzf::FrameRateFix::Init (void) +{ + CommandProcessor* comm_proc = CommandProcessor::getInstance (); + + InitializeCriticalSectionAndSpinCount (&alter_speed_cs, 1000UL); + + target_fps = config.framerate.target; + + TZF_CreateDLLHook2 ( config.system.injector.c_str (), "SK_SetPresentParamsD3D9", + SK_SetPresentParamsD3D9_Detour, + (LPVOID *)&SK_SetPresentParamsD3D9_Original, + &pfnSK_SetPresentParamsD3D9 ); + + bink_dll = LoadLibrary (L"bink2w32.dll"); + + TZF_CreateDLLHook2 ( L"bink2w32.dll", "_BinkOpen@8", + BinkOpen_Detour, + (LPVOID *)&BinkOpen_Original, + &pfnBinkOpen ); + + TZF_CreateDLLHook2 ( L"bink2w32.dll", "_BinkClose@4", + BinkClose_Detour, + (LPVOID *)&BinkClose_Original, + &pfnBinkClose ); + + TZF_CreateDLLHook2 ( config.system.injector.c_str (), "QueryPerformanceCounter_Detour", + QueryPerformanceCounter_Detour, + (LPVOID *)&QueryPerformanceCounter_Original, + (LPVOID *)&pfnQueryPerformanceCounter ); + + TZF_CreateDLLHook2 ( L"kernel32.dll", "CreateTimerQueueTimer", + CreateTimerQueueTimer_Override, + (LPVOID *)&CreateTimerQueueTimer_Original ); + + TZF_ApplyQueuedHooks (); + + if (true) { + if (*((DWORD *)config.framerate.speedresetcode_addr) != 0x428CB08D) { + uint8_t sig [] = { 0x8D, 0xB0, 0x8C, 0x42, + 0x00, 0x00, 0x8D, 0xBB, + 0x8C, 0x42, 0x00, 0x00, + 0xB9, 0x0B, 0x00, 0x00 }; + uintptr_t addr = (uintptr_t)TZF_Scan (sig, 16); + + if (addr != NULL) { + dll_log->Log (L"[FrameLimit] Scanned SpeedResetCode Address: %06Xh", addr); + config.framerate.speedresetcode_addr = addr; + } + else { + dll_log->Log (L"[FrameLimit] >> ERROR: Unable to find SpeedResetCode memory!"); + } + } + + if (*((DWORD *)config.framerate.speedresetcode3_addr) != 0x02) { + uint8_t sig [] = { 0x0F, 0x95, 0xC0, 0x3A, + 0xC3, 0x74, 0x17, 0xB8, + 0x02, 0x00, 0x00, 0x00 }; + uintptr_t addr = (uintptr_t)TZF_Scan (sig, 12); + + if (addr != NULL && *((DWORD *)((uint8_t *)addr + 8)) == 0x2) { + dll_log->Log (L"[FrameLimit] Scanned SpeedResetCode3 Address: %06Xh", addr + 8); + config.framerate.speedresetcode3_addr = addr + 8; + } + else { + dll_log->Log (L"[FrameLimit] >> ERROR: Unable to find SpeedResetCode3 memory!"); + } + } + + dll_log->LogEx (true, L"[FrameLimit] * Installing variable rate simulation... "); + + DWORD dwOld; + + // + // original code: + // + // >> lea esi, [eax+0000428C] + // >> lea edi, [ebx+0000428C] + // >> mov ecx, 11 + // rep movsd + // + // we want to skip the first two dwords + // + VirtualProtect((LPVOID)config.framerate.speedresetcode_addr, 17, PAGE_EXECUTE_READWRITE, &dwOld); + *((DWORD *)(config.framerate.speedresetcode_addr + 2)) += 8; + *((DWORD *)(config.framerate.speedresetcode_addr + 8)) += 8; + *((DWORD *)(config.framerate.speedresetcode_addr + 13)) -= 2; + VirtualProtect((LPVOID)config.framerate.speedresetcode_addr, 17, dwOld, &dwOld); + + TZF_FlushInstructionCache ((LPCVOID)config.framerate.speedresetcode_addr, 17); + + uint8_t mask [] = { 0xff, 0xff, 0xff, // cmp [ebx+28h], eax + 0, 0, // jz short <...> + 0xff, 0xff, 0xff, // cmp eax, 2 + 0, 0, // jl short <...> + 0xff, 0, 0, 0, 0, // mov , eax + 0xff, 0, 0, 0, 0, // mov , eax + 0xff, 0xff, 0xff // mov [ebx+28h], eax + }; + + uint8_t sig [] = { 0x39, 0x43, 0x28, // cmp [ebx+28h], eax + 0x74, 0x12, // jz short <...> + 0x83, 0xF8, 0x02, // cmp eax, 2 + 0x7C, 0x0D, // jl short <...> + 0xA3, 0x64, 0xB4, 0x17, 0x02, // mov , eax + 0xA3, 0x68, 0xB4, 0x17, 0x02, // mov , eax + 0x89, 0x43, 0x28 // mov [ebx+28h], eax + }; + + if (*((DWORD *)config.framerate.speedresetcode2_addr) != 0x0F8831274) { + uintptr_t addr = (uintptr_t)TZF_Scan (sig, 23, mask); + + if (addr != NULL) { + config.framerate.speedresetcode2_addr = addr + 3; + + dll_log->Log (L"[FrameLimit] Scanned SpeedResetCode2 Address: %06Xh", addr + 3); + + TICK_ADDR_BASE = *(DWORD *)((uint8_t *)(addr + 11)); + + dll_log->Log (L"[FrameLimit] >> TICK_ADDR_BASE: %06Xh", TICK_ADDR_BASE); + } + else { + dll_log->Log (L"[FrameLimit] >> ERROR: Unable to find SpeedResetCode2 memory!"); + } + } + + // + // original code: + // + // ... + // cmp [ebx+28], eax + // >> jz after_set + // >> cmp eax, 2 + // >> jl after_set + // mov 0217B3D4, eax + // mov 0217B3D8, eax + // mov [ebx+28], eax + // after_set: + // ... + // + // we just want this to be 1 always + // + // new code: + // mov eax, 01 + // nop + // nop + // + uint8_t new_code [7] = { 0xB8, 0x01, 0x00, 0x00, 0x00, 0x90, 0x90 }; + + TZF_InjectByteCode ( (LPVOID)config.framerate.speedresetcode2_addr, + new_code, + 7, + PAGE_EXECUTE_READWRITE, + old_speed_reset_code2 ); + + variable_speed_installed = true; + + // mov eax, 02 to mov eax, 01 + char scale [32]; + sprintf ( scale, + "TickScale %li", + CalcTickScale (1000.0f * (1.0f / target_fps)) ); + SK_GetCommandProcessor ()->ProcessCommandLine (scale); + + dll_log->LogEx ( false, L"Field=%lu FPS, Battle=%lu FPS (%s), Cutscene=%lu FPS\n", + target_fps, + config.framerate.battle_target, + config.framerate.battle_adaptive ? L"Adaptive" : L"Fixed", + config.framerate.cutscene_target ); + } + + variable_speed_installed = true; + + if (config.framerate.disable_limiter) { + // Replace the original jump (jb) with an unconditional jump (jmp) + uint8_t new_code [6] = { 0xE9, 0x8B, 0x00, 0x00, 0x00, 0x90 }; + + if (*(DWORD *)config.framerate.limiter_branch_addr != 0x8A820F) { + uint8_t sig [] = { 0x53, // push ebx + 0x56, // push esi + 0x57, // push edi + 0x0F, 0x82, 0x8A, 0x0, 0x0, 0x0 // jb + }; + uintptr_t addr = (uintptr_t)TZF_Scan (sig, 9); + + if (addr != NULL) { + config.framerate.limiter_branch_addr = addr + 3; + + dll_log->Log (L"[FrameLimit] Scanned Limiter Branch Address: %06Xh", addr + 3); + } + else { + dll_log->Log (L"[FrameLimit] >> ERROR: Unable to find LimiterBranchAddr memory!"); + } + } + + TZF_InjectByteCode ( (LPVOID)config.framerate.limiter_branch_addr, + new_code, + 6, + PAGE_EXECUTE_READWRITE ); + } + + if (config.lua.fix_priest) { + uint8_t sig[14] = { 0x8B, 0x4D, 0x08, // mov ecx, [ebp+08] + 0x89, 0x4C, 0x24, 0x04, // mov [esp+04], ecx + 0x8B, 0x4D, 0x0C, // mov ecx, [ebp+0C] + 0x89, 0x4C, 0x24, 0x08 // mov [esp+08], ecx + }; + uint8_t new_code[7] = { 0xE9, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90 }; + + void *addr = TZF_Scan (sig, 14); + + if (addr != NULL) { + dll_log->Log (L"[ 60 FPS ] Scanned Lua Loader Address: %06Xh", addr); + + DWORD hookOffset = (PtrToUlong (&TZF_LuaHook) - (uintptr_t)addr - 5); + memcpy (new_code + 1, &hookOffset, 4); + TZF_InjectByteCode (addr, new_code, 7, PAGE_EXECUTE_READWRITE); + pLuaReturn = (LPVOID)((uintptr_t)addr + 7); + } + else { + dll_log->Log (L"[ 60 FPS ] >> ERROR: Unable to find Lua loader address! Priest bug will occur."); + } + } + + SK_ICommandProcessor* pCmdProc = + SK_GetCommandProcessor (); + + // Special K already has something named this ... get it out of there! + pCmdProc->RemoveVariable ("TargetFPS"); + + pCmdProc->AddVariable ("TargetFPS", TZF_CreateVar (SK_IVariable::Int, (int *)&config.framerate.target)); + pCmdProc->AddVariable ("BattleFPS", TZF_CreateVar (SK_IVariable::Int, (int *)&config.framerate.battle_target)); + pCmdProc->AddVariable ("BattleAdaptive", TZF_CreateVar (SK_IVariable::Boolean, (bool *)&config.framerate.battle_adaptive)); + pCmdProc->AddVariable ("CutsceneFPS", TZF_CreateVar (SK_IVariable::Int, (int *)&config.framerate.cutscene_target)); + + // No matter which technique we use, these things need to be options + pCmdProc->AddVariable ("MinimizeLatency", TZF_CreateVar (SK_IVariable::Boolean, &config.framerate.minimize_latency)); + + // Hook this no matter what, because it lowers the _REPORTED_ CPU usage, + // and some people would object if we suddenly changed this behavior :P + TZF_CreateDLLHook ( config.system.injector.c_str (), "Sleep_Detour", + Sleep_Detour, + (LPVOID *)&Sleep_Original, + (LPVOID *)&pfnSleep ); + TZF_EnableHook (pfnSleep); + + pCmdProc->AddVariable ("AllowFakeSleep", TZF_CreateVar (SK_IVariable::Boolean, &config.framerate.allow_fake_sleep)); + pCmdProc->AddVariable ("YieldProcessor", TZF_CreateVar (SK_IVariable::Boolean, &config.framerate.yield_processor)); + + pCmdProc->AddVariable ("AutoAdjust", TZF_CreateVar (SK_IVariable::Boolean, &config.framerate.auto_adjust)); +} + +void +tzf::FrameRateFix::Shutdown (void) +{ + FreeLibrary (kernel32_dll); + FreeLibrary (bink_dll); + + ////TZF_DisableHook (pfnSK_SetPresentParamsD3D9); + + if (variable_speed_installed) { + DWORD dwOld; + + VirtualProtect ((LPVOID)config.framerate.speedresetcode_addr, 17, PAGE_EXECUTE_READWRITE, &dwOld); + *((DWORD *)(config.framerate.speedresetcode_addr + 2)) -= 8; + *((DWORD *)(config.framerate.speedresetcode_addr + 8)) -= 8; + *((DWORD *)(config.framerate.speedresetcode_addr + 13)) += 2; + VirtualProtect ((LPVOID)config.framerate.speedresetcode_addr, 17, dwOld, &dwOld); + + TZF_FlushInstructionCache ((LPCVOID)config.framerate.speedresetcode_addr, 17); + + TZF_InjectByteCode ( (LPVOID)config.framerate.speedresetcode2_addr, + old_speed_reset_code2, + 7, + PAGE_EXECUTE_READWRITE ); + + SK_GetCommandProcessor ()->ProcessCommandLine ("TickScale 2"); + + variable_speed_installed = false; + } + + ////TZF_DisableHook (pfnSleep); +} + +static int fps_before = 60; +bool forced_30 = false; + +void +tzf::FrameRateFix::Begin30FPSEvent (void) +{ + //EnterCriticalSection (&alter_speed_cs); + + if (variable_speed_installed && (! forced_30)) { + forced_30 = true; + fps_before = target_fps; + SetFPS (30); + } + + //LeaveCriticalSection (&alter_speed_cs); +} + +void +tzf::FrameRateFix::End30FPSEvent (void) +{ + //EnterCriticalSection (&alter_speed_cs); + + if (variable_speed_installed && (forced_30)) { + forced_30 = false; + SetFPS (fps_before); + } + + //LeaveCriticalSection (&alter_speed_cs); +} + +void +tzf::FrameRateFix::SetFPS (int fps) +{ + //EnterCriticalSection (&alter_speed_cs); + + if (variable_speed_installed && (target_fps != fps)) { + target_fps = fps; + char szRescale [32]; + sprintf (szRescale, "TickScale %li", CalcTickScale (1000.0f * (1.0f / fps))); + SK_GetCommandProcessor ()->ProcessCommandLine (szRescale); + } + + //LeaveCriticalSection (&alter_speed_cs); +} + + + + +bool use_accumulator = false; + +tzf::FrameRateFix::CommandProcessor* tzf::FrameRateFix::CommandProcessor::pCommProc; + +tzf::FrameRateFix::CommandProcessor::CommandProcessor (void) +{ + tick_scale_ = TZF_CreateVar (SK_IVariable::Int, &tick_scale, this); + + SK_ICommandProcessor* pCmdProc = + SK_GetCommandProcessor (); + + pCmdProc->AddVariable ("TickScale", tick_scale_); + + pCmdProc->AddVariable ("UseAccumulator", TZF_CreateVar (SK_IVariable::Boolean, &use_accumulator)); + pCmdProc->AddVariable ("MaxFrameLatency", TZF_CreateVar (SK_IVariable::Int, &max_latency)); + pCmdProc->AddVariable ("WaitForVBLANK", TZF_CreateVar (SK_IVariable::Boolean, &wait_for_vblank)); + pCmdProc->AddVariable ("LimiterTolerance", TZF_CreateVar (SK_IVariable::Float, &limiter_tolerance)); +} + +bool +tzf::FrameRateFix::CommandProcessor::OnVarChange (SK_IVariable* var, void* val) +{ + DWORD dwOld; + + if (var == tick_scale_) { + DWORD original0 = *((DWORD *)(TICK_ADDR_BASE )); + DWORD original1 = *((DWORD *)(TICK_ADDR_BASE + 4)); + + if (val != nullptr) { + VirtualProtect ((LPVOID)TICK_ADDR_BASE, 8, PAGE_READWRITE, &dwOld); + + if (variable_speed_installed) { + // Battle Tickrate + *(DWORD *)(TICK_ADDR_BASE) = *(DWORD *)val; + } + + if (variable_speed_installed) { + // World Tickrate + *(DWORD *)(TICK_ADDR_BASE + 4) = *(DWORD *)val; + } + *(DWORD *)val = *(DWORD *)(TICK_ADDR_BASE + 4); + + VirtualProtect ((LPVOID)TICK_ADDR_BASE, 8, dwOld, &dwOld); + + if (variable_speed_installed) { + // mov eax, 02 to mov eax, + VirtualProtect ((LPVOID)config.framerate.speedresetcode3_addr, 4, PAGE_EXECUTE_READWRITE, &dwOld); + *(DWORD *)config.framerate.speedresetcode3_addr = *(DWORD *)val; + VirtualProtect ((LPVOID)config.framerate.speedresetcode3_addr, 4, dwOld, &dwOld); + + uint8_t new_code [7] = { 0xB8, (uint8_t)*(DWORD *)val, 0x00, 0x00, 0x00, 0x90, 0x90 }; + + TZF_InjectByteCode ( (LPVOID)config.framerate.speedresetcode2_addr, + new_code, + 7, + PAGE_EXECUTE_READWRITE, + old_speed_reset_code2 ); + } + //InterlockedExchange ((DWORD *)val, *(DWORD *)config.framerate.speedresetcode3_addr); + + tick_scale = *(int32_t *)val; + } + } + + return true; +} + + + +long +tzf::FrameRateFix::CalcTickScale (double elapsed_ms) +{ + const double tick_ms = (1.0 / 60.0) * 1000.0; + const double inv_rate = 1.0 / target_fps; + + long scale = min (max (elapsed_ms / tick_ms, 1), 7); + + if (scale > 6) + scale = max (inv_rate / (1.0 / 60.0), 1); + + return scale; +} + + +void +tzf::FrameRateFix::RenderTick (void) +{ + if (! forced_30) { + //if (config.framerate.cutscene_target != config.framerate.target) + if (game_state.inCutscene ()) + SetFPS (config.framerate.cutscene_target); + + //if (config.framerate.battle_target != config.framerate.target) + else if (game_state.inBattle ()) + SetFPS (config.framerate.battle_target); + + else //if (! (game_state.inBattle () || game_state.inCutscene ())) + SetFPS (config.framerate.target); + } + + static LARGE_INTEGER last_time = { 0 }; + static LARGE_INTEGER freq = { 0 }; + + LARGE_INTEGER time; + + QueryPerformanceFrequency (&freq); + + static uint32_t last_limit = target_fps; + + if (limiter == nullptr) { + limiter = new FramerateLimiter (); + last_limit = 0; + } + + if (last_limit != target_fps) { + limiter->change_limit (target_fps); + + last_limit = target_fps; + } + + // Skip the limiter while loading, it needlessly + // prolongs loading screens otherwise. + if (! game_state.isLoading ()) + limiter->wait (); + + QueryPerformanceCounter_Original (&time); + + + if (forced_30) { + last_time.QuadPart = time.QuadPart; + return; + } + + + double dt = ((double)(time.QuadPart - last_time.QuadPart) / (double)freq.QuadPart) / (1.0 / 60.0); + + +#ifdef ACCUMULATOR + static double accum = 0.0; + + if (use_accumulator) { + accum += dt - floor (dt); + + time.QuadPart += + (dt - floor (dt)) * (1.0 / 60.0) * freq.QuadPart; + + dt -= (dt - floor (dt)); + + if (accum >= 1.0) { + time.QuadPart -= + (accum - (accum - floor (accum))) * (1.0 / 60.0) * freq.QuadPart; + dt += accum - (accum - floor (accum)); + accum -= accum - (accum - floor (accum)); + } + } else { + accum = 0.0; + } +#endif + + long scale = CalcTickScale (dt * (1.0 / 60.0) * 1000.0); + + static bool last_frame_battle = false; + + if (scale != tick_scale) { + if (config.framerate.auto_adjust || (config.framerate.battle_adaptive && game_state.inBattle ())) { + if (config.framerate.battle_adaptive && game_state.inBattle ()) { + last_frame_battle = true; + +#if 0 + dll_log->Log ( L"[FrameLimit] ** Adjusting TickScale because of battle framerate change " + L"(Expected: ~%4.2f ms, Got: %4.2f ms)", + last_scale * 16.666667f, dt * (1.0 / 60.0) * 1000.0 ); +#endif + } + + SK_GetCommandProcessor ()->ProcessCommandFormatted ("TickScale %li", scale); + } + } + + if (last_frame_battle && (! game_state.inBattle ())) + target_fps = 1; // Reset FPS and TickScale on the next frame. + + if (! game_state.inBattle ()) + last_frame_battle = false; + + last_time.QuadPart = time.QuadPart; +} diff --git a/src/general_io.cpp b/src/general_io.cpp new file mode 100644 index 0000000..32c3d88 --- /dev/null +++ b/src/general_io.cpp @@ -0,0 +1,323 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#include + +#include "general_io.h" + +#include "config.h" +#include "log.h" +#include "hook.h" + +#include +#include +#include + +namespace tbf +{ +class FileTracer { +public: + FileTracer (void) { + addTracePattern (L"Tales of Berseria"); + addIgnorePattern (L"\\\\?\\"); + addIgnorePattern (L"NVIDIA"); + } + + ~FileTracer (void) { + } + + void addIgnorePattern (std::wstring ignore) { + ignore_names_.push_back (ignore); + } + + void addTracePattern (std::wstring trace) { + trace_names_.push_back (trace); + } + + void addFile (std::wstring name, HANDLE file) { + if (isTracedName (name)) { + trace_handles_.insert (file); + } + } + + bool isTracedName (std::wstring name) { + bool trace = false; + + for (int i = 0; i < trace_names_.size (); i++) { + if (name.find (trace_names_ [i]) != std::wstring::npos) { + trace = true; + break; + } + } + + for (int i = 0; i < ignore_names_.size (); i++) { + if (name.find (ignore_names_ [i]) != std::wstring::npos) { + trace = false; + break; + } + } + + return trace; + } + + bool isTracedFile (HANDLE file) { + if (trace_handles_.find (file) != trace_handles_.end ()) + return true; + return false; + } + +protected: + std::vector ignore_names_; + std::vector trace_names_; + +private: + std::set trace_handles_; +} *tracer = nullptr; +}; + +typedef BOOL (WINAPI *ReadFile_fn) + ( _In_ HANDLE hFile, + _Out_ LPVOID lpBuffer, + _In_ DWORD nNumberOfBytesToRead, + _Out_opt_ LPDWORD lpNumberOfBytesRead, + _Inout_opt_ LPOVERLAPPED lpOverlapped ); + +ReadFile_fn ReadFile_Original = nullptr; + +#include + +BOOL +GetFileNameFromHandle ( HANDLE hFile, + wchar_t *pwszFileName, + const unsigned int uiMaxLen ) +{ + *pwszFileName = L'\0'; + + std::unique_ptr ptrcFni ( + new wchar_t [_MAX_PATH + sizeof FILE_NAME_INFO] + ); + + FILE_NAME_INFO *pFni = + reinterpret_cast (ptrcFni.get ()); + + BOOL success = + GetFileInformationByHandleEx ( hFile, + FileNameInfo, + pFni, + sizeof FILE_NAME_INFO + + (_MAX_PATH * sizeof (wchar_t)) ); + if (success) { + wcsncpy_s ( pwszFileName, + min (uiMaxLen, + (pFni->FileNameLength / sizeof pFni->FileName [0]) + 1), + pFni->FileName, + _TRUNCATE ); + } + + return success; +} + +BOOL +WINAPI +ReadFile_Detour ( _In_ HANDLE hFile, + _Out_ LPVOID lpBuffer, + _In_ DWORD nNumberOfBytesToRead, + _Out_opt_ LPDWORD lpNumberOfBytesRead, + _Inout_opt_ LPOVERLAPPED lpOverlapped ) +{ + if (tbf::tracer->isTracedFile (hFile)) { + wchar_t wszFileName [MAX_PATH] = { L'\0' }; + + GetFileNameFromHandle (hFile, wszFileName, MAX_PATH); + + DWORD dwPos = + SetFilePointer (hFile, 0L, nullptr, FILE_CURRENT); + + dll_log->Log ( L"[ FileIO ] Reading: %-90s (%10lu bytes at 0x%06X)", + wszFileName, + nNumberOfBytesToRead, + dwPos ); + } + + return + ReadFile_Original ( hFile, + lpBuffer, + nNumberOfBytesToRead, + lpNumberOfBytesRead, + lpOverlapped ); +} + +typedef HANDLE (WINAPI *CreateFileW_fn)( + _In_ LPCTSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile +); + +CreateFileW_fn CreateFileW_Original = nullptr; + +HANDLE +WINAPI +CreateFileW_Detour ( _In_ LPCWSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile ) +{ + dll_log->Log (L"[ FileIO ] [!] CreateFileW (%s, ...)", lpFileName); + + HANDLE hFile = + CreateFileW_Original ( lpFileName, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile ); + + if (hFile != 0) { + wchar_t wszFileName [MAX_PATH] = { L'\0' }; + + GetFileNameFromHandle (hFile, wszFileName, MAX_PATH); + + if (wcslen (wszFileName)) + tbf::tracer->addFile (wszFileName, hFile); + } + + return hFile; +} + +typedef HANDLE (WINAPI *CreateFileA_fn)( + _In_ LPCSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile ); + +CreateFileA_fn CreateFileA_Original = nullptr; + +HANDLE +WINAPI +CreateFileA_Detour ( _In_ LPCSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile ) +{ + if (config.file_io.capture) { + dll_log->Log ( L"[ FileIO ] [!] CreateFileA (%hs, 0x%X, 0x%X, ..., 0x%X, 0x%X)", + lpFileName, dwDesiredAccess, dwShareMode, + dwCreationDisposition, dwFlagsAndAttributes ); + } else { + // Cache optimization to speed up ridiculously long menu loads + dwFlagsAndAttributes |= FILE_FLAG_SEQUENTIAL_SCAN; + } + + HANDLE hFile = + CreateFileA_Original ( lpFileName, dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile ); + + if (config.file_io.capture && hFile != 0) { + wchar_t wszFileName [MAX_PATH] = { L'\0' }; + + GetFileNameFromHandle (hFile, wszFileName, MAX_PATH); + + if (wcslen (wszFileName)) + tbf::tracer->addFile (wszFileName, hFile); + } + + return hFile; +} + + +typedef HFILE (WINAPI *OpenFile_fn)( _In_ LPCSTR lpFileName, + _Inout_ LPOFSTRUCT lpReOpenBuff, + _In_ UINT uStyle ); + +OpenFile_fn OpenFile_Original = nullptr; + +HFILE +WINAPI +OpenFile_Detour ( _In_ LPCSTR lpFileName, + _Inout_ LPOFSTRUCT lpReOpenBuff, + _In_ UINT uStyle ) +{ + dll_log->Log (L"[ FileIO ] [!] OpenFile (%hs, ...)", lpFileName); + + HFILE hFile = + OpenFile_Original (lpFileName, lpReOpenBuff, uStyle); + + if (hFile != 0) { + wchar_t wszFileName [MAX_PATH] = { L'\0' }; + + GetFileNameFromHandle ((HANDLE)hFile, wszFileName, MAX_PATH); + + if (wcslen (wszFileName)) + tbf::tracer->addFile (wszFileName, (HANDLE)hFile); + } + + return hFile; +} + +void +tbf::FileIO::Init (void) +{ + TBF_CreateDLLHook2 ( L"kernel32.dll", "CreateFileA", + CreateFileA_Detour, + (LPVOID *)&CreateFileA_Original ); + + //config.file_io.capture = true; + if (config.file_io.capture) { + if (tracer == nullptr) + tracer = new FileTracer (); + + TBF_CreateDLLHook2 ( L"kernel32.dll", "OpenFile", + OpenFile_Detour, + (LPVOID *)&OpenFile_Original ); + + TBF_CreateDLLHook2 ( L"kernel32.dll", "CreateFileW", + CreateFileW_Detour, + (LPVOID *)&CreateFileW_Original ); + + TBF_CreateDLLHook2 ( L"kernel32.dll", "ReadFile", + ReadFile_Detour, + (LPVOID *)&ReadFile_Original ); + } + + TBF_ApplyQueuedHooks (); +} + +void +tbf::FileIO::Shutdown (void) +{ + if (tracer != nullptr) { + delete tracer; + tracer = nullptr; + } +} \ No newline at end of file diff --git a/src/hook.cpp b/src/hook.cpp new file mode 100644 index 0000000..4db1432 --- /dev/null +++ b/src/hook.cpp @@ -0,0 +1,441 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#define _CRT_SECURE_NO_WARNINGS + +#include + +#include "hook.h" +#include "log.h" +#include "command.h" +#include "sound.h" +#include "steam.h" + +#include "framerate.h" +#include "render.h" + +#include + +#include "config.h" + +#include +#pragma comment (lib, "winmm.lib") + +#include +#include +#include "textures.h" + +typedef BOOL (WINAPI *GetCursorInfo_pfn) + (_Inout_ PCURSORINFO pci); + +typedef BOOL (WINAPI *GetCursorPos_pfn) + (_Out_ LPPOINT lpPoint); + +GetCursorInfo_pfn GetCursorInfo_Original = nullptr; +GetCursorPos_pfn GetCursorPos_Original = nullptr; + +BOOL WINAPI GetCursorInfo_Detour (_Inout_ PCURSORINFO pci); +BOOL WINAPI GetCursorPos_Detour (_Out_ LPPOINT lpPoint); + + +// Returns the original cursor position and stores the new one in pPoint +POINT +CalcCursorPos (LPPOINT pPoint) +{ + float xscale, yscale; + float xoff, yoff; + +#if 0 + extern void TBF_ComputeAspectCoeffs ( float& xscale, + float& yscale, + float& xoff, + float& yoff ); + + TBF_ComputeAspectCoeffs (xscale, yscale, xoff, yoff); +#else + xoff = 0.0f; + yoff = 0.0f; + xscale = 1.0f; + yscale = 1.0f; +#endif + + pPoint->x = (pPoint->x - xoff) * xscale; + pPoint->y = (pPoint->y - yoff) * yscale; + + return *pPoint; +} + + +typedef void (CALLBACK *SK_PluginKeyPress_pfn)( BOOL Control, + BOOL Shift, + BOOL Alt, + BYTE vkCode ); +SK_PluginKeyPress_pfn SK_PluginKeyPress_Original = nullptr; + +void +CALLBACK +SK_TBF_PluginKeyPress ( BOOL Control, + BOOL Shift, + BOOL Alt, + BYTE vkCode ) +{ + SK_ICommandProcessor& command = + *SK_GetCommandProcessor (); + +#if 0 + if (Control && Shift) { + if (vkCode == 'U') { + command.ProcessCommandLine ("Textures.Remap toggle"); + + //tbf::RenderFix::tex_mgr.updateOSD (); + + return; + } + + else if (vkCode == 'Z') { + command.ProcessCommandLine ("Textures.Purge true"); + + //tbf::RenderFix::tex_mgr.updateOSD (); + + return; + } + + else if (vkCode == 'X') { + command.ProcessCommandLine ("Textures.Trace true"); + + //tbf::RenderFix::tex_mgr.updateOSD (); + + return; + } + + else if (vkCode == 'V') { + command.ProcessCommandLine ("Textures.ShowCache toggle"); + + //tbf::RenderFix::tex_mgr.updateOSD (); + + return; + } + + else if (vkCode == VK_OEM_6) { + extern std::vector textures_used_last_dump; + extern int tex_dbg_idx; + ++tex_dbg_idx; + + if (tex_dbg_idx > textures_used_last_dump.size ()) + tex_dbg_idx = textures_used_last_dump.size (); + + extern int debug_tex_id; + debug_tex_id = (int)textures_used_last_dump [tex_dbg_idx]; + + tbf::RenderFix::tex_mgr.updateOSD (); + + return; + } + + else if (vkCode == VK_OEM_4) { + extern std::vector textures_used_last_dump; + extern int tex_dbg_idx; + extern int debug_tex_id; + + --tex_dbg_idx; + + if (tex_dbg_idx < 0) { + tex_dbg_idx = -1; + debug_tex_id = 0; + } else { + if (tex_dbg_idx > textures_used_last_dump.size ()) + tex_dbg_idx = textures_used_last_dump.size (); + + debug_tex_id = (int)textures_used_last_dump [tex_dbg_idx]; + } + + tbf::RenderFix::tex_mgr.updateOSD (); + + return; + } + } +#endif + + SK_PluginKeyPress_Original (Control, Shift, Alt, vkCode); +} + +typedef LRESULT (CALLBACK *DetourWindowProc_pfn)( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam ); + +DetourWindowProc_pfn DetourWindowProc_Original = nullptr; + +LRESULT +CALLBACK +DetourWindowProc ( _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam ) +{ + if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) { + static POINT last_p = { LONG_MIN, LONG_MIN }; + + POINT p; + + p.x = MAKEPOINTS (lParam).x; + p.y = MAKEPOINTS (lParam).y; + + if (game_state.needsFixedMouseCoords () && config.render.aspect_correction) { + // Only do this if cursor actually moved! + // + // Otherwise, it tricks the game into thinking the input device changed + // from gamepad to mouse (and changes button icons). + if (last_p.x != p.x || last_p.y != p.y) { + CalcCursorPos (&p); + + last_p = p; + } + + return DetourWindowProc_Original (hWnd, uMsg, wParam, MAKELPARAM (p.x, p.y)); + } + + last_p = p; + } + + return DetourWindowProc_Original (hWnd, uMsg, wParam, lParam); +} + +class TBF_InputHooker +{ +private: + static TBF_InputHooker* pInputHook; + +protected: + TBF_InputHooker (void) { } + +public: + static TBF_InputHooker* getInstance (void) + { + if (pInputHook == NULL) + pInputHook = new TBF_InputHooker (); + + return pInputHook; + } + + void Start (void) + { +#if 0 + TBF_CreateDLLHook2 ( L"user32.dll", "GetCursorInfo", + GetCursorInfo_Detour, + (LPVOID*)&GetCursorInfo_Original ); + + TBF_CreateDLLHook2 ( L"user32.dll", "GetCursorPos", + GetCursorPos_Detour, + (LPVOID*)&GetCursorPos_Original ); +#endif + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), + "SK_PluginKeyPress", + SK_TBF_PluginKeyPress, + (LPVOID *)&SK_PluginKeyPress_Original ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), + "SK_DetourWindowProc", + DetourWindowProc, + (LPVOID *)&DetourWindowProc_Original ); + + TBF_ApplyQueuedHooks (); + } + + void End (void) + { + } +}; + + +BOOL +WINAPI +GetCursorInfo_Detour (PCURSORINFO pci) +{ + BOOL ret = GetCursorInfo_Original (pci); + + // Correct the cursor position for Aspect Ratio + if (game_state.needsFixedMouseCoords () && config.render.aspect_correction) { + POINT pt; + + pt.x = pci->ptScreenPos.x; + pt.y = pci->ptScreenPos.y; + + CalcCursorPos (&pt); + + pci->ptScreenPos.x = pt.x; + pci->ptScreenPos.y = pt.y; + } + + return ret; +} + +BOOL +WINAPI +GetCursorPos_Detour (LPPOINT lpPoint) +{ + BOOL ret = GetCursorPos_Original (lpPoint); + + // Correct the cursor position for Aspect Ratio + if (game_state.needsFixedMouseCoords () && config.render.aspect_correction) + CalcCursorPos (lpPoint); + + return ret; +} + +MH_STATUS +WINAPI +TBF_CreateFuncHook ( LPCWSTR pwszFuncName, + LPVOID pTarget, + LPVOID pDetour, + LPVOID *ppOriginal ) +{ + static HMODULE hParent = GetModuleHandle (config.system.injector.c_str ()); + + typedef MH_STATUS (WINAPI *SK_CreateFuncHook_pfn) + ( LPCWSTR pwszFuncName, LPVOID pTarget, + LPVOID pDetour, LPVOID *ppOriginal ); + static SK_CreateFuncHook_pfn SK_CreateFuncHook = + (SK_CreateFuncHook_pfn)GetProcAddress (hParent, "SK_CreateFuncHook"); + + return + SK_CreateFuncHook (pwszFuncName, pTarget, pDetour, ppOriginal); +} + +MH_STATUS +WINAPI +TBF_CreateDLLHook ( LPCWSTR pwszModule, LPCSTR pszProcName, + LPVOID pDetour, LPVOID *ppOriginal, + LPVOID *ppFuncAddr ) +{ + static HMODULE hParent = GetModuleHandle (config.system.injector.c_str ()); + + typedef MH_STATUS (WINAPI *SK_CreateDLLHook_pfn)( + LPCWSTR pwszModule, LPCSTR pszProcName, + LPVOID pDetour, LPVOID *ppOriginal, + LPVOID *ppFuncAddr ); + static SK_CreateDLLHook_pfn SK_CreateDLLHook = + (SK_CreateDLLHook_pfn)GetProcAddress (hParent, "SK_CreateDLLHook"); + + return + SK_CreateDLLHook (pwszModule,pszProcName,pDetour,ppOriginal,ppFuncAddr); +} + +MH_STATUS +WINAPI +TBF_CreateDLLHook2 ( LPCWSTR pwszModule, LPCSTR pszProcName, + LPVOID pDetour, LPVOID *ppOriginal, + LPVOID *ppFuncAddr ) +{ + static HMODULE hParent = GetModuleHandle (config.system.injector.c_str ()); + + typedef MH_STATUS (WINAPI *SK_CreateDLLHook2_pfn)( + LPCWSTR pwszModule, LPCSTR pszProcName, + LPVOID pDetour, LPVOID *ppOriginal, + LPVOID *ppFuncAddr ); + static SK_CreateDLLHook2_pfn SK_CreateDLLHook2 = + (SK_CreateDLLHook2_pfn)GetProcAddress (hParent, "SK_CreateDLLHook2"); + + return + SK_CreateDLLHook2 (pwszModule,pszProcName,pDetour,ppOriginal,ppFuncAddr); +} + +MH_STATUS +WINAPI +TBF_ApplyQueuedHooks (void) +{ + static HMODULE hParent = GetModuleHandle (config.system.injector.c_str ()); + + typedef MH_STATUS (WINAPI *SK_ApplyQueuedHooks_pfn)(void); + static SK_ApplyQueuedHooks_pfn SK_ApplyQueuedHooks = + (SK_ApplyQueuedHooks_pfn)GetProcAddress (hParent, "SK_ApplyQueuedHooks"); + + return SK_ApplyQueuedHooks (); +} + +MH_STATUS +WINAPI +TBF_EnableHook (LPVOID pTarget) +{ + static HMODULE hParent = GetModuleHandle (config.system.injector.c_str ()); + + typedef MH_STATUS (WINAPI *SK_EnableHook_pfn)(LPVOID pTarget); + static SK_EnableHook_pfn SK_EnableHook = + (SK_EnableHook_pfn)GetProcAddress (hParent, "SK_EnableHook"); + + return SK_EnableHook (pTarget); +} + +MH_STATUS +WINAPI +TBF_DisableHook (LPVOID pTarget) +{ + static HMODULE hParent = GetModuleHandle (config.system.injector.c_str ()); + + typedef MH_STATUS (WINAPI *SK_DisableHook_pfn)(LPVOID pTarget); + static SK_DisableHook_pfn SK_DisableHook = + (SK_DisableHook_pfn)GetProcAddress (hParent, "SK_DisableHook"); + + return SK_DisableHook (pTarget); +} + +MH_STATUS +WINAPI +TBF_RemoveHook (LPVOID pTarget) +{ + static HMODULE hParent = GetModuleHandle (config.system.injector.c_str ()); + + typedef MH_STATUS (WINAPI *SK_RemoveHook_pfn)(LPVOID pTarget); + static SK_RemoveHook_pfn SK_RemoveHook = + (SK_RemoveHook_pfn)GetProcAddress (hParent, "SK_RemoveHook"); + + return SK_RemoveHook (pTarget); +} + +MH_STATUS +WINAPI +TBF_Init_MinHook (void) +{ + MH_STATUS status = MH_OK; + + TBF_InputHooker* pHook = TBF_InputHooker::getInstance (); + pHook->Start (); + + return status; +} + +MH_STATUS +WINAPI +TBF_UnInit_MinHook (void) +{ + MH_STATUS status = MH_OK; + + TBF_InputHooker* pHook = TBF_InputHooker::getInstance (); + pHook->End (); + + return status; +} + + +TBF_InputHooker* TBF_InputHooker::pInputHook; \ No newline at end of file diff --git a/src/ini.cpp b/src/ini.cpp new file mode 100644 index 0000000..8286247 --- /dev/null +++ b/src/ini.cpp @@ -0,0 +1,52 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#define _CRT_SECURE_NO_WARNINGS + +#include +#include "ini.h" + +iSK_INI* +TBF_CreateINI (const wchar_t* const wszName) +{ + extern HMODULE hInjectorDLL; + + typedef iSK_INI* (__stdcall *SK_CreateINI_pfn)(const wchar_t* const wszName); + static SK_CreateINI_pfn SK_CreateINI = nullptr; + + if (SK_CreateINI == nullptr) { + SK_CreateINI = + (SK_CreateINI_pfn) + GetProcAddress ( + hInjectorDLL, + "SK_CreateINI" + ); + } + + iSK_INI* pINI = SK_CreateINI (wszName); + + if (pINI != nullptr) { + return pINI; + } else { + // ASSERT: WHAT THE HELL?! + return nullptr; + } +} \ No newline at end of file diff --git a/src/keyboard.cpp b/src/keyboard.cpp new file mode 100644 index 0000000..da59ea3 --- /dev/null +++ b/src/keyboard.cpp @@ -0,0 +1,160 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#include "keyboard.h" +#include + +#include "hook.h" +#include "config.h" + +extern "C" { +typedef int (__cdecl *SDL_GetKeyFromScancode_pfn)(int scancode); +SDL_GetKeyFromScancode_pfn SDL_GetKeyFromScancode_Original = nullptr; + +typedef uint8_t* (__cdecl *SDL_GetKeyboardState_pfn)(int* numkeys); +SDL_GetKeyboardState_pfn SDL_GetKeyboardState_Original = nullptr; + +const uint8_t* +__cdecl +SDL_GetKeyboardState_Detour (int *numkeys) +{ + int num_keys; + + uint8_t* out = SDL_GetKeyboardState_Original (&num_keys); + + if (numkeys != nullptr) + *numkeys = num_keys; + + static uint8_t keys [512]; + memcpy (keys, out, sizeof (uint8_t) * num_keys); + + std::vector >::iterator it = + tbf::KeyboardFix::swapped_keys.begin (); + + while (it != tbf::KeyboardFix::swapped_keys.end ()) { + keys [it->first] = out [it->second]; + keys [it->second] = out [it->first]; + ++it; + } + + return keys; +} + +int +__cdecl +SDL_GetKeyFromScancode_Detour (int scancode) +{ + if (tbf::KeyboardFix::remapped_scancodes.find (scancode) == + tbf::KeyboardFix::remapped_scancodes.end ()) { + return SDL_GetKeyFromScancode_Original (scancode); + } else { + std::vector >::iterator it = + tbf::KeyboardFix::swapped_keys.begin (); + + while (it != tbf::KeyboardFix::swapped_keys.end ()) { + if (it->first == scancode) + return SDL_GetKeyFromScancode_Original (it->second); + else if (it->second == scancode) + return SDL_GetKeyFromScancode_Original (it->first); + ++it; + } + } + + // This should never happen, but ... just in case. + return 0; +} +} + + +#include "log.h" + +void +tbf::KeyboardFix::Init (void) +{ + // Don't even hook this stuff if no keyboard remapping is requested. + if (config.keyboard.swap_keys.empty ()) + return; + + wchar_t* pairs = _wcsdup (config.keyboard.swap_keys.c_str ()); + wchar_t* orig = pairs; + + size_t len = wcslen (pairs); + size_t remaining = len; + + TBF_CreateDLLHook2 ( L"SDL2.dll", "SDL_GetKeyFromScancode", + SDL_GetKeyFromScancode_Detour, + (LPVOID *)&SDL_GetKeyFromScancode_Original ); + + TBF_CreateDLLHook2 ( L"SDL2.dll", "SDL_GetKeyboardState", + SDL_GetKeyboardState_Detour, + (LPVOID *)&SDL_GetKeyboardState_Original ); + + TBF_ApplyQueuedHooks (); + + // Parse the swap pairs + while (remaining > 0 && remaining <= len) { + wchar_t* wszSwapPair = pairs; + + size_t pair_len = wcscspn (pairs, L","); + + remaining -= (pair_len + 1); + pairs += (pair_len); + + *(pairs++) = L'\0'; + + size_t sep = wcscspn (wszSwapPair, L"-"); + + *(wszSwapPair + sep) = L'\0'; + + wchar_t* wszSwapFirst = wszSwapPair; + int16_t first = _wtoi (wszSwapFirst); + + wchar_t* wszSwapSecond = (wszSwapPair + sep + 1); + int16_t second = _wtoi (wszSwapSecond); + + if (remapped_scancodes.find (first) == remapped_scancodes.end () && + remapped_scancodes.find (second) == remapped_scancodes.end ()) { + remapped_scancodes.insert (first); + remapped_scancodes.insert (second); + + swapped_keys.push_back (std::pair (first, second)); + + dll_log->Log (L"[ Keyboard ] # SDL Scancode Swap: (%i <-> %i)", first, second); + } else { + // Do not allow multiple remapping + dll_log->Log ( L"[ Keyboard ] @ SDL Scancode Remapped Multiple Times! -- (%i <-> %i)", + first, + second ); + } + } + + // Free the copied string + free (orig); +} + +void +tbf::KeyboardFix::Shutdown (void) +{ +} + +std::set tbf::KeyboardFix::remapped_scancodes; +std::vector > tbf::KeyboardFix::swapped_keys; \ No newline at end of file diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..ef270ad --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,54 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ +#define _CRT_SECURE_NO_WARNINGS + +#include +#include "log.h" + +iSK_Logger* dll_log; + +iSK_Logger* +TBF_CreateLog (const wchar_t* const wszName) +{ + extern HMODULE hInjectorDLL; + + typedef iSK_Logger* (__stdcall *SK_CreateLog_pfn)(const wchar_t* const wszName); + static SK_CreateLog_pfn SK_CreateLog = nullptr; + + if (SK_CreateLog == nullptr) { + SK_CreateLog = + (SK_CreateLog_pfn) + GetProcAddress ( + hInjectorDLL, + "SK_CreateLog" + ); + } + + iSK_Logger* pLog = SK_CreateLog (wszName); + + if (pLog != nullptr) { + return pLog; + } else { + // ASSERT: WHAT THE HELL?! + return nullptr; + } +} \ No newline at end of file diff --git a/src/parameter.cpp b/src/parameter.cpp new file mode 100644 index 0000000..5a66e3b --- /dev/null +++ b/src/parameter.cpp @@ -0,0 +1,380 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NON_CONFORMING_SWPRINTFS + +#include "parameter.h" + +std::wstring +tbf::ParameterInt::get_value_str (void) +{ + wchar_t str [32]; + _itow (value, str, 10); + + return std::wstring (str); +} + +int +tbf::ParameterInt::get_value (void) +{ + return value; +} + +void +tbf::ParameterInt::set_value (int val) +{ + value = val; +} + + +void +tbf::ParameterInt::set_value_str (std::wstring str) +{ + value = _wtoi (str.c_str ()); +} + + +void +tbf::ParameterInt::store_str (std::wstring str) +{ + set_value_str (str); + iParameter::store (); +} + +void +tbf::ParameterInt::store (int val) +{ + set_value (val); + iParameter::store (); +} + +bool +tbf::ParameterInt::load (int& ref) +{ + bool bRet = + iParameter::load (); + + if (bRet) + ref = get_value (); + + return bRet; +} + + + +std::wstring +tbf::ParameterInt64::get_value_str (void) +{ + wchar_t str [32]; + _i64tow (value, str, 10); + + return std::wstring (str); +} + +int64_t +tbf::ParameterInt64::get_value (void) +{ + return value; +} + +void +tbf::ParameterInt64::set_value (int64_t val) +{ + value = val; +} + + +void +tbf::ParameterInt64::set_value_str (std::wstring str) +{ + value = _wtoll (str.c_str ()); +} + + +void +tbf::ParameterInt64::store_str (std::wstring str) +{ + set_value_str (str); + iParameter::store (); +} + +void +tbf::ParameterInt64::store (int64_t val) +{ + set_value (val); + iParameter::store (); +} + +bool +tbf::ParameterInt64::load (int64_t& ref) +{ + bool bRet = + iParameter::load (); + + if (bRet) + ref = get_value (); + + return bRet; +} + + + +std::wstring +tbf::ParameterBool::get_value_str (void) +{ + if (value == true) + return L"true"; + + return L"false"; +} + +bool +tbf::ParameterBool::get_value (void) +{ + return value; +} + +void +tbf::ParameterBool::set_value (bool val) +{ + value = val; +} + + +void +tbf::ParameterBool::set_value_str (std::wstring str) +{ + if (str.length () == 1 && + str [0] == L'1') + value = true; + + else if (str.length () == 4 && + towlower (str [0]) == L't' && + towlower (str [1]) == L'r' && + towlower (str [2]) == L'u' && + towlower (str [3]) == L'e') + value = true; + + else + value = false; +} + + +void +tbf::ParameterBool::store_str (std::wstring str) +{ + set_value_str (str); + iParameter::store (); +} + +void +tbf::ParameterBool::store (bool val) +{ + set_value (val); + iParameter::store (); +} + +bool +tbf::ParameterBool::load (bool& ref) +{ + bool bRet = + iParameter::load (); + + if (bRet) + ref = get_value (); + + return bRet; +} + + + +std::wstring +tbf::ParameterFloat::get_value_str (void) +{ + wchar_t val_str [16]; + swprintf (val_str, L"%f", value); + + // Remove trailing 0's after the . + size_t len = wcslen (val_str); + for (size_t i = (len - 1); i > 1; i--) { + if (val_str [i] == L'0' && val_str [i - 1] != L'.') + len--; + if (val_str [i] != L'0' && val_str [i] != L'\0') + break; + } + + val_str [len] = L'\0'; + + return std::wstring (val_str); +} + +float +tbf::ParameterFloat::get_value (void) +{ + return value; +} + +void +tbf::ParameterFloat::set_value (float val) +{ + value = val; +} + + +void +tbf::ParameterFloat::set_value_str (std::wstring str) +{ + value = (float)wcstod (str.c_str (), NULL); +} + + +void +tbf::ParameterFloat::store_str (std::wstring str) +{ + set_value_str (str); + iParameter::store (); +} + +void +tbf::ParameterFloat::store (float val) +{ + set_value (val); + iParameter::store (); +} + +bool +tbf::ParameterFloat::load (float& ref) +{ + bool bRet = + iParameter::load (); + + if (bRet) + ref = get_value (); + + return bRet; +} + + + +std::wstring +tbf::ParameterStringW::get_value_str (void) +{ + return value; +} + +std::wstring +tbf::ParameterStringW::get_value (void) +{ + return value; +} + +void +tbf::ParameterStringW::set_value (std::wstring val) +{ + value = val; +} + + +void +tbf::ParameterStringW::set_value_str (std::wstring str) +{ + value = str; +} + + +void +tbf::ParameterStringW::store_str (std::wstring str) +{ + set_value_str (str); + iParameter::store (); +} + +void +tbf::ParameterStringW::store (std::wstring val) +{ + set_value (val); + iParameter::store (); +} + +bool +tbf::ParameterStringW::load (std::wstring& ref) +{ + bool bRet = + iParameter::load (); + + if (bRet) + ref = get_value (); + + return bRet; +} + + +template <> +tbf::iParameter* +tbf::ParameterFactory::create_parameter (const wchar_t* name) +{ + iParameter* param = new ParameterInt (); + params.push_back (param); + + return param; +} + +template <> +tbf::iParameter* +tbf::ParameterFactory::create_parameter (const wchar_t* name) +{ + iParameter* param = new ParameterInt64 (); + params.push_back (param); + + return param; +} + +template <> +tbf::iParameter* +tbf::ParameterFactory::create_parameter (const wchar_t* name) +{ + iParameter* param = new ParameterBool (); + params.push_back (param); + + return param; +} + +template <> +tbf::iParameter* +tbf::ParameterFactory::create_parameter (const wchar_t* name) +{ + iParameter* param = new ParameterFloat (); + params.push_back (param); + + return param; +} + +template <> +tbf::iParameter* +tbf::ParameterFactory::create_parameter (const wchar_t* name) +{ + iParameter* param = new ParameterStringW (); + params.push_back (param); + + return param; +} \ No newline at end of file diff --git a/src/render.cpp b/src/render.cpp new file mode 100644 index 0000000..2024779 --- /dev/null +++ b/src/render.cpp @@ -0,0 +1,1566 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#include "render.h" +#include "framerate.h" +#include "config.h" +#include "log.h" +#include "scanner.h" + +#include "textures.h" + +#include + +#include +#include + +tbf::RenderFix::tbf_draw_states_s + tbf::RenderFix::draw_state; + +typedef HRESULT (STDMETHODCALLTYPE *SetRenderState_pfn) +( + IDirect3DDevice9* This, + D3DRENDERSTATETYPE State, + DWORD Value +); + +extern SetRenderState_pfn D3D9SetRenderState_Original; + +DrawPrimitive_pfn D3D9DrawPrimitive_Original = nullptr; +DrawIndexedPrimitive_pfn D3D9DrawIndexedPrimitive_Original = nullptr; +DrawPrimitiveUP_pfn D3D9DrawPrimitiveUP_Original = nullptr; +DrawIndexedPrimitiveUP_pfn D3D9DrawIndexedPrimitiveUP_Original = nullptr; + + +bool fullscreen_blit = false; +bool needs_aspect = false; +bool world_radial = false; +int TEST_VS = 107874419; + +uint32_t +TBF_MakeShadowBitShift (uint32_t dim) +{ + uint32_t shift = abs (config.render.shadow_rescale); + + // If this is 64x64 and we want all shadows the same resolution, then add + // an extra shift. + shift += ((config.render.shadow_rescale) < 0L) * (dim == 64UL); + + return shift; +} + + +void +TBF_ComputeAspectCoeffs (float& x, float& y, float& xoff, float& yoff) +{ + yoff = 0.0f; + xoff = 0.0f; + + x = 1.0f; + y = 1.0f; + + if (! (config.render.aspect_correction || config.render.blackbar_videos)) + return; + + float rescale = (1.77777778f / config.render.aspect_ratio); + + // Wider + if (config.render.aspect_ratio > 1.7777f) { + int width = (16.0f / 9.0f) * tbf::RenderFix::height; + int x_off = (tbf::RenderFix::width - width) / 2; + + x = (float)tbf::RenderFix::width / (float)width; + xoff = x_off; + } else { + int height = (9.0f / 16.0f) * tbf::RenderFix::width; + int y_off = (tbf::RenderFix::height - height) / 2; + + y = (float)tbf::RenderFix::height / (float)height; + yoff = y_off; + } +} + +#include "hook.h" + +typedef HRESULT (STDMETHODCALLTYPE *SetSamplerState_pfn) +(IDirect3DDevice9* This, + DWORD Sampler, + D3DSAMPLERSTATETYPE Type, + DWORD Value); + +SetSamplerState_pfn D3D9SetSamplerState_Original = nullptr; + +LPVOID SetSamplerState = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetSamplerState_Detour (IDirect3DDevice9* This, + DWORD Sampler, + D3DSAMPLERSTATETYPE Type, + DWORD Value) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) + return D3D9SetSamplerState_Original (This, Sampler, Type, Value); + + static int aniso = 1; + + //dll_log->Log ( L" [!] IDirect3DDevice9::SetSamplerState (%lu, %lu, %lu)", + //Sampler, Type, Value ); + + // Pending removal - these are not configurable tweaks and not particularly useful + if (Type == D3DSAMP_MIPFILTER || + Type == D3DSAMP_MINFILTER || + Type == D3DSAMP_MAGFILTER || + Type == D3DSAMP_MIPMAPLODBIAS) { + //dll_log->Log (L" [!] IDirect3DDevice9::SetSamplerState (...)"); + + if (Type < 8) { + //if (Value != D3DTEXF_ANISOTROPIC) + //D3D9SetSamplerState_Original (This, Sampler, D3DSAMP_MAXANISOTROPY, aniso); + + //dll_log->Log (L" %s Filter: %x", Type == D3DSAMP_MIPFILTER ? L"Mip" : Type == D3DSAMP_MINFILTER ? L"Min" : L"Mag", Value); + if (Type == D3DSAMP_MIPFILTER && Value != D3DTEXF_NONE) { + Value = D3DTEXF_LINEAR; + } + + if (Type == D3DSAMP_MAGFILTER || + Type == D3DSAMP_MINFILTER) + if (Value != D3DTEXF_POINT) + Value = D3DTEXF_ANISOTROPIC; + + // Clamp [0, oo) + if (Type == D3DSAMP_MIPMAPLODBIAS) + *(float *)Value = max (0.0f, *(float *)Value); + } + } + + if (Type == D3DSAMP_MAXANISOTROPY) + aniso = Value; + + if (Type == D3DSAMP_MAXMIPLEVEL) + Value = 0; + + return D3D9SetSamplerState_Original (This, Sampler, Type, Value); +} + +IDirect3DVertexShader9* g_pVS; +IDirect3DPixelShader9* g_pPS; + +static uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +uint32_t +crc32(uint32_t crc, const void *buf, size_t size) +{ + const uint8_t *p; + + p = (uint8_t *)buf; + crc = crc ^ ~0U; + + while (size--) + crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); + + return crc ^ ~0U; +} + +#include +std::unordered_map vs_checksums; +std::unordered_map ps_checksums; + +// Store the CURRENT shader's checksum instead of repeatedly +// looking it up in the above hashmaps. +uint32_t vs_checksum = 0; +uint32_t ps_checksum = 0; + +typedef HRESULT (STDMETHODCALLTYPE *SetVertexShader_pfn) + (IDirect3DDevice9* This, + IDirect3DVertexShader9* pShader); + +SetVertexShader_pfn D3D9SetVertexShader_Original = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetVertexShader_Detour (IDirect3DDevice9* This, + IDirect3DVertexShader9* pShader) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) + return D3D9SetVertexShader_Original (This, pShader); + + if (g_pVS != pShader) { + if (pShader != nullptr) { + if (vs_checksums.find (pShader) == vs_checksums.end ()) { + UINT len = 2048; + char szFunc [2048]; + + pShader->GetFunction (szFunc, &len); + + vs_checksums [pShader] = crc32 (0, szFunc, len); + } + else { + vs_checksum = 0; + } + + vs_checksum = vs_checksums [pShader]; + } + } + + g_pVS = pShader; + return D3D9SetVertexShader_Original (This, pShader); +} + + +typedef HRESULT (STDMETHODCALLTYPE *SetPixelShader_pfn) + (IDirect3DDevice9* This, + IDirect3DPixelShader9* pShader); + +SetPixelShader_pfn D3D9SetPixelShader_Original = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetPixelShader_Detour (IDirect3DDevice9* This, + IDirect3DPixelShader9* pShader) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) + return D3D9SetPixelShader_Original (This, pShader); + + if (g_pPS != pShader) { + if (pShader != nullptr) { + if (ps_checksums.find (pShader) == ps_checksums.end ()) { + UINT len = 8191; + char szFunc [8192] = { 0 }; + + pShader->GetFunction (szFunc, &len); + + ps_checksums [pShader] = crc32 (0, szFunc, len); + } + } else { + ps_checksum = 0; + } + + ps_checksum = ps_checksums [pShader]; + } + + g_pPS = pShader; + return D3D9SetPixelShader_Original (This, pShader); +} + + +// +// Bink Video Vertex Shader +// +const uint32_t VS_CHECKSUM_BINK = 3463109298UL; + +const uint32_t PS_CHECKSUM_UI = 363447431UL; +const uint32_t VS_CHECKSUM_UI = 657093040UL; + +const uint32_t VS_CHECKSUM_SUBS = 0xCE6ADAB2UL; +const uint32_t VS_CHECKSUM_TITLE = -653456248; /// Main menu +const uint32_t VS_CHECKSUM_RADIAL = -18938562; /// Radial Gauges +// 107874419 /// Progress Bar + +// +// Model Shadow Shaders (primary) +// +const uint32_t PS_CHECKSUM_CHAR_SHADOW = 1180797962UL; +const uint32_t VS_CHECKSUM_CHAR_SHADOW = 446150694UL; + + +typedef void (STDMETHODCALLTYPE *SK_BeginBufferSwap_pfn)(void); +SK_BeginBufferSwap_pfn SK_BeginBufferSwap = nullptr; + +typedef HRESULT (STDMETHODCALLTYPE *SK_EndBufferSwap_pfn) + (HRESULT hr, + IUnknown* device); +SK_EndBufferSwap_pfn SK_EndBufferSwap = nullptr; + +typedef HRESULT (STDMETHODCALLTYPE *SetScissorRect_pfn)( + IDirect3DDevice9* This, + const RECT* pRect); + +SetScissorRect_pfn D3D9SetScissorRect_Original = nullptr; + +typedef HRESULT (STDMETHODCALLTYPE *EndScene_pfn) +(IDirect3DDevice9* This); + +EndScene_pfn D3D9EndScene_Original = nullptr; + +int draw_count = 0; +int next_draw = 0; +int scene_count = 0; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9EndScene_Detour (IDirect3DDevice9* This) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) + return D3D9EndScene_Original (This); + + // EndScene is invoked multiple times per-frame, but we + // are only interested in the first. + if (scene_count++ > 0) + return D3D9EndScene_Original (This); + + if ( ((game_state.hasFixedAspect () && + config.render.aspect_correction) || + (config.render.blackbar_videos && + tbf::RenderFix::bink)) && + config.render.clear_blackbars ) { + D3DCOLOR color = 0xff000000; + + int width = tbf::RenderFix::width; + int height = (9.0f / 16.0f) * width; + + // We can't do this, so instead we need to sidebar the stuff + if (height > tbf::RenderFix::height) { + width = (16.0f / 9.0f) * tbf::RenderFix::height; + height = tbf::RenderFix::height; + } + + if (height != tbf::RenderFix::height) { + RECT top; + top.top = 0; + top.left = 0; + top.right = tbf::RenderFix::width; + top.bottom = top.top + (tbf::RenderFix::height - height) / 2 + 1; + D3D9SetScissorRect_Original (tbf::RenderFix::pDevice, &top); + tbf::RenderFix::pDevice->SetRenderState (D3DRS_SCISSORTESTENABLE, 1); + + tbf::RenderFix::pDevice->Clear (0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0xff); + + RECT bottom; + bottom.top = tbf::RenderFix::height - (tbf::RenderFix::height - height) / 2; + bottom.left = 0; + bottom.right = tbf::RenderFix::width; + bottom.bottom = tbf::RenderFix::height; + D3D9SetScissorRect_Original (tbf::RenderFix::pDevice, &bottom); + + tbf::RenderFix::pDevice->Clear (0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0xff); + + tbf::RenderFix::pDevice->SetRenderState (D3DRS_SCISSORTESTENABLE, 0); + } + + if (width != tbf::RenderFix::width) { + RECT left; + left.top = 0; + left.left = 0; + left.right = left.left + (tbf::RenderFix::width - width) / 2 + 1; + left.bottom = tbf::RenderFix::height; + D3D9SetScissorRect_Original (tbf::RenderFix::pDevice, &left); + tbf::RenderFix::pDevice->SetRenderState (D3DRS_SCISSORTESTENABLE, 1); + + tbf::RenderFix::pDevice->Clear (0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0xff); + + RECT right; + right.top = 0; + right.left = tbf::RenderFix::width - (tbf::RenderFix::width - width) / 2; + right.right = tbf::RenderFix::width; + right.bottom = tbf::RenderFix::height; + D3D9SetScissorRect_Original (tbf::RenderFix::pDevice, &right); + tbf::RenderFix::pDevice->SetRenderState (D3DRS_SCISSORTESTENABLE, 1); + + tbf::RenderFix::pDevice->Clear (0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0xff); + + tbf::RenderFix::pDevice->SetRenderState (D3DRS_SCISSORTESTENABLE, 0); + } + } + + HRESULT hr = D3D9EndScene_Original (This); + + extern bool pending_loads (void); + if (pending_loads ()) { + extern void TBFix_LoadQueuedTextures (void); + TBFix_LoadQueuedTextures (); + } + + game_state.in_skit = false; + + needs_aspect = false; + fullscreen_blit = false; + draw_count = 0; + next_draw = 0; + + g_pPS = nullptr; + g_pVS = nullptr; + vs_checksum = 0; + ps_checksum = 0; + + return hr; +} + +COM_DECLSPEC_NOTHROW +void +STDMETHODCALLTYPE +D3D9EndFrame_Pre (void) +{ + tbf::RenderFix::draw_state.cegui_active = true; + + void TBFix_LogUsedTextures (void); + TBFix_LogUsedTextures (); + + if (! config.framerate.minimize_latency) + tbf::FrameRateFix::RenderTick (); + + return SK_BeginBufferSwap (); +} + +std::string mod_text; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9EndFrame_Post (HRESULT hr, IUnknown* device) +{ + // Ignore anything that's not the primary render device. + if (device != tbf::RenderFix::pDevice) + return SK_EndBufferSwap (hr, device); + + tbf::RenderFix::draw_state.cegui_active = false; + + scene_count = 0; + + tbf::RenderFix::dwRenderThreadID = GetCurrentThreadId (); + + hr = SK_EndBufferSwap (hr, device); + + extern bool pending_loads (void); + if (pending_loads ()) { + extern void TBFix_LoadQueuedTextures (void); + TBFix_LoadQueuedTextures (); + } + + if (config.framerate.minimize_latency) + tbf::FrameRateFix::RenderTick (); + + typedef BOOL (__stdcall *SK_DrawExternalOSD_pfn)(std::string app_name, std::string text); + + static HMODULE hMod = + GetModuleHandle (config.system.injector.c_str ()); + static SK_DrawExternalOSD_pfn SK_DrawExternalOSD + = + (SK_DrawExternalOSD_pfn)GetProcAddress (hMod, "SK_DrawExternalOSD"); + + extern bool __show_cache; + + if (__show_cache) { + static std::string output; + + output = "Texture Cache\n"; + output += "-------------\n"; + output += tbf::RenderFix::tex_mgr.osdStats (); + + output += mod_text; + + SK_DrawExternalOSD ("ToZFix", output); + + output = ""; + } else + SK_DrawExternalOSD ("ToZFix", mod_text); + + mod_text = ""; + + return hr; +} + +typedef HRESULT (STDMETHODCALLTYPE *UpdateSurface_pfn) + ( _In_ IDirect3DDevice9 *This, + _In_ IDirect3DSurface9 *pSourceSurface, + _In_ const RECT *pSourceRect, + _In_ IDirect3DSurface9 *pDestinationSurface, + _In_ const POINT *pDestinationPoint ); + +UpdateSurface_pfn D3D9UpdateSurface_Original = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9UpdateSurface_Detour ( IDirect3DDevice9 *This, + _In_ IDirect3DSurface9 *pSourceSurface, + _In_ const RECT *pSourceRect, + _In_ IDirect3DSurface9 *pDestinationSurface, + _In_ const POINT *pDestinationPoint ) +{ + HRESULT hr = + D3D9UpdateSurface_Original ( This, + pSourceSurface, + pSourceRect, + pDestinationSurface, + pDestinationPoint ); + +//#define DUMP_TEXTURES + if (SUCCEEDED (hr)) { +#ifdef DUMP_TEXTURES + IDirect3DTexture9 *pBase = nullptr; + + HRESULT hr2 = + pDestinationSurface->GetContainer ( + __uuidof (IDirect3DTexture9), + (void **)&pBase + ); + + if (SUCCEEDED (hr2) && pBase != nullptr) { + if (D3DXSaveTextureToFile == nullptr) { + D3DXSaveTextureToFile = + (D3DXSaveTextureToFile_t) + GetProcAddress ( tbf::RenderFix::d3dx9_43_dll, + "D3DXSaveTextureToFileW" ); + } + + if (D3DXSaveTextureToFile != nullptr) { + wchar_t wszFileName [MAX_PATH] = { L'\0' }; + _swprintf ( wszFileName, L"textures\\UpdateSurface_%x.png", + pBase ); + D3DXSaveTextureToFile (wszFileName, D3DXIFF_PNG, pBase, NULL); + } + + pBase->Release (); + } +#endif + } + + return hr; +} + +typedef HRESULT (STDMETHODCALLTYPE *UpdateTexture_pfn) + (IDirect3DDevice9 *This, + IDirect3DBaseTexture9 *pSourceTexture, + IDirect3DBaseTexture9 *pDestinationTexture); + +UpdateTexture_pfn D3D9UpdateTexture_Original = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9UpdateTexture_Detour (IDirect3DDevice9 *This, + IDirect3DBaseTexture9 *pSourceTexture, + IDirect3DBaseTexture9 *pDestinationTexture) +{ + HRESULT hr = D3D9UpdateTexture_Original (This, pSourceTexture, + pDestinationTexture); + +//#define DUMP_TEXTURES + if (SUCCEEDED (hr)) { +#if 0 + if ( incomplete_textures.find (pDestinationTexture) != + incomplete_textures.end () ) { + dll_log->Log (L" Generating Mipmap LODs for incomplete texture!"); + (pDestinationTexture->GenerateMipSubLevels ()); + } +#endif +#ifdef DUMP_TEXTURES + if (SUCCEEDED (hr)) { + if (D3DXSaveTextureToFile != nullptr) { + wchar_t wszFileName [MAX_PATH] = { L'\0' }; + _swprintf ( wszFileName, L"textures\\UpdateTexture_%x.dds", + pSourceTexture ); + D3DXSaveTextureToFile (wszFileName, D3DXIFF_DDS, pDestinationTexture, NULL); + } + } +#endif + } + + return hr; +} + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetScissorRect_Detour (IDirect3DDevice9* This, + const RECT* pRect) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) + return D3D9SetScissorRect_Original (This, pRect); + + // Let the mod's GUI render without any restrictions. + if (tbf::RenderFix::draw_state.cegui_active) + return D3D9SetScissorRect_Original (This, pRect); + + // If we don't care about aspect ratio, then just early-out + if (! config.render.aspect_correction) + return D3D9SetScissorRect_Original (This, pRect); + + // Otherwise, fix this because the UI's scissor rectangles are + // completely wrong after we start messing with viewport scaling. + + RECT fixed_scissor; + fixed_scissor.bottom = pRect->bottom; + fixed_scissor.top = pRect->top; + fixed_scissor.left = pRect->left; + fixed_scissor.right = pRect->right; + + float x_scale, y_scale; + float x_off, y_off; + TBF_ComputeAspectCoeffs (x_scale, y_scale, x_off, y_off); + + // Wider + if (config.render.aspect_ratio > 1.7777f) { + float left_ndc = 2.0f * ((float)pRect->left / (float)tbf::RenderFix::width) - 1.0f; + float right_ndc = 2.0f * ((float)pRect->right / (float)tbf::RenderFix::width) - 1.0f; + + int width = (16.0f / 9.0f) * tbf::RenderFix::height; + + fixed_scissor.left = (left_ndc * width + width) / 2.0f + x_off; + fixed_scissor.right = (right_ndc * width + width) / 2.0f + x_off; + } else { + float top_ndc = 2.0f * ((float)pRect->top / (float)tbf::RenderFix::height) - 1.0f; + float bottom_ndc = 2.0f * ((float)pRect->bottom / (float)tbf::RenderFix::height) - 1.0f; + + int height = (9.0f / 16.0f) * tbf::RenderFix::width; + + fixed_scissor.top = (top_ndc * height + height) / 2.0f + y_off; + fixed_scissor.bottom = (bottom_ndc * height + height) / 2.0f + y_off; + } + + return D3D9SetScissorRect_Original (This, &fixed_scissor); +} + +typedef HRESULT (STDMETHODCALLTYPE *SetViewport_pfn)( + IDirect3DDevice9* This, + CONST D3DVIEWPORT9* pViewport); + +SetViewport_pfn D3D9SetViewport_Original = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetViewport_Detour (IDirect3DDevice9* This, + CONST D3DVIEWPORT9* pViewport) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) + return D3D9SetViewport_Original (This, pViewport); + + // + // Adjust Character Drop Shadows + // + if (pViewport->Width == pViewport->Height && + (pViewport->Width == 64 || pViewport->Width == 128)) { + D3DVIEWPORT9 rescaled_shadow = *pViewport; + + uint32_t shift = TBF_MakeShadowBitShift (pViewport->Width); + + rescaled_shadow.Width <<= shift; + rescaled_shadow.Height <<= shift; + + return D3D9SetViewport_Original (This, &rescaled_shadow); + } + +#if 0 + // + // Environmental Shadows + // + if (pViewport->Width == pViewport->Height && + (pViewport->Width == 512 || + pViewport->Width == 1024 || + pViewport->Width == 2048)) { + D3DVIEWPORT9 rescaled_shadow = *pViewport; + + rescaled_shadow.Width <<= config.render.env_shadow_rescale; + rescaled_shadow.Height <<= config.render.env_shadow_rescale; + + return D3D9SetViewport_Original (This, &rescaled_shadow); + } +#endif + + // + // Adjust Post-Processing + // + if (pViewport->Width == 512 && + pViewport->Height == 256 && config.render.postproc_ratio > 0.0f) { + D3DVIEWPORT9 rescaled_post_proc = *pViewport; + + float scale_x, scale_y; + float x_off, y_off; + scale_x = 1.0f; scale_y = 1.0f; + x_off = 0.0f; y_off = 0.0f; + //TBF_ComputeAspectScale (scale_x, scale_y, x_off, y_off); + + rescaled_post_proc.Width = tbf::RenderFix::width * config.render.postproc_ratio * scale_x; + rescaled_post_proc.Height = tbf::RenderFix::height * config.render.postproc_ratio * scale_y; + rescaled_post_proc.X += x_off; + rescaled_post_proc.Y += y_off; + + return D3D9SetViewport_Original (This, &rescaled_post_proc); + } + + return D3D9SetViewport_Original (This, pViewport); +} + +#if 0 +float data [20]; +memcpy (data, pConstantData, sizeof (float) * 20); + +float rescale = ((16.0f / 9.0f) / config.render.aspect_ratio); + +// Wider +if (config.render.aspect_ratio > (16.0f / 9.0f)) { + int width = (16.0f / 9.0f) * tbf::RenderFix::height; + int x_off = (tbf::RenderFix::width - width) / 2; + + data [ 0] *= rescale; + data [ 3] *= rescale; + data [ 4] *= rescale; + data [ 8] *= rescale; + data [12] *= rescale; +} else { + int height = (9.0f / 16.0f) * tbf::RenderFix::width; + int y_off = (tbf::RenderFix::height - height) / 2; + + data [ 1] *= rescale; + data [ 5] *= rescale; + data [ 7] *= rescale; + data [ 9] *= rescale; + data [13] *= rescale; +} + +return D3D9SetVertexShaderConstantF_Original (This, StartRegister, data, Vector4fCount); +#endif + +void +TBF_AdjustViewport (IDirect3DDevice9* This, bool UI) +{ + D3DVIEWPORT9 vp9_orig; + This->GetViewport (&vp9_orig); + + if (! UI) { + vp9_orig.MinZ = 0.0f; + vp9_orig.MaxZ = 1.0f; + vp9_orig.X = 0; + vp9_orig.Y = 0; + vp9_orig.Width = tbf::RenderFix::width; + vp9_orig.Height = tbf::RenderFix::height; + D3D9SetViewport_Original (This, &vp9_orig); + return; + } + + vp9_orig.X = 0; + vp9_orig.Y = 0; + vp9_orig.Width = tbf::RenderFix::width; + vp9_orig.Height = tbf::RenderFix::height; + + DWORD width = vp9_orig.Width; + DWORD height = (9.0f / 16.0f) * vp9_orig.Width; + + // We can't do this, so instead we need to sidebar the stuff + if (height > vp9_orig.Height) { + width = (16.0f / 9.0f) * vp9_orig.Height; + height = vp9_orig.Height; + } + + if (height != vp9_orig.Height) { + D3DVIEWPORT9 vp9; + vp9.X = vp9_orig.X; vp9.Y = vp9_orig.Y + (vp9_orig.Height - height) / 2; + vp9.Width = width; vp9.Height = height; + vp9.MinZ = vp9_orig.MinZ; vp9.MaxZ = vp9_orig.MaxZ; + + D3D9SetViewport_Original (This, &vp9); + } + + // Sidebar Videos + if (width != vp9_orig.Width) { + D3DVIEWPORT9 vp9; + vp9.X = vp9_orig.X + (vp9_orig.Width - width) / 2; vp9.Y = vp9_orig.Y; + vp9.Width = width; vp9.Height = height; + vp9.MinZ = vp9_orig.MinZ; vp9.MaxZ = vp9_orig.MaxZ; + + D3D9SetViewport_Original (This, &vp9); + } +} + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9DrawIndexedPrimitive_Detour (IDirect3DDevice9* This, + D3DPRIMITIVETYPE Type, + INT BaseVertexIndex, + UINT MinVertexIndex, + UINT NumVertices, + UINT startIndex, + UINT primCount) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + return D3D9DrawIndexedPrimitive_Original ( This, Type, + BaseVertexIndex, MinVertexIndex, + NumVertices, startIndex, + primCount ); + } + + ++tbf::RenderFix::draw_state.draws; + ++draw_count; + +// Battle Works Well +#if 0 + if (vs_checksum == 107874419/* && ps_checksum == 3087596655*/) + needs_aspect = true; + if (vs_checksum == 3486499850 && ps_checksum == 2539463060) + needs_aspect = true; +#endif + + if (vs_checksum == VS_CHECKSUM_TITLE && *game_state.base_addr) + needs_aspect = true; + + if (vs_checksum == 657093040 && ps_checksum == 363447431) + needs_aspect = true; + + if ((config.render.aspect_correction && Type == D3DPT_TRIANGLESTRIP && ((vs_checksum == 0x52BD224A || + vs_checksum == 0x272A71B0 || // Splash Screen + (vs_checksum == VS_CHECKSUM_TITLE && *game_state.base_addr) || + vs_checksum == VS_CHECKSUM_SUBS || + vs_checksum == 107874419) || vs_checksum == VS_CHECKSUM_RADIAL) || + tbf::RenderFix::cutscene_frame.in_use) || + (config.render.blackbar_videos && tbf::RenderFix::bink && vs_checksum == VS_CHECKSUM_BINK)) { + + D3DVIEWPORT9 vp9_orig; + This->GetViewport (&vp9_orig); + + if (vs_checksum == VS_CHECKSUM_RADIAL) { + if (world_radial) { + TBF_AdjustViewport (This, false); + } else { + TBF_AdjustViewport (This, true); + } + } else { + if ((vs_checksum != 107874419) || (! fullscreen_blit)) + TBF_AdjustViewport (This, true); + else + TBF_AdjustViewport (This, false); + } + + if (tbf::RenderFix::cutscene_frame.in_use) + TBF_AdjustViewport (This, false); + + HRESULT hr = + D3D9DrawIndexedPrimitive_Original ( This, Type, + BaseVertexIndex, MinVertexIndex, + NumVertices, startIndex, + primCount ); + + //if (vs_checksum != 107874419) + //This->SetViewport (&vp9_orig); + //TBF_AdjustViewport (This, false); + + return hr; + } + else if (Type == D3DPT_TRIANGLESTRIP) { + //dll_log->Log (L" Consider Vertex Shader %4i...", vs_checksum); + } + + return D3D9DrawIndexedPrimitive_Original ( This, Type, + BaseVertexIndex, MinVertexIndex, + NumVertices, startIndex, + primCount ); +} + + +typedef HRESULT (STDMETHODCALLTYPE *SetVertexShaderConstantF_pfn)( + IDirect3DDevice9* This, + UINT StartRegister, + CONST float* pConstantData, + UINT Vector4fCount); + +SetVertexShaderConstantF_pfn D3D9SetVertexShaderConstantF_Original = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetVertexShaderConstantF_Detour (IDirect3DDevice9* This, + UINT StartRegister, + CONST float* pConstantData, + UINT Vector4fCount) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + return D3D9SetVertexShaderConstantF_Original ( This, + StartRegister, + pConstantData, + Vector4fCount ); + } + + if (vs_checksum == VS_CHECKSUM_RADIAL) + { +#if 0 + dll_log->LogEx (false, L" Draw Call %u: Radial\n", draw_count); + for (int i = 0; i < Vector4fCount; i++) { + dll_log->LogEx ( false, L" %f %f %f %f\n", + pConstantData [0+i*4], pConstantData [1+i*4], + pConstantData [2+i*4], pConstantData [3+i*4] ); + } +#endif + if (Vector4fCount == 8) { + if (pConstantData [30] != 0.0f || pConstantData [14] != 0.5f) { + world_radial = true; + } + else + world_radial = false; + } + } + + // + // Fullscreen UI Blit + // + if (StartRegister == 0 && + Vector4fCount == 5) { + fullscreen_blit = false; + + if (pConstantData [ 0] == 2.0f / 1280.0f && + pConstantData [ 5] == 2.0f / 720.0f) { + // + // If the origin is translated all the way to the left, we assume this + // is an effect that covers the entire screen. + // + // (Also anything that is not horizontally translated) + // + if (vs_checksum == 107874419 && ps_checksum == 3087596655) { + if ((pConstantData [12] == -pConstantData [15]) || + (pConstantData [12] == pConstantData [15]) || + (pConstantData [12] == 0.0f && pConstantData [15] == 1.0f)) { + // Do not stretch skits + if (game_state.inExplanation () && pConstantData [19] == 0.4f) { + game_state.in_skit = true; + } else + fullscreen_blit = true; + } + } + } + } + + // + // Model Shadows + // + if (StartRegister == 240 && Vector4fCount == 1) { + uint32_t shift; + uint32_t dim = 0; + + if (pConstantData [0] == -1.0f / 64.0f) { + dim = 64UL; + //dll_log->Log (L" 64x64 Shadow: VS CRC: %lu, PS CRC: %lu", vs_checksum, ps_checksum); + } + + if (pConstantData [0] == -1.0f / 128.0f) { + dim = 128UL; + //dll_log->Log (L" 128x128 Shadow: VS CRC: %lu, PS CRC: %lu", vs_checksum, ps_checksum); + } + + shift = TBF_MakeShadowBitShift (dim); + + float newData [4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + newData [0] = -1.0f / (dim << shift); + newData [1] = 1.0f / (dim << shift); + + if (pConstantData [2] != 0.0f || + pConstantData [3] != 0.0f) { + dll_log->Log (L"[ D3D9 ] Assertion failed: non-zero 2 or 3 (line %lu)", __LINE__); + } + + if (dim != 0) { + return D3D9SetVertexShaderConstantF_Original (This, 240, newData, 1); + } + } + + // + // Post-Processing + // + if (StartRegister == 240 && + Vector4fCount == 1 && + pConstantData [0] == -1.0f / 512.0f && + pConstantData [1] == 1.0f / 256.0f && + config.render.postproc_ratio > 0.0f) { + if (SUCCEEDED (This->GetRenderTarget (0, &tbf::RenderFix::pPostProcessSurface))) + tbf::RenderFix::pPostProcessSurface->Release (); + + float newData [4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + float scale_x, scale_y; + float x_off, y_off; + scale_x = 1.0f; scale_y = 1.0f; + x_off = 0.0f; y_off = 0.0f; + //TBF_ComputeAspectScale (scale_x, scale_y, x_off, y_off); + + newData [0] = -1.0f / ((float)tbf::RenderFix::width * config.render.postproc_ratio * scale_x); + newData [1] = 1.0f / ((float)tbf::RenderFix::height * config.render.postproc_ratio * scale_y); + + if (pConstantData [2] != 0.0f || + pConstantData [3] != 0.0f) { + dll_log->Log (L"[ D3D9 ] Assertion failed: non-zero 2 or 3 (line %lu)", __LINE__); + } + + return D3D9SetVertexShaderConstantF_Original (This, 240, newData, 1); + } + + // + // Env Shadow + // + if (StartRegister == 240 && Vector4fCount == 1) { + uint32_t shift; + uint32_t dim = 0; + + if (pConstantData [0] == -1.0f / 512.0f) { + dim = 512UL; + //dll_log->Log (L" 512x512 Shadow: VS CRC: %lu, PS CRC: %lu", vs_checksum, ps_checksum); + } + + if (pConstantData [0] == -1.0f / 1024.0f) { + dim = 1024UL; + //dll_log->Log (L" 1024x1024 Shadow: VS CRC: %lu, PS CRC: %lu", vs_checksum, ps_checksum); + } + + if (pConstantData [0] == -1.0f / 2048.0f) { + dim = 2048UL; + //dll_log->Log (L" 2048x2048 Shadow: VS CRC: %lu, PS CRC: %lu", vs_checksum, ps_checksum); + } + + shift = config.render.env_shadow_rescale; + + float newData [4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + newData [0] = -1.0f / (dim << shift); + newData [1] = 1.0f / (dim << shift); + + if (pConstantData [2] != 0.0f || + pConstantData [3] != 0.0f) { + dll_log->Log (L"[ D3D9 ] Assertion failed: non-zero 2 or 3 (line %lu)", __LINE__); + } + + if (dim != 0) { + return D3D9SetVertexShaderConstantF_Original (This, 240, newData, 1); + } + } + + + // + // Model Shadows and Post-Processing + // + if (StartRegister == 0 && (Vector4fCount == 2 || Vector4fCount == 3)) { + IDirect3DSurface9* pSurf = nullptr; + + if (This == tbf::RenderFix::pDevice && SUCCEEDED (This->GetRenderTarget (0, &pSurf)) && pSurf != nullptr) { + D3DSURFACE_DESC desc; + pSurf->GetDesc (&desc); + pSurf->Release (); + + // + // Post-Processing + // + if (config.render.postproc_ratio > 0.0f) { + if (desc.Width == tbf::RenderFix::width && + desc.Height == tbf::RenderFix::height) { + if (pSurf == tbf::RenderFix::pPostProcessSurface) { + float newData [12]; + + float scale_x, scale_y; + float x_off, y_off; + + scale_x = 1.0f; scale_y = 1.0f; + x_off = 0.0f; y_off = 0.0f; + + //TBF_ComputeAspectScale (scale_x, scale_y, x_off, y_off); + + float rescale_x = 512.0f / ((float)tbf::RenderFix::width * config.render.postproc_ratio * scale_x); + float rescale_y = 256.0f / ((float)tbf::RenderFix::height * config.render.postproc_ratio * scale_y); + + for (int i = 0; i < 8; i += 2) { + newData [i] = pConstantData [i] * rescale_x; + } + + for (int i = 1; i < 8; i += 2) { + newData [i] = pConstantData [i] * rescale_y; + } + + //fix_aspect = true; + + return D3D9SetVertexShaderConstantF_Original (This, 0, newData, Vector4fCount); + } + } + } + + // + // Model Shadows + // + if (desc.Width == desc.Height) { + float newData [12]; + + uint32_t shift = TBF_MakeShadowBitShift (desc.Width); + + for (UINT i = 0; i < Vector4fCount * 4; i++) { + newData [i] = pConstantData [i] / (float)(1 << shift); + } + + return D3D9SetVertexShaderConstantF_Original (This, 0, newData, Vector4fCount); + } + } + } + + return D3D9SetVertexShaderConstantF_Original (This, StartRegister, pConstantData, Vector4fCount); +} + + + +typedef HRESULT (STDMETHODCALLTYPE *SetPixelShaderConstantF_pfn)( + IDirect3DDevice9* This, + UINT StartRegister, + CONST float* pConstantData, + UINT Vector4fCount); + +SetVertexShaderConstantF_pfn D3D9SetPixelShaderConstantF_Original = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetPixelShaderConstantF_Detour (IDirect3DDevice9* This, + UINT StartRegister, + CONST float* pConstantData, + UINT Vector4fCount) +{ + return D3D9SetPixelShaderConstantF_Original (This, StartRegister, pConstantData, Vector4fCount); +} + + + + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9DrawPrimitive_Detour (IDirect3DDevice9* This, + D3DPRIMITIVETYPE PrimitiveType, + UINT StartVertex, + UINT PrimitiveCount) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + dll_log->Log (L"[Render Fix] >> WARNING: DrawPrimitive came from unknown IDirect3DDevice9! << "); + + return D3D9DrawPrimitive_Original ( This, PrimitiveType, + StartVertex, PrimitiveCount ); + } + + tbf::RenderFix::draw_state.draws++; + +#if 0 + if (tsf::RenderFix::tracer.log) { + dll_log->Log ( L"[FrameTrace] DrawPrimitive - %X, StartVertex: %lu, PrimitiveCount: %lu", + PrimitiveType, StartVertex, PrimitiveCount ); + } +#endif + + return D3D9DrawPrimitive_Original ( This, PrimitiveType, + StartVertex, PrimitiveCount ); +} + +const wchar_t* +SK_D3D9_PrimitiveTypeToStr (D3DPRIMITIVETYPE pt) +{ + switch (pt) + { + case D3DPT_POINTLIST : return L"D3DPT_POINTLIST"; + case D3DPT_LINELIST : return L"D3DPT_LINELIST"; + case D3DPT_LINESTRIP : return L"D3DPT_LINESTRIP"; + case D3DPT_TRIANGLELIST : return L"D3DPT_TRIANGLELIST"; + case D3DPT_TRIANGLESTRIP : return L"D3DPT_TRIANGLESTRIP"; + case D3DPT_TRIANGLEFAN : return L"D3DPT_TRIANGLEFAN"; + } + + return L"Invalid Primitive"; +} + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9DrawPrimitiveUP_Detour ( IDirect3DDevice9* This, + D3DPRIMITIVETYPE PrimitiveType, + UINT PrimitiveCount, + const void *pVertexStreamZeroData, + UINT VertexStreamZeroStride ) +{ +#if 0 + if (tsf::RenderFix::tracer.log && This == tsf::RenderFix::pDevice) { + dll_log->Log ( L"[FrameTrace] DrawPrimitiveUP (Type: %s) - PrimitiveCount: %lu"/* + L" [FrameTrace] -" + L" BaseIdx: %5li, MinVtxIdx: %5lu,\n" + L" [FrameTrace] -" + L" NumVertices: %5lu, startIndex: %5lu,\n" + L" [FrameTrace] -" + L" primCount: %5lu"*/, + SK_D3D9_PrimitiveTypeToStr (PrimitiveType), + PrimitiveCount/*, + BaseVertexIndex, MinVertexIndex, + NumVertices, startIndex, primCount*/ ); + } +#endif + + tbf::RenderFix::draw_state.draws++; + + return + D3D9DrawPrimitiveUP_Original ( This, + PrimitiveType, + PrimitiveCount, + pVertexStreamZeroData, + VertexStreamZeroStride ); +} + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9DrawIndexedPrimitiveUP_Detour ( IDirect3DDevice9* This, + D3DPRIMITIVETYPE PrimitiveType, + UINT MinVertexIndex, + UINT NumVertices, + UINT PrimitiveCount, + const void *pIndexData, + D3DFORMAT IndexDataFormat, + const void *pVertexStreamZeroData, + UINT VertexStreamZeroStride ) +{ +#if 0 + if (tsf::RenderFix::tracer.log && This == tsf::RenderFix::pDevice) { + dll_log->Log ( L"[FrameTrace] DrawIndexedPrimitiveUP (Type: %s) - NumVertices: %lu, PrimitiveCount: %lu"/* + L" [FrameTrace] -" + L" BaseIdx: %5li, MinVtxIdx: %5lu,\n" + L" [FrameTrace] -" + L" NumVertices: %5lu, startIndex: %5lu,\n" + L" [FrameTrace] -" + L" primCount: %5lu"*/, + SK_D3D9_PrimitiveTypeToStr (PrimitiveType), + NumVertices, PrimitiveCount/*, + BaseVertexIndex, MinVertexIndex, + NumVertices, startIndex, primCount*/ ); + } +#endif + + tbf::RenderFix::draw_state.draws++; + + return + D3D9DrawIndexedPrimitiveUP_Original ( + This, + PrimitiveType, + MinVertexIndex, + NumVertices, + PrimitiveCount, + pIndexData, + IndexDataFormat, + pVertexStreamZeroData, + VertexStreamZeroStride ); +} + + + + +typedef HRESULT (__stdcall *Reset_pfn)( + IDirect3DDevice9 *This, + D3DPRESENT_PARAMETERS *pPresentationParameters +); + +Reset_pfn D3D9Reset_Original = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +__stdcall +D3D9Reset_Detour ( IDirect3DDevice9 *This, + D3DPRESENT_PARAMETERS *pPresentationParameters ) +{ + if (This != tbf::RenderFix::pDevice) + return D3D9Reset_Original (This, pPresentationParameters); + + tbf::RenderFix::tex_mgr.reset (); + + tbf::RenderFix::pDevice = This; + + tbf::RenderFix::width = pPresentationParameters->BackBufferWidth; + tbf::RenderFix::height = pPresentationParameters->BackBufferHeight; + + HRESULT hr = + D3D9Reset_Original (This, pPresentationParameters); + + return hr; +} + + +void +tbf::RenderFix::Init (void) +{ + tex_mgr.Init (); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9SetSamplerState_Override", + D3D9SetSamplerState_Detour, + (LPVOID*)&D3D9SetSamplerState_Original, + &SetSamplerState ); + +#if 0 + TBF_CreateDLLHook ( L"d3d9.dll", "D3D9SetPixelShaderConstantF_Override", + D3D9SetPixelShaderConstantF_Detour, + (LPVOID*)&D3D9SetPixelShaderConstantF_Original ); +#endif + + + + +#if 1 + // Needed for shadow re-scaling + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9SetViewport_Override", + D3D9SetViewport_Detour, + (LPVOID*)&D3D9SetViewport_Original ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9SetVertexShaderConstantF_Override", + D3D9SetVertexShaderConstantF_Detour, + (LPVOID*)&D3D9SetVertexShaderConstantF_Original ); + + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9SetVertexShader_Override", + D3D9SetVertexShader_Detour, + (LPVOID*)&D3D9SetVertexShader_Original ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9SetPixelShader_Override", + D3D9SetPixelShader_Detour, + (LPVOID*)&D3D9SetPixelShader_Original ); + + // Needed for UI re-scaling + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9SetScissorRect_Override", + D3D9SetScissorRect_Detour, + (LPVOID*)&D3D9SetScissorRect_Original ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9EndScene_Override", + D3D9EndScene_Detour, + (LPVOID*)&D3D9EndScene_Original ); +#endif + + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), + "D3D9Reset_Override", + D3D9Reset_Detour, + (LPVOID *)&D3D9Reset_Original ); + +#if 0 + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9UpdateTexture_Override", + D3D9UpdateTexture_Detour, + (LPVOID*)&D3D9UpdateTexture_Original ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "D3D9UpdateSurface_Override", + D3D9UpdateSurface_Detour, + (LPVOID*)&D3D9UpdateSurface_Original ); +#endif + + user32_dll = LoadLibrary (L"User32.dll"); + + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "SK_BeginBufferSwap", + D3D9EndFrame_Pre, + (LPVOID*)&SK_BeginBufferSwap ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), "SK_EndBufferSwap", + D3D9EndFrame_Post, + (LPVOID*)&SK_EndBufferSwap ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), + "D3D9DrawPrimitive_Override", + D3D9DrawPrimitive_Detour, + (LPVOID*)&D3D9DrawPrimitive_Original ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), + "D3D9DrawIndexedPrimitive_Override", + D3D9DrawIndexedPrimitive_Detour, + (LPVOID*)&D3D9DrawIndexedPrimitive_Original ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), + "D3D9DrawPrimitiveUP_Override", + D3D9DrawPrimitiveUP_Detour, + (LPVOID*)&D3D9DrawPrimitiveUP_Original ); + + TBF_CreateDLLHook2 ( config.system.injector.c_str (), + "D3D9DrawIndexedPrimitiveUP_Override", + D3D9DrawIndexedPrimitiveUP_Detour, + (LPVOID*)&D3D9DrawIndexedPrimitiveUP_Original ); + + CommandProcessor* comm_proc = CommandProcessor::getInstance (); + + TBF_ApplyQueuedHooks (); +} + +void +tbf::RenderFix::Shutdown (void) +{ + tex_mgr.Shutdown (); +} + +tbf::RenderFix::CommandProcessor::CommandProcessor (void) +{ + SK_ICommandProcessor& command = + *SK_GetCommandProcessor (); + + fovy_ = TBF_CreateVar (SK_IVariable::Float, &config.render.fovy, this); + aspect_ratio_ = TBF_CreateVar (SK_IVariable::Float, &config.render.aspect_ratio, this); + + SK_IVariable* aspect_correct_vids = TBF_CreateVar (SK_IVariable::Boolean, &config.render.blackbar_videos); + SK_IVariable* aspect_correction = TBF_CreateVar (SK_IVariable::Boolean, &config.render.aspect_correction); + + SK_IVariable* remaster_textures = TBF_CreateVar (SK_IVariable::Boolean, &config.textures.remaster); + SK_IVariable* rescale_shadows = TBF_CreateVar (SK_IVariable::Int, &config.render.shadow_rescale); + SK_IVariable* rescale_env_shadows = TBF_CreateVar (SK_IVariable::Int, &config.render.env_shadow_rescale); + SK_IVariable* postproc_ratio = TBF_CreateVar (SK_IVariable::Float, &config.render.postproc_ratio); + SK_IVariable* clear_blackbars = TBF_CreateVar (SK_IVariable::Boolean, &config.render.clear_blackbars); + + command.AddVariable ("AspectRatio", aspect_ratio_); + command.AddVariable ("FOVY", fovy_); + + command.AddVariable ("AspectCorrectVideos", aspect_correct_vids); + command.AddVariable ("AspectCorrection", aspect_correction); + command.AddVariable ("RemasterTextures", remaster_textures); + command.AddVariable ("RescaleShadows", rescale_shadows); + command.AddVariable ("RescaleEnvShadows", rescale_env_shadows); + command.AddVariable ("PostProcessRatio", postproc_ratio); + command.AddVariable ("ClearBlackbars", clear_blackbars); + + command.AddVariable ("TestVS", TBF_CreateVar (SK_IVariable::Int, &TEST_VS)); + + uint8_t signature [] = { 0x39, 0x8E, 0xE3, 0x3F, + 0xDB, 0x0F, 0x49, 0x3F }; + + if (*(float *)config.render.aspect_addr != 16.0f / 9.0f) { + void* addr = TBF_Scan (signature, sizeof (float) * 2, nullptr); + if (addr != nullptr) { + dll_log->Log (L"[Asp. Ratio] Scanned Aspect Ratio Address: %06Xh", addr); + config.render.aspect_addr = (DWORD)addr; + dll_log->Log (L"[Asp. Ratio] Scanned FOVY Address: %06Xh", (float *)addr + 1); + config.render.fovy_addr = (DWORD)((float *)addr + 1); + } + else { + dll_log->Log (L"[Asp. Ratio] >> ERROR: Unable to find Aspect Ratio Address!"); + } + } +} + +bool +tbf::RenderFix::CommandProcessor::OnVarChange (SK_IVariable* var, void* val) +{ + DWORD dwOld; + + if (var == aspect_ratio_) { + VirtualProtect ((LPVOID)config.render.aspect_addr, 4, PAGE_READWRITE, &dwOld); + float original = *((float *)config.render.aspect_addr); + + if (((original < 1.777778f + 0.01f && original > 1.777778f - 0.01f) || + (original == config.render.aspect_ratio)) + && val != nullptr) { + config.render.aspect_ratio = *(float *)val; + + if (fabs (original - config.render.aspect_ratio) > 0.01f) { + dll_log->Log ( L"[Asp. Ratio] * Changing Aspect Ratio from %f to %f", + original, + config.render.aspect_ratio ); + *((float *)config.render.aspect_addr) = config.render.aspect_ratio; + } + } + else { + if (val != nullptr) + dll_log->Log ( L"[Asp. Ratio] * Unable to change Aspect Ratio, invalid memory address... (%f)", + *((float *)config.render.aspect_addr) ); + } + } + + if (var == fovy_) { + VirtualProtect ((LPVOID)config.render.fovy_addr, 4, PAGE_READWRITE, &dwOld); + float original = *((float *)config.render.fovy_addr); + + if (((original < 0.785398f + 0.01f && original > 0.785398f - 0.01f) || + (original == config.render.fovy)) + && val != nullptr) { + config.render.fovy = *(float *)val; + dll_log->Log ( L"[Asp. Ratio] * Changing FOVY from %f to %f", + original, + config.render.fovy ); + *((float *)config.render.fovy_addr) = config.render.fovy; + } + else { + if (val != nullptr) + dll_log->Log ( L"[Asp. Ratio] * Unable to change FOVY, invalid memory address... (%f)", + *((float *)config.render.fovy_addr) ); + } + } + + return true; +} + + +tbf::RenderFix::CommandProcessor* tbf::RenderFix::CommandProcessor::pCommProc; + +HWND tbf::RenderFix::hWndDevice = NULL; +IDirect3DDevice9* tbf::RenderFix::pDevice = nullptr; + +uint32_t tbf::RenderFix::width = 0UL; +uint32_t tbf::RenderFix::height = 0UL; +uint32_t tbf::RenderFix::dwRenderThreadID = 0UL; + +IDirect3DSurface9* tbf::RenderFix::pPostProcessSurface = nullptr; +bool tbf::RenderFix::bink = false; + +HMODULE tbf::RenderFix::user32_dll = 0; \ No newline at end of file diff --git a/src/scanner.cpp b/src/scanner.cpp new file mode 100644 index 0000000..a2511cd --- /dev/null +++ b/src/scanner.cpp @@ -0,0 +1,90 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#include "scanner.h" + +#include + +void* +TBF_Scan (uint8_t* pattern, size_t len, uint8_t* mask) +{ + uint8_t* base_addr = (uint8_t *)GetModuleHandle (nullptr); + + MEMORY_BASIC_INFORMATION mem_info; + VirtualQuery (base_addr, &mem_info, sizeof mem_info); + + IMAGE_DOS_HEADER* pDOS = + (IMAGE_DOS_HEADER *)mem_info.AllocationBase; + IMAGE_NT_HEADERS* pNT = + (IMAGE_NT_HEADERS *)((uintptr_t)(pDOS + pDOS->e_lfanew)); + + uint8_t* end_addr = base_addr + pNT->OptionalHeader.SizeOfImage; + + uint8_t* begin = (uint8_t *)base_addr; + uint8_t* it = begin; + int idx = 0; + + while (it < end_addr) + { + VirtualQuery (it, &mem_info, sizeof mem_info); + + uint8_t* next_rgn = (uint8_t *)mem_info.BaseAddress + mem_info.RegionSize; + + if (mem_info.Type != MEM_IMAGE || mem_info.State != MEM_COMMIT || mem_info.Protect & PAGE_NOACCESS) { + it = next_rgn; + idx = 0; + begin = it; + continue; + } + + while (it < next_rgn) { + uint8_t* scan_addr = it; + + bool match = (*scan_addr == pattern [idx]); + + // For portions we do not care about... treat them + // as matching. + if (mask != nullptr && (! mask [idx])) + match = true; + + if (match) { + if (++idx == len) + return (void *)begin; + + ++it; + } + + else { + // No match?! + if (it > end_addr - len) + break; + + + + it = ++begin; + idx = 0; + } + } + } + + return nullptr; +} \ No newline at end of file diff --git a/src/sound.cpp b/src/sound.cpp new file mode 100644 index 0000000..54192b1 --- /dev/null +++ b/src/sound.cpp @@ -0,0 +1,854 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#define _CRT_SECURE_NO_WARNINGS + +#include + +#include "log.h" +#include "config.h" +#include "sound.h" +#include "hook.h" + +#include + +#include + +#include +#include +#include +#include +#include + +// +// Create and reuse a single DirectSound Instance to minimize issues with Bink +// +IDirectSound* g_pDS = nullptr; +bool new_session = false; + +iSK_Logger* audio_log; + +bool tbf::SoundFix::wasapi_init = false; + +WAVEFORMATEX tbf::SoundFix::snd_core_fmt; +WAVEFORMATEX tbf::SoundFix::snd_bink_fmt; +WAVEFORMATEX tbf::SoundFix::snd_device_fmt; + +HMODULE tbf::SoundFix::dsound_dll = 0; +HMODULE tbf::SoundFix::ole32_dll = 0; + +WAVEFORMATEXTENSIBLE g_DeviceFormat; + +const wchar_t* +TBF_DescribeHRESULT (HRESULT result) +{ + switch (result) + { + /* Generic (SUCCEEDED) */ + + case S_OK: + return L"S_OK"; + + case S_FALSE: + return L"S_FALSE"; + + + /* DirectSound */ + case DS_NO_VIRTUALIZATION: + return L"DS_NO_VIRTUALIZATION"; + + case DSERR_ALLOCATED: + return L"DSERR_ALLOCATED"; + + case DSERR_CONTROLUNAVAIL: + return L"DSERR_CONTROLUNAVAIL"; + + case DSERR_INVALIDPARAM: + return L"DSERR_INVALIDPARAM"; + + case DSERR_INVALIDCALL: + return L"DSERR_INVALIDCALL"; + + case DSERR_GENERIC: + return L"DSERR_GENERIC"; + + case DSERR_PRIOLEVELNEEDED: + return L"DSERR_PRIOLEVELNEEDED"; + + case DSERR_OUTOFMEMORY: + return L"DSERR_OUTOFMEMORY"; + + case DSERR_BADFORMAT: + return L"DSERR_BADFORMAT"; + + case DSERR_UNSUPPORTED: + return L"DSERR_UNSUPPORTED"; + + case DSERR_NODRIVER: + return L"DSERR_NODRIVER"; + + case DSERR_ALREADYINITIALIZED: + return L"DSERR_ALREADYINITIALIZED"; + + case DSERR_NOAGGREGATION: + return L"DSERR_NOAGGREGATION"; + + case DSERR_BUFFERLOST: + return L"DSERR_BUFFERLOST"; + + case DSERR_OTHERAPPHASPRIO: + return L"DSERR_OTHERAPPHASPRIO"; + + case DSERR_UNINITIALIZED: + return L"DSERR_UNINITIALIZED"; + + case DSERR_NOINTERFACE: + return L"DSERR_NOINTERFACE"; + + case DSERR_ACCESSDENIED: + return L"DSERR_ACCESSDENIED"; + + case DSERR_BUFFERTOOSMALL: + return L"DSERR_BUFFERTOOSMALL"; + + case DSERR_DS8_REQUIRED: + return L"DSERR_DS8_REQUIRED"; + + case DSERR_SENDLOOP: + return L"DSERR_SENDLOOP"; + + case DSERR_BADSENDBUFFERGUID: + return L"DSERR_BADSENDBUFFERGUID"; + + case DSERR_OBJECTNOTFOUND: + return L"DSERR_OBJECTNOTFOUND"; + + case DSERR_FXUNAVAILABLE: + return L"DSERR_FXUNAVAILABLE"; + + +#if 0 + /* Generic (FAILED) */ + + case E_FAIL: + return L"E_FAIL"; + + case E_INVALIDARG: + return L"E_INVALIDARG"; + + case E_OUTOFMEMORY: + return L"E_OUTOFMEMORY"; + + case E_NOTIMPL: + return L"E_NOTIMPL"; +#endif + + + default: + audio_log->Log ( L" *** Encountered unknown HRESULT: (0x%08X)", + (unsigned long)result ); + return L"UNKNOWN"; + } +} + +#define DSOUND_CALL(_Ret, _Call) { \ + audio_log->LogEx (true, L" Calling original function: "); \ + (_Ret) = (_Call); \ + audio_log->LogEx (false, L"(ret=%s)\n\n", TBF_DescribeHRESULT (_Ret));\ +} + +typedef HRESULT (WINAPI *DSound_GetSpeakerConfig_t) + (IDirectSound *This, + _Out_ LPDWORD pdwSpeakerConfig); + +DSound_GetSpeakerConfig_t DSound_GetSpeakerConfig_Original = nullptr; + +HRESULT +WINAPI DSound_GetSpeakerConfig (IDirectSound *This, + _Out_ LPDWORD pdwSpeakerConfig) +{ + audio_log->Log ( L"[!] %s (%08Xh, %08Xh) - " + L"[Calling Thread: 0x%04x]", + L"IDirectSound::GetSpeakerConfig", + This, + pdwSpeakerConfig, + GetCurrentThreadId () + ); + + HRESULT ret; + DSOUND_CALL(ret,DSound_GetSpeakerConfig_Original (This, pdwSpeakerConfig)); + + if (*pdwSpeakerConfig == DSSPEAKER_7POINT1_SURROUND || + *pdwSpeakerConfig == DSSPEAKER_7POINT1 ) { + *pdwSpeakerConfig = DSSPEAKER_5POINT1_SURROUND; + audio_log->Log ( L"IDirectSound::GetSpeakerConfig (...) : " + L"Reporting 7.1 system as 5.1\n" ); + } + + return ret; +} + +typedef HRESULT (WINAPI *DSound_SetSpeakerConfig_t) + (IDirectSound *This, + _In_ DWORD dwSpeakerConfig); + +DSound_SetSpeakerConfig_t DSound_SetSpeakerConfig_Original = nullptr; + +HRESULT +WINAPI DSound_SetSpeakerConfig (IDirectSound *This, + _In_ DWORD dwSpeakerConfig) +{ + audio_log->Log ( L"[!] %s (%08Xh, %08Xh) - " + L"[Calling Thread: 0x%04x]", + L"IDirectSound::SetSpeakerConfig", + This, dwSpeakerConfig, + GetCurrentThreadId () + ); + + HRESULT ret; + DSOUND_CALL(ret,DSound_SetSpeakerConfig_Original (This, dwSpeakerConfig)); + + if (dwSpeakerConfig == DSSPEAKER_7POINT1_SURROUND || + dwSpeakerConfig == DSSPEAKER_7POINT1 ) { + dwSpeakerConfig = DSSPEAKER_5POINT1_SURROUND; + audio_log->Log ( L"IDirectSound::SetSpeakerConfig (...) : " + L"Changing 7.1 system to 5.1\n" ); + } + + return ret; +} + +typedef HRESULT (WINAPI *DSound_GetSpeakerConfig8_t) + (IDirectSound8 *This, + _Out_ LPDWORD pdwSpeakerConfig); + +DSound_GetSpeakerConfig8_t DSound_GetSpeakerConfig8_Original = nullptr; + +HRESULT +WINAPI DSound_GetSpeakerConfig8 (IDirectSound8 *This, + _Out_ LPDWORD pdwSpeakerConfig) +{ + audio_log->Log ( L"[!] %s (%08Xh, %08Xh) - " + L"[Calling Thread: 0x%04x]", + L"IDirectSound8::GetSpeakerConfig", + This, pdwSpeakerConfig, + GetCurrentThreadId () + ); + + HRESULT ret; + DSOUND_CALL(ret,DSound_GetSpeakerConfig8_Original (This, pdwSpeakerConfig)); + + if (*pdwSpeakerConfig == DSSPEAKER_7POINT1_SURROUND || + *pdwSpeakerConfig == DSSPEAKER_7POINT1 ) { + *pdwSpeakerConfig = DSSPEAKER_5POINT1_SURROUND; + audio_log->Log ( L"IDirectSound8::GetSpeakerConfig (...) : " + L"Reporting 7.1 system as 5.1\n" ); + } + + return ret; +} + +typedef HRESULT (WINAPI *DirectSoundCreate_t) +( _In_opt_ LPCGUID pcGuidDevice, + _Outptr_ LPDIRECTSOUND *ppDS, + _Pre_null_ LPUNKNOWN pUnkOuter); + +static DirectSoundCreate_t DirectSoundCreate_Original = nullptr; + +_Check_return_ +HRESULT +WINAPI +DirectSoundCreate_Detour (_In_opt_ LPCGUID pcGuidDevice, + _Outptr_ LPDIRECTSOUND *ppDS, + _Pre_null_ LPUNKNOWN pUnkOuter) +{ + //new_session = false; + //g_DeviceFormat.Format.cbSize = 0; + + audio_log->Log ( L"[!] %s (%08Xh, %08Xh, %08Xh) - " + L"[Calling Thread: 0x%04x]", + L"DirectSoundCreate", + pcGuidDevice, ppDS, pUnkOuter, + GetCurrentThreadId () + ); + + if (g_pDS) { + audio_log->Log ( L" @ Short-Circuiting and returning previous IDirectSound " + L"interface." ); + + *ppDS = g_pDS; + (*ppDS)->AddRef (); + + return S_OK; + } + + HRESULT ret = DirectSoundCreate_Original (pcGuidDevice, ppDS, pUnkOuter); + + if (SUCCEEDED (ret)) { + void** vftable = *(void***)*ppDS; + + TBF_CreateFuncHook ( L"IDirectSound::GetSpeakerConfig", + vftable [8], + DSound_GetSpeakerConfig, + (LPVOID *)&DSound_GetSpeakerConfig_Original ); + + TBF_EnableHook (vftable [8]); + + TBF_CreateFuncHook ( L"IDirectSound::SetSpeakerConfig", + vftable [9], + DSound_SetSpeakerConfig, + (LPVOID *)&DSound_SetSpeakerConfig_Original ); + + TBF_EnableHook (vftable [9]); + + DWORD dwSpeaker; + + if (SUCCEEDED ((*ppDS)->GetSpeakerConfig (&dwSpeaker))) + (*ppDS)->SetSpeakerConfig (dwSpeaker); + } + else { + _com_error ce (ret); + audio_log->Log ( L" > FAILURE %s - (%s)", TBF_DescribeHRESULT (ret), + ce.ErrorMessage () ); + } + + return ret; +} + + + +typedef HRESULT (STDAPICALLTYPE *CoCreateInstance_t)( + _In_ REFCLSID rclsid, + _In_opt_ LPUNKNOWN pUnkOuter, + _In_ DWORD dwClsContext, + _In_ REFIID riid, +_COM_Outptr_ _At_(*ppv, _Post_readable_size_(_Inexpressible_(varies))) + LPVOID FAR* + ppv); + +CoCreateInstance_t CoCreateInstance_Original = nullptr; + +IMMDevice* g_pAudioDev; + + +typedef HRESULT (STDMETHODCALLTYPE *IAudioClient_GetMixFormat_t) + (IAudioClient *This, + _Out_ WAVEFORMATEX **ppDeviceFormat); + +IAudioClient_GetMixFormat_t IAudioClient_GetMixFormat_Original = nullptr; + +HRESULT +STDMETHODCALLTYPE +IAudioClient_GetMixFormat_Detour (IAudioClient *This, + _Out_ WAVEFORMATEX **ppDeviceFormat) +{ + audio_log->Log (L" [!] IAudioClient::GetMixFormat (%08Xh)", This); + + if (new_session) { + audio_log->Log ( L" >> Overriding - " + L"Using Device Format rather than Mix Format <<" ); + + WAVEFORMATEX* pMixFormat; + IPropertyStore* pStore; + + if (SUCCEEDED (g_pAudioDev->OpenPropertyStore (STGM_READ, &pStore))) + { + PROPVARIANT property; + + if (SUCCEEDED (pStore->GetValue ( + PKEY_AudioEngine_DeviceFormat, &property + ) + ) + ) + { + pMixFormat = (PWAVEFORMATEX)property.blob.pBlobData; + if (pMixFormat->cbSize > sizeof (WAVEFORMATEX)) { + if (pMixFormat->cbSize == 22) { + memcpy (&tbf::SoundFix::snd_device_fmt, pMixFormat, sizeof (WAVEFORMATEX)); + + std::wstring format_name; + + if (((PWAVEFORMATEXTENSIBLE)pMixFormat)->SubFormat == + KSDATAFORMAT_SUBTYPE_PCM) + format_name = L"PCM"; + if (((PWAVEFORMATEXTENSIBLE)pMixFormat)->SubFormat == + KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) + format_name = L"IEEE Floating-Point"; + else + format_name = L"Unknown"; + + audio_log->Log (L" Extensible WAVEFORMAT Found..."); + audio_log->Log (L" %lu Channels, %lu Samples Per Sec, %lu Bits" + L" Per Sample (%lu Actual)", + ((PWAVEFORMATEXTENSIBLE)pMixFormat)->Format.nChannels, + ((PWAVEFORMATEXTENSIBLE)pMixFormat)->Format.nSamplesPerSec, + ((PWAVEFORMATEXTENSIBLE)pMixFormat)->Format.wBitsPerSample, + ((PWAVEFORMATEXTENSIBLE)pMixFormat)->Samples.wValidBitsPerSample); + audio_log->Log (L" Format: %s", format_name.c_str ()); + + pMixFormat->wBitsPerSample = + ((PWAVEFORMATEXTENSIBLE)pMixFormat)->Samples.wValidBitsPerSample; + + DWORD dwChannels = + ((PWAVEFORMATEXTENSIBLE)pMixFormat)->dwChannelMask; + + audio_log->LogEx (true, L" Speaker Geometry:"); + + if (dwChannels & SPEAKER_FRONT_LEFT) + audio_log->LogEx (false, L" FrontLeft"); + if (dwChannels & SPEAKER_FRONT_RIGHT) + audio_log->LogEx (false, L" FrontRight"); + if (dwChannels & SPEAKER_FRONT_CENTER) + audio_log->LogEx (false, L" FrontCenter"); + if (dwChannels & SPEAKER_LOW_FREQUENCY) + audio_log->LogEx (false, L" LowFrequencyEmitter"); + if (dwChannels & SPEAKER_BACK_LEFT) + audio_log->LogEx (false, L" BackLeft"); + if (dwChannels & SPEAKER_BACK_RIGHT) + audio_log->LogEx (false, L" BackRight"); + if (dwChannels & SPEAKER_FRONT_LEFT_OF_CENTER) + audio_log->LogEx (false, L" FrontLeftOfCenter"); + if (dwChannels & SPEAKER_FRONT_RIGHT_OF_CENTER) + audio_log->LogEx (false, L" FrontRightOfCenter"); + if (dwChannels & SPEAKER_SIDE_LEFT) + audio_log->LogEx (false, L" SideLeft"); + if (dwChannels & SPEAKER_SIDE_RIGHT) + audio_log->LogEx (false, L" SideRight"); + + audio_log->LogEx (false, L"\n"); + } + } + + if (pMixFormat->nChannels > config.audio.channels) { + audio_log->Log ( L" ** Downmixing from %lu channels to %lu", + pMixFormat->nChannels, config.audio.channels ); + pMixFormat->nChannels = config.audio.channels; + } + + #define TARGET_SAMPLE_RATE config.audio.sample_hz + + if (pMixFormat->nSamplesPerSec != TARGET_SAMPLE_RATE) { + audio_log->Log ( L" ** Resampling Audiostream from %lu Hz to %lu Hz", + pMixFormat->nSamplesPerSec, TARGET_SAMPLE_RATE ); + pMixFormat->nSamplesPerSec = TARGET_SAMPLE_RATE; + } + + pMixFormat->nAvgBytesPerSec = (pMixFormat->nSamplesPerSec * pMixFormat->nChannels * pMixFormat->wBitsPerSample) >> 3; + + g_DeviceFormat.Format.cbSize = 22; + g_DeviceFormat.Format.nSamplesPerSec = pMixFormat->nSamplesPerSec; + g_DeviceFormat.Format.nChannels = pMixFormat->nChannels; + g_DeviceFormat.Format.nBlockAlign = pMixFormat->nBlockAlign; + g_DeviceFormat.Format.nAvgBytesPerSec = pMixFormat->nAvgBytesPerSec; + g_DeviceFormat.Format.wBitsPerSample = pMixFormat->wBitsPerSample; + //g_DeviceFormat.Format.wBitsPerSample = ((PWAVEFORMATEXTENSIBLE)pMixFormat)->Samples.wValidBitsPerSample; + + // We may have gotten an extensible wave format, but... we need to + // truncate this sucker to a plain old WAVEFORMATEX + HRESULT hr = IAudioClient_GetMixFormat_Original (This, ppDeviceFormat); + memcpy (*ppDeviceFormat, pMixFormat, (*ppDeviceFormat)->cbSize); + + pStore->Release (); + + return hr; + } + + pStore->Release (); + } + } + + return IAudioClient_GetMixFormat_Original (This, ppDeviceFormat); +} + + +typedef HRESULT (STDMETHODCALLTYPE *IAudioClient_Initialize_t) + (IAudioClient *This, + _In_ AUDCLNT_SHAREMODE ShareMode, + _In_ DWORD StreamFlags, + _In_ REFERENCE_TIME hnsBufferDuration, + _In_ REFERENCE_TIME hnsPeriodicity, + _In_ const WAVEFORMATEX *pFormat, + _In_opt_ LPCGUID AudioSessionGuid); + +IAudioClient_Initialize_t IAudioClient_Initialize_Original = nullptr; + +HRESULT +STDMETHODCALLTYPE +IAudioClient_Initialize_Detour (IAudioClient *This, + _In_ AUDCLNT_SHAREMODE ShareMode, + _In_ DWORD StreamFlags, + _In_ REFERENCE_TIME hnsBufferDuration, + _In_ REFERENCE_TIME hnsPeriodicity, + _In_ const WAVEFORMATEX *pFormat, + _In_opt_ LPCGUID AudioSessionGuid) +{ + audio_log->Log (L" [!] IAudioClient::Initialize (...)"); + + audio_log->Log ( + L" {Share Mode: %li - Stream Flags: 0x%04X}", ShareMode, StreamFlags ); + audio_log->Log ( + L" >> Channels: %lu, Samples Per Sec: %lu, Bits Per Sample: %hu\n", + pFormat->nChannels, pFormat->nSamplesPerSec, pFormat->wBitsPerSample ); + +#if 0 + WAVEFORMATEX format; + + format.cbSize = pFormat->cbSize; + format.nAvgBytesPerSec = pFormat->nAvgBytesPerSec; + format.nBlockAlign = pFormat->nBlockAlign; + format.nChannels = pFormat->nChannels; + format.nSamplesPerSec = pFormat->nSamplesPerSec; + format.wBitsPerSample = pFormat->wBitsPerSample; + format.wFormatTag = pFormat->wFormatTag; + + WAVEFORMATEX *pClosestMatch = + (PWAVEFORMATEX)CoTaskMemAlloc (sizeof (WAVEFORMATEXTENSIBLE)); + + if (This->IsFormatSupported (AUDCLNT_SHAREMODE_SHARED, pFormat, &pClosestMatch) == S_OK) { + CoTaskMemFree (pClosestMatch); + pClosestMatch = &format; + } +#endif + + WAVEFORMATEX *pClosestMatch = (WAVEFORMATEX *)pFormat; + + #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 + #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000 + + // These two flags were observed when using WinMM to play a sound that had a + // different format than the output device. Like the SRC_DEFAULT_QUALITY + // flag above, these are undocumented. + #define AUDCLNT_STREAMFLAGS_UNKNOWN4000000 0x4000000 + #define AUDCLNT_STREAMFLAGS_UNKNOWN0100000 0x0100000 + + // This is REQUIRED or we cannot get the shared stream to work this way! + StreamFlags |= ( AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | + AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY ); + + // Since these two flags are even LESS documented than the ones above (which + // are needed, make the use of these optional). + if (! config.audio.compatibility) { + StreamFlags |= ( AUDCLNT_STREAMFLAGS_UNKNOWN0100000 | + AUDCLNT_STREAMFLAGS_UNKNOWN4000000 ); + } + + if (g_DeviceFormat.Format.cbSize == 22) { + pClosestMatch->nChannels = g_DeviceFormat.Format.nChannels; + pClosestMatch->nSamplesPerSec = g_DeviceFormat.Format.nSamplesPerSec; + //pClosestMatch->nBlockAlign = g_DeviceFormat.Format.nBlockAlign; + //pClosestMatch->nAvgBytesPerSec = g_DeviceFormat.Format.nAvgBytesPerSec; + //pClosestMatch->wBitsPerSample = g_DeviceFormat.Format.wBitsPerSample; + } + + HRESULT ret = + IAudioClient_Initialize_Original (This, AUDCLNT_SHAREMODE_SHARED, + StreamFlags, hnsBufferDuration, + hnsPeriodicity, pClosestMatch, + AudioSessionGuid); + + if (new_session) + memcpy (&tbf::SoundFix::snd_core_fmt, pFormat, sizeof (WAVEFORMATEX)); + + _com_error error (ret); + +#if 0 + if (pClosestMatch != &format) + CoTaskMemFree (pClosestMatch); +#endif + + audio_log->Log ( L" Result: 0x%04X (%s)\n", ret - AUDCLNT_ERR (0x0000), + error.ErrorMessage () ); + + //new_session = false; + //DSOUND_VIRTUAL_OVERRIDE ( ppAudioClient, 8, "IAudioClient::GetMixFormat", + //IAudioClient_GetMixFormat_Original, + //IAudioClient_GetMixFormat_Original, + //IAudioClient_GetMixFormat_t ); + + return ret; +} + +typedef HRESULT (STDMETHODCALLTYPE *IMMDevice_Activate_t) + (IMMDevice *This, + _In_ REFIID iid, + _In_ DWORD dwClsCtx, + _In_opt_ PROPVARIANT *pActivationParams, + _Out_ void **ppInterface); + +IMMDevice_Activate_t IMMDevice_Activate_Original = nullptr; + +HRESULT +STDMETHODCALLTYPE +IMMDevice_Activate_Detour (IMMDevice *This, + _In_ REFIID iid, + _In_ DWORD dwClsCtx, + _In_opt_ PROPVARIANT *pActivationParams, + _Out_ void **ppInterface) +{ + if (iid == __uuidof (IAudioSessionManager2)) { + audio_log->Log ( L" >> IMMDevice Activating IAudioSessionManager2 Interface " + L" (new_session = true)" ); + new_session = true; + } + + else if (iid == __uuidof (IAudioClient)) { + audio_log->Log (L" >> IMMDevice Activating IAudioClient Interface"); + } + + else { + std::wstring iid_str; + wchar_t* pwszIID; + + if (SUCCEEDED (StringFromIID (iid, (LPOLESTR *)&pwszIID))) + { + iid_str = pwszIID; + CoTaskMemFree (pwszIID); + } + + audio_log->Log (L"IMMDevice::Activate (%s, ...)\n", iid_str.c_str ()); + } + + + + HRESULT ret = + IMMDevice_Activate_Original ( This, + iid, + dwClsCtx, + pActivationParams, + ppInterface ); + + if (SUCCEEDED (ret)) { + if (iid == __uuidof (IAudioClient)) { + IAudioClient** ppAudioClient = (IAudioClient **)ppInterface; + + // vtbl (ppAudioClient) + // -------------------- + // 3 Initialize + // 4 GetBufferSize + // 5 GetStreamLatency + // 6 GetCurrentPadding + // 7 IsFormatSupported + // 8 GetMixFormat + // 9 GetDevicePeriod + // 10 Start + // 11 Stop + // 12 Reset + // 13 SetEventHandle + // 14 GetService + + void** vftable = *(void***)*ppAudioClient; + + TBF_CreateFuncHook ( L"IAudioClient::Initialize", + vftable [3], + IAudioClient_Initialize_Detour, + (LPVOID *)&IAudioClient_Initialize_Original ); + + TBF_EnableHook (vftable [3]); + + TBF_CreateFuncHook ( L"IAudioClient::GetMixFormat", + vftable [8], + IAudioClient_GetMixFormat_Detour, + (LPVOID *)&IAudioClient_GetMixFormat_Original ); + + TBF_EnableHook (vftable [8]); + + g_pAudioDev = This; + + // Stop listening to device enumeration and so forth after the first + // audio session is created... this is behavior specific to this game. + if (new_session) { + tbf::SoundFix::wasapi_init = true; + } + } + } + + return ret; +} + +HRESULT +STDAPICALLTYPE +CoCreateInstance_Detour (_In_ REFCLSID rclsid, + _In_opt_ LPUNKNOWN pUnkOuter, + _In_ DWORD dwClsContext, + _In_ REFIID riid, + _COM_Outptr_ _At_(*ppv, _Post_readable_size_(_Inexpressible_(varies))) + LPVOID FAR* ppv) +{ + HRESULT hr = + CoCreateInstance_Original (rclsid, pUnkOuter, dwClsContext, riid, ppv); + + if (SUCCEEDED (hr) && riid == __uuidof (IMMDeviceEnumerator)) { + IMMDeviceEnumerator** ppEnum = (IMMDeviceEnumerator **)ppv; + + IMMDevice* pDev; + HRESULT hr2 = + (*ppEnum)->GetDefaultAudioEndpoint (eRender, eConsole, &pDev); + + // vtbl (&pDev) + // ------------ + // 3 Activate + + if (SUCCEEDED (hr2) && pDev != nullptr) { + void** vftable = *(void***)*&pDev; + + TBF_CreateFuncHook ( L"IMMDevice::Activate", + vftable [3], + IMMDevice_Activate_Detour, + (LPVOID *)&IMMDevice_Activate_Original ); + + TBF_EnableHook (vftable [3]); + + pDev->Release (); + } + + // vtbl (ppEnum) + // ------------- + // 3 EnumAudioEndpoints + // 4 GetDefaultAudioEndpoint + // 5 GetDevice + } + + return hr; +} + + + +#include "hook.h" + +LPVOID pfnCoCreateInstance = nullptr; +LPVOID pfnDirectSoundCreate = nullptr; + +void +tbf::SoundFix::Init (void) +{ + CommandProcessor* pCmdProc = CommandProcessor::getInstance (); + + if (! config.audio.enable_fix) + return; + + audio_log = TBF_CreateLog (L"logs/audio.log"); + + audio_log->Log (L"audio.log created"); + + // Not setup yet + g_DeviceFormat.Format.cbSize = 0; + + dsound_dll = LoadLibrary (L"dsound.dll"); + ole32_dll = LoadLibrary (L"Ole32.dll"); + + audio_log->LogEx (true, L"@ Hooking DirectSoundCreate... "); + + TBF_CreateDLLHook ( L"dsound.dll", "DirectSoundCreate", + DirectSoundCreate_Detour, + (LPVOID *)&DirectSoundCreate_Original, + (LPVOID *)&pfnDirectSoundCreate ); + + TBF_EnableHook (pfnDirectSoundCreate); + + audio_log->LogEx (false, L"%06Xh\n", pfnDirectSoundCreate); + + // We need DSSCL_EXCLUSIVE for Bink to work, or at least we did + // when the code was originally written -- test this in the future. + DirectSoundCreate_Detour (NULL, &g_pDS, NULL); + g_pDS->SetCooperativeLevel (NULL, DSSCL_EXCLUSIVE); + + new_session = true; + + audio_log->LogEx (true, L"@ Hooking CoCreateInstance... "); + + TBF_CreateDLLHook ( L"Ole32.dll", "CoCreateInstance", + CoCreateInstance_Detour, + (LPVOID *)&CoCreateInstance_Original, + (LPVOID *)&pfnCoCreateInstance ); + + TBF_EnableHook (pfnCoCreateInstance); + + audio_log->LogEx (false, L"%06Xh\n", pfnCoCreateInstance); +} + +void +tbf::SoundFix::Shutdown (void) +{ + if (! config.audio.enable_fix) + return; + + //TBF_RemoveHook (pfnCoCreateInstance); + //TBF_RemoveHook (pfnDirectSoundCreate); + + FreeLibrary (dsound_dll); + FreeLibrary (ole32_dll); + + audio_log->Log (L"Closing log file..."); + audio_log->close (); +} + + +class SndInfoCmd : public SK_ICommand { +public: + virtual SK_ICommandResult execute (const char* szArgs) { + char info_str [2048]; + sprintf (info_str, "\n" + " (SoundCore) SampleRate: %6lu Channels: %lu Format: 0x%04X BytesPerSec: %7lu BitsPerSample: %lu\n" + " ( Device ) SampleRate: %6lu Channels: %lu Format: 0x%04X BytesPerSec: %7lu BitsPerSample: %lu\n", + tbf::SoundFix::snd_core_fmt.nSamplesPerSec, + tbf::SoundFix::snd_core_fmt.nChannels, + tbf::SoundFix::snd_core_fmt.wFormatTag, + tbf::SoundFix::snd_core_fmt.nAvgBytesPerSec, + tbf::SoundFix::snd_core_fmt.wBitsPerSample, + + tbf::SoundFix::snd_device_fmt.nSamplesPerSec, + tbf::SoundFix::snd_device_fmt.nChannels, + tbf::SoundFix::snd_device_fmt.wFormatTag, + tbf::SoundFix::snd_device_fmt.nAvgBytesPerSec, + tbf::SoundFix::snd_device_fmt.wBitsPerSample); + + return SK_ICommandResult ("SoundInfo", "", info_str, 1); + } +}; + +tbf::SoundFix::CommandProcessor::CommandProcessor (void) +{ + SK_ICommandProcessor& command = + *SK_GetCommandProcessor (); + + sample_rate_ = TBF_CreateVar (SK_IVariable::Int, (int *)&config.audio.sample_hz); + channels_ = TBF_CreateVar (SK_IVariable::Int, (int *)&config.audio.channels); + enable_ = TBF_CreateVar (SK_IVariable::Boolean, &config.audio.enable_fix); + compatibility_ = TBF_CreateVar (SK_IVariable::Boolean, &config.audio.compatibility); + + command.AddVariable ("SampleRate", sample_rate_); + command.AddVariable ("Channels", channels_); + command.AddVariable ("SoundFixCompatibility", compatibility_); + command.AddVariable ("EnableSoundFix", enable_); + + SndInfoCmd* sndinfo = new SndInfoCmd (); + command.AddCommand ("SoundInfo", sndinfo); +} + +bool +tbf::SoundFix::CommandProcessor::OnVarChange (SK_IVariable* var, void* val) +{ + return true; +} + + +tbf::SoundFix::CommandProcessor* tbf::SoundFix::CommandProcessor::pCommProc; \ No newline at end of file diff --git a/src/steam.cpp b/src/steam.cpp new file mode 100644 index 0000000..e82109b --- /dev/null +++ b/src/steam.cpp @@ -0,0 +1,175 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#include + +#include "steam.h" +#include "log.h" +#include "config.h" +#include "hook.h" + +#include + +HMODULE tbf::SteamFix::steam_dll = 0; + +typedef uint32_t AppId_t; + +class ISteamVideo +{ +public: + + // Get a URL suitable for streaming the given Video app ID's video + virtual void GetVideoURL( AppId_t unVideoAppID ) = 0; + + // returns true if user is uploading a live broadcast + virtual bool IsBroadcasting( int *pnNumViewers ) = 0; +}; + +enum { k_iClientVideoCallbacks = 4600 }; + +#define k_iClientVideo_BroadcastUploadStart (k_iClientVideoCallbacks + 4) + +class SteamVideoFake : public ISteamVideo { +public: + SteamVideoFake (ISteamVideo* real) : real_ (real) { }; + + // Get a URL suitable for streaming the given Video app ID's video + virtual void GetVideoURL( AppId_t unVideoAppID ) { + return real_->GetVideoURL (unVideoAppID); + } + + // returns true if user is uploading a live broadcast + virtual bool IsBroadcasting( int *pnNumViewers ) { + *pnNumViewers = 0; + return false; + } + +protected: +private: + ISteamVideo* real_; +}; + +SteamVideoFake* faker = nullptr; + +#define S_CALLTYPE __cdecl + +typedef ISteamVideo* (S_CALLTYPE *SteamVideo_t)(void); + +LPVOID SteamVideo = nullptr; +SteamVideo_t SteamVideo_Original = nullptr; + +ISteamVideo* +S_CALLTYPE +SteamVideo_Detour (void) +{ + ISteamVideo* pVideo = SteamVideo_Original (); + + int x; + if (pVideo != nullptr && pVideo->IsBroadcasting (&x) == true) { + if (faker != nullptr) { + delete faker; + } + + faker = new SteamVideoFake (pVideo); + + return faker; + } + + return pVideo; +} + + +void +tbf::SteamFix::Init (void) +{ + CommandProcessor* comm_proc = CommandProcessor::getInstance (); + + if (! config.steam.allow_broadcasts) + return; + + steam_dll = LoadLibrary (L"steam_api.dll"); + + TBF_CreateDLLHook ( L"steam_api.dll", "SteamVideo", + SteamVideo_Detour, + (LPVOID *)&SteamVideo_Original, + (LPVOID *)&SteamVideo ); + + TBF_EnableHook (SteamVideo); +} + +void +tbf::SteamFix::Shutdown (void) +{ + if (! config.steam.allow_broadcasts) + return; + + //TBF_RemoveHook (SteamVideo); + + FreeLibrary (steam_dll); +} + +void +tbf::SteamFix::SetOverlayState (bool active) +{ + // Avoid duplicating a SK feature + static HMODULE hD3D9 = GetModuleHandle (config.system.injector.c_str ()); + + typedef void (__stdcall *SK_SteamAPI_SetOverlayState_pfn)(bool); + static SK_SteamAPI_SetOverlayState_pfn SK_SteamAPI_SetOverlayState = + (SK_SteamAPI_SetOverlayState_pfn)GetProcAddress ( hD3D9, + "SK_SteamAPI_SetOverlayState" ); + + SK_SteamAPI_SetOverlayState (active); +} + + + + +tbf::SteamFix::CommandProcessor::CommandProcessor (void) +{ + SK_ICommandProcessor& command = + *SK_GetCommandProcessor (); + + allow_broadcasts_ = TBF_CreateVar (SK_IVariable::Boolean, &config.steam.allow_broadcasts, this); + + command.AddVariable ("AllowBroadcasts", allow_broadcasts_); +} + +bool +tbf::SteamFix::CommandProcessor::OnVarChange (SK_IVariable* var, void* val) +{ + if (var == allow_broadcasts_) { + if (*(bool *)val == true) { + config.steam.allow_broadcasts = true; + SteamFix::Init (); + } + + if (*(bool *)val == false) { + SteamFix::Shutdown (); + config.steam.allow_broadcasts = false; + } + } + return true; +} + + +tbf::SteamFix::CommandProcessor* tbf::SteamFix::CommandProcessor::pCommProc; \ No newline at end of file diff --git a/src/textures.cpp b/src/textures.cpp new file mode 100644 index 0000000..69cb19f --- /dev/null +++ b/src/textures.cpp @@ -0,0 +1,3252 @@ +/** + * This file is part of Tales of Berseria "Fix". + * + * Tales of Berseria "Fix" is free software : you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * as published by The Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * Tales of Berseria "Fix" is distributed in the hope that it will be + * useful, + * + * But WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tales of Berseria "Fix". + * + * If not, see . + * +**/ + +#define _CRT_SECURE_NO_WARNINGS +#define NOMINMAX + +#include + +#include "textures.h" +#include "config.h" +#include "framerate.h" +#include "hook.h" +#include "log.h" +#include + +#include +#include + +#include "command.h" + +#define TBFIX_TEXTURE_DIR L"TBFix_Res" +#define TBFIX_TEXTURE_EXT L".dds" + + +typedef HRESULT (STDMETHODCALLTYPE *StretchRect_pfn) + ( IDirect3DDevice9 *This, + IDirect3DSurface9 *pSourceSurface, + const RECT *pSourceRect, + IDirect3DSurface9 *pDestSurface, + const RECT *pDestRect, + D3DTEXTUREFILTERTYPE Filter + ); + +typedef HRESULT (STDMETHODCALLTYPE *SetRenderState_pfn) +( + IDirect3DDevice9* This, + D3DRENDERSTATETYPE State, + DWORD Value +); + + +static D3DXSaveTextureToFile_pfn D3DXSaveTextureToFile = nullptr; +static D3DXCreateTextureFromFileInMemoryEx_pfn D3DXCreateTextureFromFileInMemoryEx_Original = nullptr; + +static BeginScene_pfn D3D9BeginScene_Original = nullptr; +static EndScene_pfn D3D9EndScene_Original = nullptr; + SetRenderState_pfn D3D9SetRenderState_Original = nullptr; + +static StretchRect_pfn D3D9StretchRect_Original = nullptr; +static CreateTexture_pfn D3D9CreateTexture_Original = nullptr; +static CreateRenderTarget_pfn D3D9CreateRenderTarget_Original = nullptr; +static CreateDepthStencilSurface_pfn D3D9CreateDepthStencilSurface_Original = nullptr; + +static SetTexture_pfn D3D9SetTexture_Original = nullptr; +static SetRenderTarget_pfn D3D9SetRenderTarget_Original = nullptr; +static SetDepthStencilSurface_pfn D3D9SetDepthStencilSurface_Original = nullptr; + +extern uint32_t +TBF_MakeShadowBitShift (uint32_t dim); + +typedef BOOL(WINAPI *QueryPerformanceCounter_t)(_Out_ LARGE_INTEGER *lpPerformanceCount); +extern QueryPerformanceCounter_t QueryPerformanceCounter_Original; + +tbf::RenderFix::TextureManager + tbf::RenderFix::tex_mgr; + +iSK_Logger* tex_log; + +#include + +// Textures that are missing mipmaps +std::set incomplete_textures; + +tbf::RenderFix::frame_texture_t tbf::RenderFix::cutscene_frame; +tbf::RenderFix::pad_buttons_t tbf::RenderFix::pad_buttons; + +// D3DXSaveSurfaceToFile issues a StretchRect, but we don't want to log that... +bool dumping = false; +bool __remap_textures = true; +bool __need_purge = false; +bool __log_used = false; +bool __show_cache = false; + +enum tbf_load_method_t { + Streaming, + Blocking, + DontCare +}; + +struct tbf_tex_record_s { + int archive = -1; + int fileno = 0UL; + enum tbf_load_method_t method = DontCare; + size_t size = 0UL; +}; + +bool pending_loads (void); +void TBFix_LoadQueuedTextures (void); + +#include +#include +#include +#include +#include + +// All of the enumerated textures in TBFix_Textures/inject/... +std::unordered_map injectable_textures; +std::vector archives; +std::set dumped_textures; + +// The set of textures used during the last frame +std::vector textures_last_frame; +std::set textures_used; + +std::wstring +SK_D3D9_UsageToStr (DWORD dwUsage) +{ + std::wstring usage; + + if (dwUsage & D3DUSAGE_RENDERTARGET) + usage += L"RenderTarget "; + + if (dwUsage & D3DUSAGE_DEPTHSTENCIL) + usage += L"Depth/Stencil "; + + if (dwUsage & D3DUSAGE_DYNAMIC) + usage += L"Dynamic"; + + if (usage.empty ()) + usage = L"Don't Care"; + + return usage; +} + +std::wstring +SK_D3D9_FormatToStr (D3DFORMAT Format, bool include_ordinal = true) +{ + switch (Format) + { + case D3DFMT_UNKNOWN: + return std::wstring (L"Unknown") + (include_ordinal ? L" (0)" : + L""); + + case D3DFMT_R8G8B8: + return std::wstring (L"R8G8B8") + + (include_ordinal ? L" (20)" : L""); + case D3DFMT_A8R8G8B8: + return std::wstring (L"A8R8G8B8") + + (include_ordinal ? L" (21)" : L""); + case D3DFMT_X8R8G8B8: + return std::wstring (L"X8R8G8B8") + + (include_ordinal ? L" (22)" : L""); + case D3DFMT_R5G6B5 : + return std::wstring (L"R5G6B5") + + (include_ordinal ? L" (23)" : L""); + case D3DFMT_X1R5G5B5 : + return std::wstring (L"X1R5G5B5") + + (include_ordinal ? L" (24)" : L""); + case D3DFMT_A1R5G5B5 : + return std::wstring (L"A1R5G5B5") + + (include_ordinal ? L" (25)" : L""); + case D3DFMT_A4R4G4B4 : + return std::wstring (L"A4R4G4B4") + + (include_ordinal ? L" (26)" : L""); + case D3DFMT_R3G3B2 : + return std::wstring (L"R3G3B2") + + (include_ordinal ? L" (27)" : L""); + case D3DFMT_A8 : + return std::wstring (L"A8") + + (include_ordinal ? L" (28)" : L""); + case D3DFMT_A8R3G3B2 : + return std::wstring (L"A8R3G3B2") + + (include_ordinal ? L" (29)" : L""); + case D3DFMT_X4R4G4B4 : + return std::wstring (L"X4R4G4B4") + + (include_ordinal ? L" (30)" : L""); + case D3DFMT_A2B10G10R10 : + return std::wstring (L"A2B10G10R10") + + (include_ordinal ? L" (31)" : L""); + case D3DFMT_A8B8G8R8 : + return std::wstring (L"A8B8G8R8") + + (include_ordinal ? L" (32)" : L""); + case D3DFMT_X8B8G8R8 : + return std::wstring (L"X8B8G8R8") + + (include_ordinal ? L" (33)" : L""); + case D3DFMT_G16R16 : + return std::wstring (L"G16R16") + + (include_ordinal ? L" (34)" : L""); + case D3DFMT_A2R10G10B10 : + return std::wstring (L"A2R10G10B10") + + (include_ordinal ? L" (35)" : L""); + case D3DFMT_A16B16G16R16 : + return std::wstring (L"A16B16G16R16") + + (include_ordinal ? L" (36)" : L""); + + case D3DFMT_A8P8 : + return std::wstring (L"A8P8") + + (include_ordinal ? L" (40)" : L""); + case D3DFMT_P8 : + return std::wstring (L"P8") + + (include_ordinal ? L" (41)" : L""); + + case D3DFMT_L8 : + return std::wstring (L"L8") + + (include_ordinal ? L" (50)" : L""); + case D3DFMT_A8L8 : + return std::wstring (L"A8L8") + + (include_ordinal ? L" (51)" : L""); + case D3DFMT_A4L4 : + return std::wstring (L"A4L4") + + (include_ordinal ? L" (52)" : L""); + + case D3DFMT_V8U8 : + return std::wstring (L"V8U8") + + (include_ordinal ? L" (60)" : L""); + case D3DFMT_L6V5U5 : + return std::wstring (L"L6V5U5") + + (include_ordinal ? L" (61)" : L""); + case D3DFMT_X8L8V8U8 : + return std::wstring (L"X8L8V8U8") + + (include_ordinal ? L" (62)" : L""); + case D3DFMT_Q8W8V8U8 : + return std::wstring (L"Q8W8V8U8") + + (include_ordinal ? L" (63)" : L""); + case D3DFMT_V16U16 : + return std::wstring (L"V16U16") + + (include_ordinal ? L" (64)" : L""); + case D3DFMT_A2W10V10U10 : + return std::wstring (L"A2W10V10U10") + + (include_ordinal ? L" (67)" : L""); + + case D3DFMT_UYVY : + return std::wstring (L"FourCC 'UYVY'"); + case D3DFMT_R8G8_B8G8 : + return std::wstring (L"FourCC 'RGBG'"); + case D3DFMT_YUY2 : + return std::wstring (L"FourCC 'YUY2'"); + case D3DFMT_G8R8_G8B8 : + return std::wstring (L"FourCC 'GRGB'"); + case D3DFMT_DXT1 : + return std::wstring (L"DXT1"); + case D3DFMT_DXT2 : + return std::wstring (L"DXT2"); + case D3DFMT_DXT3 : + return std::wstring (L"DXT3"); + case D3DFMT_DXT4 : + return std::wstring (L"DXT4"); + case D3DFMT_DXT5 : + return std::wstring (L"DXT5"); + + case D3DFMT_D16_LOCKABLE : + return std::wstring (L"D16_LOCKABLE") + + (include_ordinal ? L" (70)" : L""); + case D3DFMT_D32 : + return std::wstring (L"D32") + + (include_ordinal ? L" (71)" : L""); + case D3DFMT_D15S1 : + return std::wstring (L"D15S1") + + (include_ordinal ? L" (73)" : L""); + case D3DFMT_D24S8 : + return std::wstring (L"D24S8") + + (include_ordinal ? L" (75)" : L""); + case D3DFMT_D24X8 : + return std::wstring (L"D24X8") + + (include_ordinal ? L" (77)" : L""); + case D3DFMT_D24X4S4 : + return std::wstring (L"D24X4S4") + + (include_ordinal ? L" (79)" : L""); + case D3DFMT_D16 : + return std::wstring (L"D16") + + (include_ordinal ? L" (80)" : L""); + + case D3DFMT_D32F_LOCKABLE : + return std::wstring (L"D32F_LOCKABLE") + + (include_ordinal ? L" (82)" : L""); + case D3DFMT_D24FS8 : + return std::wstring (L"D24FS8") + + (include_ordinal ? L" (83)" : L""); + +/* D3D9Ex only -- */ +#if !defined(D3D_DISABLE_9EX) + + /* Z-Stencil formats valid for CPU access */ + case D3DFMT_D32_LOCKABLE : + return std::wstring (L"D32_LOCKABLE") + + (include_ordinal ? L" (84)" : L""); + case D3DFMT_S8_LOCKABLE : + return std::wstring (L"S8_LOCKABLE") + + (include_ordinal ? L" (85)" : L""); + +#endif // !D3D_DISABLE_9EX + + + + case D3DFMT_L16 : + return std::wstring (L"L16") + + (include_ordinal ? L" (81)" : L""); + + case D3DFMT_VERTEXDATA : + return std::wstring (L"VERTEXDATA") + + (include_ordinal ? L" (100)" : L""); + case D3DFMT_INDEX16 : + return std::wstring (L"INDEX16") + + (include_ordinal ? L" (101)" : L""); + case D3DFMT_INDEX32 : + return std::wstring (L"INDEX32") + + (include_ordinal ? L" (102)" : L""); + + case D3DFMT_Q16W16V16U16 : + return std::wstring (L"Q16W16V16U16") + + (include_ordinal ? L" (110)" : L""); + + case D3DFMT_MULTI2_ARGB8 : + return std::wstring (L"FourCC 'MET1'"); + + // Floating point surface formats + + // s10e5 formats (16-bits per channel) + case D3DFMT_R16F : + return std::wstring (L"R16F") + + (include_ordinal ? L" (111)" : L""); + case D3DFMT_G16R16F : + return std::wstring (L"G16R16F") + + (include_ordinal ? L" (112)" : L""); + case D3DFMT_A16B16G16R16F : + return std::wstring (L"A16B16G16R16F") + + (include_ordinal ? L" (113)" : L""); + + // IEEE s23e8 formats (32-bits per channel) + case D3DFMT_R32F : + return std::wstring (L"R32F") + + (include_ordinal ? L" (114)" : L""); + case D3DFMT_G32R32F : + return std::wstring (L"G32R32F") + + (include_ordinal ? L" (115)" : L""); + case D3DFMT_A32B32G32R32F : + return std::wstring (L"A32B32G32R32F") + + (include_ordinal ? L" (116)" : L""); + + case D3DFMT_CxV8U8 : + return std::wstring (L"CxV8U8") + + (include_ordinal ? L" (117)" : L""); + +/* D3D9Ex only -- */ +#if !defined(D3D_DISABLE_9EX) + + // Monochrome 1 bit per pixel format + case D3DFMT_A1 : + return std::wstring (L"A1") + + (include_ordinal ? L" (118)" : L""); + + // 2.8 biased fixed point + case D3DFMT_A2B10G10R10_XR_BIAS : + return std::wstring (L"A2B10G10R10_XR_BIAS") + + (include_ordinal ? L" (119)" : L""); + + + // Binary format indicating that the data has no inherent type + case D3DFMT_BINARYBUFFER : + return std::wstring (L"BINARYBUFFER") + + (include_ordinal ? L" (199)" : L""); + +#endif // !D3D_DISABLE_9EX +/* -- D3D9Ex only */ + } + + return std::wstring (L"UNKNOWN?!"); +} + +const wchar_t* +SK_D3D9_PoolToStr (D3DPOOL pool) +{ + switch (pool) + { + case D3DPOOL_DEFAULT: + return L" Default (0)"; + case D3DPOOL_MANAGED: + return L" Managed (1)"; + case D3DPOOL_SYSTEMMEM: + return L"System Memory (2)"; + case D3DPOOL_SCRATCH: + return L" Scratch (3)"; + default: + return L" UNKNOWN?! "; + } +} + +#if 0 +COM_DECLSPEC_NOTHROW +__declspec (noinline) +HRESULT +STDMETHODCALLTYPE +D3D9StretchRect_Detour ( IDirect3DDevice9 *This, + IDirect3DSurface9 *pSourceSurface, + const RECT *pSourceRect, + IDirect3DSurface9 *pDestSurface, + const RECT *pDestRect, + D3DTEXTUREFILTERTYPE Filter ) +{ +#if 0 + if (tbf::RenderFix::tracer.log && (! dumping)) + { + RECT source, dest; + + if (pSourceRect == nullptr) { + D3DSURFACE_DESC desc; + pSourceSurface->GetDesc (&desc); + source.left = 0; + source.top = 0; + source.bottom = desc.Height; + source.right = desc.Width; + } else + source = *pSourceRect; + + if (pDestRect == nullptr) { + D3DSURFACE_DESC desc; + pDestSurface->GetDesc (&desc); + dest.left = 0; + dest.top = 0; + dest.bottom = desc.Height; + dest.right = desc.Width; + } else + dest = *pDestRect; + + dll_log->Log ( L"[FrameTrace] StretchRect - " + L"%s[%lu,%lu/%lu,%lu] ==> %s[%lu,%lu/%lu,%lu]", + pSourceRect != nullptr ? + L" " : L" *", + source.left, source.top, source.right, source.bottom, + pDestRect != nullptr ? + L" " : L" *", + dest.left, dest.top, dest.right, dest.bottom ); + } +#endif + + dumping = false; + + return D3D9StretchRect_Original (This, pSourceSurface, pSourceRect, + pDestSurface, pDestRect, + Filter); +} +#endif + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetRenderState_Detour (IDirect3DDevice9* This, + D3DRENDERSTATETYPE State, + DWORD Value) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + dll_log->Log (L"[Render Fix] >> WARNING: SetRenderState came from unknown IDirect3DDevice9! << "); + + return D3D9SetRenderState_Original (This, State, Value); + } + +#if 0 + if (tbf::RenderFix::tracer.log) { + dll_log->Log ( L"[FrameTrace] SetRenderState - State: %24s, Value: %lu", + SK_D3D9_RenderStateToStr (State), + Value ); + } +#endif + + return D3D9SetRenderState_Original (This, State, Value); +} + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9CreateRenderTarget_Detour (IDirect3DDevice9 *This, + UINT Width, + UINT Height, + D3DFORMAT Format, + D3DMULTISAMPLE_TYPE MultiSample, + DWORD MultisampleQuality, + BOOL Lockable, + IDirect3DSurface9 **ppSurface, + HANDLE *pSharedHandle) +{ + tex_log->Log (L"[Unexpected][!] IDirect3DDevice9::CreateRenderTarget (%lu, %lu, " + L"%lu, %lu, %lu, %lu, %08Xh, %08Xh)", + Width, Height, Format, MultiSample, MultisampleQuality, + Lockable, ppSurface, pSharedHandle); + + return D3D9CreateRenderTarget_Original (This, Width, Height, Format, + MultiSample, MultisampleQuality, + Lockable, ppSurface, pSharedHandle); +} + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9CreateDepthStencilSurface_Detour (IDirect3DDevice9 *This, + UINT Width, + UINT Height, + D3DFORMAT Format, + D3DMULTISAMPLE_TYPE MultiSample, + DWORD MultisampleQuality, + BOOL Discard, + IDirect3DSurface9 **ppSurface, + HANDLE *pSharedHandle) +{ + tex_log->Log (L"[Unexpected][!] IDirect3DDevice9::CreateDepthStencilSurface (%lu, %lu, " + L"%lu, %lu, %lu, %lu, %08Xh, %08Xh)", + Width, Height, Format, MultiSample, MultisampleQuality, + Discard, ppSurface, pSharedHandle); + + return D3D9CreateDepthStencilSurface_Original (This, Width, Height, Format, + MultiSample, MultisampleQuality, + Discard, ppSurface, pSharedHandle); +} + +int +tbf::RenderFix::TextureManager::numInjectedTextures (void) +{ + return injected_count; +} + +int64_t +tbf::RenderFix::TextureManager::cacheSizeInjected (void) +{ + return injected_size; +} + +int64_t +tbf::RenderFix::TextureManager::cacheSizeBasic (void) +{ + return basic_size; +} + +int64_t +tbf::RenderFix::TextureManager::cacheSizeTotal (void) +{ + return cacheSizeBasic () + cacheSizeInjected (); +} + +#include "render.h" + +COM_DECLSPEC_NOTHROW +__declspec (noinline) +HRESULT +STDMETHODCALLTYPE +D3D9StretchRect_Detour ( IDirect3DDevice9 *This, + IDirect3DSurface9 *pSourceSurface, + const RECT *pSourceRect, + IDirect3DSurface9 *pDestSurface, + const RECT *pDestRect, + D3DTEXTUREFILTERTYPE Filter ) +{ + dumping = false; + + return D3D9StretchRect_Original (This, pSourceSurface, pSourceRect, + pDestSurface, pDestRect, + Filter); +} + + +std::set tbf::RenderFix::active_samplers; +extern IDirect3DTexture9* pFontTex; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetDepthStencilSurface_Detour ( + _In_ IDirect3DDevice9 *This, + _In_ IDirect3DSurface9 *pNewZStencil +) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + return D3D9SetDepthStencilSurface_Original (This, pNewZStencil); + } + + return D3D9SetDepthStencilSurface_Original (This, pNewZStencil); +} + + +int debug_tex_id; +uint32_t current_tex; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetTexture_Detour ( + _In_ IDirect3DDevice9 *This, + _In_ DWORD Sampler, + _In_ IDirect3DBaseTexture9 *pTexture +) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + return D3D9SetTexture_Original (This, Sampler, pTexture); + } + + // + // Hacky way of detecting the fullscreen frame border + // + if (Sampler == 0) { + if (pTexture == tbf::RenderFix::cutscene_frame.tex_corner || + pTexture == tbf::RenderFix::cutscene_frame.tex_side) + tbf::RenderFix::cutscene_frame.in_use = true; + else + tbf::RenderFix::cutscene_frame.in_use = false; + } + + + //if (tbf::RenderFix::tracer.log) { + //dll_log->Log ( L"[FrameTrace] SetTexture - Sampler: %lu, pTexture: %ph", + //Sampler, pTexture ); + //} + + void* dontcare; + if ( pTexture != nullptr && + pTexture->QueryInterface (IID_SKTextureD3D9, &dontcare) == S_OK ) { + ISKTextureD3D9* pSKTex = + (ISKTextureD3D9 *)pTexture; + + current_tex = pSKTex->tex_crc32; + + textures_used.insert (pSKTex->tex_crc32); + + QueryPerformanceCounter_Original (&pSKTex->last_used); + + // + // This is how blocking is implemented -- only do it when a texture that needs + // this feature is being applied. + // + while ( __remap_textures && pSKTex->must_block && + pSKTex->pTexOverride == nullptr ) { + if (pending_loads ()) + TBFix_LoadQueuedTextures (); + else { + YieldProcessor (); + } + } + + if (__remap_textures && pSKTex->pTexOverride != nullptr) + pTexture = pSKTex->pTexOverride; + else + pTexture = pSKTex->pTex; + + if (pSKTex->tex_crc32 == (uint32_t)debug_tex_id) + pTexture = nullptr; + } + +#if 0 + if (pTexture != nullptr) tsf::RenderFix::active_samplers.insert (Sampler); + else tsf::RenderFix::active_samplers.erase (Sampler); +#endif + + return D3D9SetTexture_Original (This, Sampler, pTexture); +} + +D3DXSaveSurfaceToFile_pfn D3DXSaveSurfaceToFileW = nullptr; + +IDirect3DSurface9* pOld = nullptr; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9CreateTexture_Detour (IDirect3DDevice9 *This, + UINT Width, + UINT Height, + UINT Levels, + DWORD Usage, + D3DFORMAT Format, + D3DPOOL Pool, + IDirect3DTexture9 **ppTexture, + HANDLE *pSharedHandle) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + return D3D9CreateTexture_Original ( This, Width, Height, + Levels, Usage, Format, + Pool, ppTexture, pSharedHandle ); + } + +#if 0 + if (Usage == D3DUSAGE_RENDERTARGET) + dll_log->Log (L" [!] IDirect3DDevice9::CreateTexture (%lu, %lu, %lu, %lu, " + L"%lu, %lu, %08Xh, %08Xh)", + Width, Height, Levels, Usage, Format, Pool, ppTexture, + pSharedHandle); +#endif + +#if 0 + //if (config.textures.log) { + tex_log->Log ( L"[Load Trace] >> Creating Texture: " + L"(%d x %d), Format: %s, Usage: [%s], Pool: %s", + Width, Height, + SK_D3D9_FormatToStr (Format), + SK_D3D9_UsageToStr (Usage).c_str (), + SK_D3D9_PoolToStr (Pool) ); + //} +//#endif +#endif + + // + // Model Shadows + // + if (Width == Height && (Width == 64 || Width == 128) && + Usage == D3DUSAGE_RENDERTARGET) { + // Assert (Levels == 1) + // + // If Levels is not 1, then we've kind of screwed up because now we don't + // have a complete mipchain anymore. + + uint32_t shift = TBF_MakeShadowBitShift (Width); + + Width <<= shift; + Height <<= shift; + } + + // + // Post-Processing (512x256) - FIXME damnit! + // + if (Width == 512 && + Height == 256 && Usage == D3DUSAGE_RENDERTARGET) { + if (config.render.postproc_ratio > 0.0f) { + Width = tbf::RenderFix::width * config.render.postproc_ratio; + Height = tbf::RenderFix::height * config.render.postproc_ratio; + } + } + + if (Usage == D3DUSAGE_DEPTHSTENCIL) { + if (Width == Height && (Height == 512 || Height == 1024 || Height == 2048)) { + uint32_t shift = config.render.env_shadow_rescale; + + Width <<= shift; + Height <<= shift; + } + } + + int levels = Levels; + + HRESULT result = + D3D9CreateTexture_Original (This, Width, Height, levels, Usage, + Format, Pool, ppTexture, pSharedHandle); + + return result; +} + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9BeginScene_Detour (IDirect3DDevice9* This) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + dll_log->Log (L"[Render Fix] >> WARNING: D3D9 BeginScene came from unknown IDirect3DDevice9! << "); + + return D3D9BeginScene_Original (This); + } + + tbf::RenderFix::draw_state.draws = 0; + +#if 0 + if ( This != nullptr && + tsf::RenderFix::pFont == nullptr ) { + D3D9_VIRTUAL_OVERRIDE ( &This, 16, + L"IDirect3DDevice9::Reset", + D3D9Reset_Detour, + D3D9Reset_Original, + Reset_pfn ); + + D3DXCreateFontW ( This, + 22, 0, + FW_NORMAL, + 0, false, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, ANTIALIASED_QUALITY, + DEFAULT_PITCH | FF_DONTCARE, +sss L"Arial", + &tsf::RenderFix::pFont ); + } +#endif + + HRESULT result = D3D9BeginScene_Original (This); + + return result; +} + +#if 0 +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9EndScene_Detour (IDirect3DDevice9* This) +{ + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + return D3D9EndScene_Original (This); + } + return D3D9EndScene_Original (This); +} +#endif + + +static uint32_t crc32_tab[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + +extern +uint32_t +crc32 (uint32_t crc, const void *buf, size_t size); + +typedef HRESULT (WINAPI *D3DXGetImageInfoFromFileInMemory_pfn) +( + _In_ LPCVOID pSrcData, + _In_ UINT SrcDataSize, + _In_ D3DXIMAGE_INFO *pSrcInfo +); + +D3DXGetImageInfoFromFileInMemory_pfn + D3DXGetImageInfoFromFileInMemory = nullptr; + +typedef HRESULT (WINAPI *D3DXGetImageInfoFromFile_pfn) +( + _In_ LPCWSTR pSrcFile, + _In_ D3DXIMAGE_INFO *pSrcInfo +); + +D3DXGetImageInfoFromFile_pfn + D3DXGetImageInfoFromFile = nullptr; + +typedef HRESULT (WINAPI *D3DXCreateTextureFromFileEx_pfn) +( + _In_ LPDIRECT3DDEVICE9 pDevice, + _In_ LPCWSTR pSrcFile, + _In_ UINT Width, + _In_ UINT Height, + _In_ UINT MipLevels, + _In_ DWORD Usage, + _In_ D3DFORMAT Format, + _In_ D3DPOOL Pool, + _In_ DWORD Filter, + _In_ DWORD MipFilter, + _In_ D3DCOLOR ColorKey, + _Inout_ D3DXIMAGE_INFO *pSrcInfo, + _Out_ PALETTEENTRY *pPalette, + _Out_ LPDIRECT3DTEXTURE9 *ppTexture +); + +D3DXCreateTextureFromFileEx_pfn + D3DXCreateTextureFromFileEx = nullptr; + +typedef HRESULT (WINAPI *D3DXCreateTextureFromFile_pfn) +( + _In_ LPDIRECT3DDEVICE9 pDevice, + _In_ LPCWSTR pSrcFile, + _Out_ LPDIRECT3DTEXTURE9 *ppTexture +); + +static D3DXCreateTextureFromFile_pfn + D3DXCreateTextureFromFile = nullptr; + +#define FONT_CRC32 0xef2d9b55 + +#define D3DX_FILTER_NONE 0x00000001 +#define D3DX_FILTER_POINT 0x00000002 +#define D3DX_FILTER_LINEAR 0x00000003 +#define D3DX_FILTER_TRIANGLE 0x00000004 +#define D3DX_FILTER_BOX 0x00000005 +#define D3DX_FILTER_MIRROR_U 0x00010000 +#define D3DX_FILTER_MIRROR_V 0x00020000 +#define D3DX_FILTER_MIRROR_W 0x00040000 +#define D3DX_FILTER_MIRROR 0x00070000 +#define D3DX_FILTER_DITHER 0x00080000 +#define D3DX_FILTER_DITHER_DIFFUSION 0x00100000 +#define D3DX_FILTER_SRGB_IN 0x00200000 +#define D3DX_FILTER_SRGB_OUT 0x00400000 +#define D3DX_FILTER_SRGB 0x00600000 + + +#define D3DX_SKIP_DDS_MIP_LEVELS_MASK 0x1f +#define D3DX_SKIP_DDS_MIP_LEVELS_SHIFT 26 +#define D3DX_SKIP_DDS_MIP_LEVELS(l, f) ((((l) & D3DX_SKIP_DDS_MIP_LEVELS_MASK) \ +<< D3DX_SKIP_DDS_MIP_LEVELS_SHIFT) | ((f) == D3DX_DEFAULT ? D3DX_FILTER_BOX : (f))) + +typedef BOOL(WINAPI *QueryPerformanceCounter_t)(_Out_ LARGE_INTEGER *lpPerformanceCount); +extern QueryPerformanceCounter_t QueryPerformanceCounter_Original; + + +#define __PTR_SIZE sizeof LPCVOID +#define __PAGE_PRIVS PAGE_EXECUTE_READWRITE + +#define D3D9_VIRTUAL_OVERRIDE(_Base,_Index,_Name,_Override,_Original,_Type) { \ + void** vftable = *(void***)*_Base; \ + \ + if (vftable [_Index] != _Override) { \ + DWORD dwProtect; \ + \ + VirtualProtect (&vftable [_Index], __PTR_SIZE, __PAGE_PRIVS, &dwProtect); \ + \ + /*dll_log->Log (L" Old VFTable entry for %s: %08Xh (Memory Policy: %s)",*/\ + /*L##_Name, vftable [_Index], */\ + /*SK_DescribeVirtualProtectFlags (dwProtect)); */\ + \ + if (_Original == NULL) \ + _Original = (##_Type)vftable [_Index]; \ + \ + /*dll_log->Log (L" + %s: %08Xh", L#_Original, _Original);*/ \ + \ + vftable [_Index] = _Override; \ + \ + VirtualProtect (&vftable [_Index], __PTR_SIZE, dwProtect, &dwProtect); \ + \ + /*dll_log->Log (L" New VFTable entry for %s: %08Xh (Memory Policy: %s)\n",*/\ + /*L##_Name, vftable [_Index], */\ + /*SK_DescribeVirtualProtectFlags (dwProtect)); */\ + } \ +} + +struct tbf_tex_load_s { + enum { + Stream, // This load will be streamed + Immediate, // This load must finish immediately (pSrc is unused) + Resample // Change image properties (pData is supplied) + } type; + + LPDIRECT3DDEVICE9 pDevice; + + // Resample only + LPVOID pSrcData; + UINT SrcDataSize; + + uint32_t checksum; + uint32_t size; + + // Stream / Immediate + wchar_t wszFilename [MAX_PATH]; + + LPDIRECT3DTEXTURE9 pDest = nullptr; + LPDIRECT3DTEXTURE9 pSrc = nullptr; + + LARGE_INTEGER start = { 0LL }; + LARGE_INTEGER end = { 0LL }; + LARGE_INTEGER freq = { 0LL }; +}; + +class TexLoadRef { +public: + TexLoadRef (tbf_tex_load_s* ref) { ref_ = ref;} + ~TexLoadRef (void) { } + + operator tbf_tex_load_s* (void) { + return ref_; + } + +protected: + tbf_tex_load_s* ref_; +}; + +class SK_TextureThreadPool; + +class SK_TextureWorkerThread { +friend class SK_TextureThreadPool; +public: + SK_TextureWorkerThread (SK_TextureThreadPool* pool) + { + pool_ = pool; + job_ = nullptr; + + control_.start = + CreateEvent (nullptr, FALSE, FALSE, nullptr); + control_.trim = + CreateEvent (nullptr, FALSE, FALSE, nullptr); + control_.shutdown = + CreateEvent (nullptr, FALSE, FALSE, nullptr); + + thread_ = + (HANDLE)_beginthreadex ( nullptr, + 0, + ThreadProc, + this, + 0, + &thread_id_ ); + } + + ~SK_TextureWorkerThread (void) + { + shutdown (); + + WaitForSingleObject (thread_, INFINITE); + + CloseHandle (control_.shutdown); + CloseHandle (control_.trim); + CloseHandle (control_.start); + + CloseHandle (thread_); + } + + void startJob (tbf_tex_load_s* job) { + job_ = job; + SetEvent (control_.start); + } + + void trim (void) { + SetEvent (control_.trim); + } + + void finishJob (void); + + bool isBusy (void) { + return (job_ != nullptr); + } + + void shutdown (void) { + SetEvent (control_.shutdown); + } + +protected: + static CRITICAL_SECTION cs_worker_init; + static ULONG num_threads_init; + + static unsigned int __stdcall ThreadProc (LPVOID user); + + SK_TextureThreadPool* pool_; + + unsigned int thread_id_; + HANDLE thread_; + + tbf_tex_load_s* job_; + + struct { + union { + struct { + HANDLE start; + HANDLE trim; + HANDLE shutdown; + }; + HANDLE ops [3]; + }; + } control_; +}; + +class SK_TextureThreadPool { +friend class SK_TextureWorkerThread; +public: + SK_TextureThreadPool (void) { + events_.jobs_added = + CreateEvent (nullptr, FALSE, FALSE, nullptr); + + events_.results_waiting = + CreateEvent (nullptr, FALSE, FALSE, nullptr); + + events_.shutdown = + CreateEvent (nullptr, FALSE, FALSE, nullptr); + + InitializeCriticalSectionAndSpinCount (&cs_jobs, 10UL); + InitializeCriticalSectionAndSpinCount (&cs_results, 1000UL); + + const int MAX_THREADS = config.textures.worker_threads; + + static bool init_worker_sync = false; + if (! init_worker_sync) { + // We will add a sync. barrier that waits for all of the threads in this pool, plus all of the threads + // in the other pool to initialize. This design is flawed, but safe. + InitializeCriticalSectionAndSpinCount (&SK_TextureWorkerThread::cs_worker_init, 10000UL); + init_worker_sync = true; + } + + for (int i = 0; i < MAX_THREADS; i++) { + SK_TextureWorkerThread* pWorker = + new SK_TextureWorkerThread (this); + + workers_.push_back (pWorker); + } + + // This will be deferred until it is first needed... + spool_thread_ = nullptr; + } + + ~SK_TextureThreadPool (void) { + if (spool_thread_ != nullptr) { + shutdown (); + + WaitForSingleObject (spool_thread_, INFINITE); + CloseHandle (spool_thread_); + } + + DeleteCriticalSection (&cs_results); + DeleteCriticalSection (&cs_jobs); + + CloseHandle (events_.results_waiting); + CloseHandle (events_.jobs_added); + CloseHandle (events_.shutdown); + } + + void postJob (tbf_tex_load_s* job) + { + EnterCriticalSection (&cs_jobs); + { + // Defer the creation of this until the first job is posted + if (! spool_thread_) { + spool_thread_ = + (HANDLE)_beginthreadex ( nullptr, + 0, + Spooler, + this, + 0x00, + nullptr ); + } + + // Don't let the game free this while we are working on it... + job->pDest->AddRef (); + + jobs_.push (job); + SetEvent (events_.jobs_added); + } + LeaveCriticalSection (&cs_jobs); + } + + std::vector getFinished (void) + { + std::vector results; + + DWORD dwResults = + WaitForSingleObject (events_.results_waiting, 0); + + // Nothing waiting + if (dwResults != WAIT_OBJECT_0) + return results; + + EnterCriticalSection (&cs_results); + { + while (! results_.empty ()) { + results.push_back (results_.front ()); + results_.pop (); + } + } + LeaveCriticalSection (&cs_results); + + return results; + } + + bool working (void) { + return (! results_.empty ()); + } + + int queueLength (void) { + int num = 0; + + EnterCriticalSection (&cs_jobs); + { + num = jobs_.size (); + } + LeaveCriticalSection (&cs_jobs); + + return num; + } + + void shutdown (void) { + SetEvent (events_.shutdown); + } + + +protected: + static unsigned int __stdcall Spooler (LPVOID user); + + tbf_tex_load_s* getNextJob (void) { + tbf_tex_load_s* job = nullptr; + DWORD dwResults = 0; + + //while (dwResults != WAIT_OBJECT_0) { + //dwResults = WaitForSingleObject (events_.jobs_added, INFINITE); + //} + + if (jobs_.empty ()) + return nullptr; + + EnterCriticalSection (&cs_jobs); + { + job = jobs_.front (); + jobs_.pop (); + } + LeaveCriticalSection (&cs_jobs); + + return job; + } + + void postFinished (tbf_tex_load_s* finished) + { + EnterCriticalSection (&cs_results); + { + // Remove the temporary reference we added earlier + finished->pDest->Release (); + + results_.push (finished); + SetEvent (events_.results_waiting); + } + LeaveCriticalSection (&cs_results); + } + +private: + std::queue jobs_; + std::queue results_; + + std::vector workers_; + + struct { + HANDLE jobs_added; + HANDLE results_waiting; + HANDLE shutdown; + } events_; + + CRITICAL_SECTION cs_jobs; + CRITICAL_SECTION cs_results; + + HANDLE spool_thread_; +} *resample_pool = nullptr; + +std::queue textures_to_stream; + +std::map + textures_in_flight; + +std::queue finished_loads; + +CRITICAL_SECTION cs_tex_stream; +CRITICAL_SECTION cs_tex_resample; +CRITICAL_SECTION cs_tex_inject; + +#define D3DX_DEFAULT ((UINT) -1) +#define D3DX_DEFAULT_NONPOW2 ((UINT) -2) +#define D3DX_DEFAULT_FLOAT FLT_MAX +#define D3DX_FROM_FILE ((UINT) -3) +#define D3DFMT_FROM_FILE ((D3DFORMAT) -3) + +std::set inject_tids; + + LONG streaming = 0UL; +ULONG streaming_bytes = 0L; + +LONG resampling = 0L; + +bool +pending_loads (void) +{ + bool ret = false; + + return resample_pool != nullptr && resample_pool->working (); + +// EnterCriticalSection (&cs_tex_inject); +// ret = (! finished_loads.empty ()); +// LeaveCriticalSection (&cs_tex_inject); + + return ret; +} + +void +start_load (void) +{ + EnterCriticalSection (&cs_tex_inject); + + inject_tids.insert (GetCurrentThreadId ()); + + LeaveCriticalSection (&cs_tex_inject); +} + +void +end_load (void) +{ + EnterCriticalSection (&cs_tex_inject); + + inject_tids.erase (GetCurrentThreadId ()); + + LeaveCriticalSection (&cs_tex_inject); +} + + +bool +pending_streams (void) +{ + bool ret = false; + + bool stream = false; + bool resample = false; + + + if (InterlockedIncrement (&streaming) > 1) + stream = true; + + InterlockedDecrement (&streaming); + + if (stream) + ret = true; + + + if (InterlockedIncrement (&resampling) > 1) + resample = true; + + InterlockedDecrement (&resampling); + + if (resample || resample_pool->queueLength ()) + return true; + + return ret; +} + +bool +is_streaming (uint32_t checksum) +{ + bool ret = false; + + EnterCriticalSection (&cs_tex_stream); + + if (textures_in_flight.count (checksum)) + ret = true; + + LeaveCriticalSection (&cs_tex_stream); + + return ret; +} + +void +finished_streaming (uint32_t checksum) +{ + EnterCriticalSection (&cs_tex_stream); + + if (textures_in_flight.count (checksum)) + textures_in_flight.erase (checksum); + + LeaveCriticalSection (&cs_tex_stream); +} + + +HANDLE decomp_semaphore; + +// Keep a pool of memory around so that we are not allocating and freeing +// memory constantly... +namespace streaming_memory { + std::unordered_map data; + std::unordered_map data_len; + std::unordered_map data_age; + + bool alloc (size_t len, DWORD dwThreadId = GetCurrentThreadId ()) + { + if (data_len [dwThreadId] < len) { + if (data [dwThreadId] != nullptr) + free (data [dwThreadId]); + + if (len < 8192 * 1024) + data_len [dwThreadId] = 8192 * 1024; + else + data_len [dwThreadId] = len; + + data [dwThreadId] = malloc (data_len [dwThreadId]); + data_age [dwThreadId] = timeGetTime (); + + if (data [dwThreadId] != nullptr) { + return true; + } else { + data_len [dwThreadId] = 0; + return false; + } + } else { + return true; + } + } + + void trim (size_t max_size, uint32_t min_age, DWORD dwThreadId = GetCurrentThreadId ()) { + if (data_age [dwThreadId] < min_age) { + if (data_len [dwThreadId] > max_size) { + free (data [dwThreadId]); + data [dwThreadId] = nullptr; + + if (max_size > 0) + data [dwThreadId] = malloc (max_size); + + if (data [dwThreadId] != nullptr) { + data_len [dwThreadId] = max_size; + data_age [dwThreadId] = timeGetTime (); + } else { + data_len [dwThreadId] = 0; + data_age [dwThreadId] = 0; + } + } + } + } +} + +HRESULT +InjectTexture (tbf_tex_load_s* load) +{ + D3DXIMAGE_INFO img_info = { 0 }; + + bool streamed; + size_t size = 0; + HRESULT hr = E_FAIL; + + streamed = + injectable_textures [load->checksum].method == Streaming; + + // + // Load: From Regular Filesystem + // + if (injectable_textures [load->checksum].archive == -1) { + HANDLE hTexFile = + CreateFile ( load->wszFilename, + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_SEQUENTIAL_SCAN, + nullptr ); + + DWORD read = 0UL; + + if (hTexFile != INVALID_HANDLE_VALUE) { + size = GetFileSize (hTexFile, nullptr); + load->pSrcData = new uint8_t [size]; + + if (load->pSrcData != nullptr) { + ReadFile (hTexFile, load->pSrcData, size, &read, nullptr); + + load->SrcDataSize = read; + + if (streamed && size > (16 * 1024)) { + SetThreadPriority ( GetCurrentThread (), + THREAD_PRIORITY_BELOW_NORMAL | + THREAD_MODE_BACKGROUND_BEGIN ); + } + + D3DXGetImageInfoFromFileInMemory ( + load->pSrcData, + load->SrcDataSize, + &img_info ); + + hr = D3DXCreateTextureFromFileInMemoryEx_Original ( + load->pDevice, + load->pSrcData, load->SrcDataSize, + D3DX_DEFAULT, D3DX_DEFAULT, img_info.MipLevels, + 0, D3DFMT_FROM_FILE, + D3DPOOL_DEFAULT, + D3DX_DEFAULT, D3DX_DEFAULT, + 0, + &img_info, nullptr, + &load->pSrc ); + + delete [] load->pSrcData; + load->pSrcData = nullptr; + } else { + // OUT OF MEMORY ?! + } + + CloseHandle (hTexFile); + } + } + + // + // Load: From (Compressed) Archive (.7z or .zip) + // + else { + } + + if (streamed && size > (16 * 1024)) { + SetThreadPriority ( GetCurrentThread (), + THREAD_MODE_BACKGROUND_END ); + } + + return hr; +} + +void +TBFix_LoadQueuedTextures (void) +{ + extern std::string mod_text; + mod_text = ""; + + static DWORD dwTime = timeGetTime (); + static unsigned char spin = 193; + + if (dwTime < timeGetTime ()-100UL) + spin++; + + if (spin > 199) + spin = 193; + + if (resampling) { + mod_text += spin; + + char szFormatted [64]; + sprintf (szFormatted, " Resampling: %li texture", resampling); + + mod_text += szFormatted; + + if (streaming > 1) + mod_text += 's'; + + int queue_len = resample_pool->queueLength (); + + if (queue_len) { + sprintf (szFormatted, " (%lu queued)", queue_len); + mod_text += szFormatted; + } + + mod_text += "\n\n"; + } + + if (streaming) { + mod_text += spin; + + char szFormatted [64]; + sprintf (szFormatted, " Streaming: %li texture", streaming); + + mod_text += szFormatted; + + if (streaming > 1) + mod_text += 's'; + + sprintf (szFormatted, " [%7.2f MiB]", (double)streaming_bytes / (1024.0f * 1024.0f)); + mod_text += szFormatted; + + if (textures_to_stream.size ()) { + sprintf (szFormatted, " (%lu outstanding)", textures_to_stream.size ()); + mod_text += szFormatted; + } + } + + int loads = 0; + + std::vector finished; + + if (resample_pool != nullptr) + finished = resample_pool->getFinished (); + + for (auto it = finished.begin (); it != finished.end (); it++) { + tbf_tex_load_s* load = + *it; + + QueryPerformanceCounter_Original (&load->end); + + if (true) { + tex_log->Log ( L"[%s] Finished %s texture %08x (%5.2f MiB in %9.4f ms)", + (load->type == tbf_tex_load_s::Stream) ? L"Inject Tex" : + (load->type == tbf_tex_load_s::Immediate) ? L"Inject Tex" : + L" Resample ", + (load->type == tbf_tex_load_s::Stream) ? L"streaming" : + (load->type == tbf_tex_load_s::Immediate) ? L"loading" : + L"filtering", + load->checksum, + (double)load->SrcDataSize / (1024.0f * 1024.0f), + 1000.0f * (double)(load->end.QuadPart - load->start.QuadPart) / + (double)load->freq.QuadPart ); + } + + tbf::RenderFix::Texture* pTex = + tbf::RenderFix::tex_mgr.getTexture (load->checksum); + + if (pTex != nullptr) { + pTex->load_time = 1000.0f * (double)(load->end.QuadPart - load->start.QuadPart) / + (double)load->freq.QuadPart; + } + + ISKTextureD3D9* pSKTex = + (ISKTextureD3D9 *)load->pDest; + + if (pSKTex->refs == 0 && load->pSrc != nullptr) { + tex_log->Log (L"[ Tex. Mgr ] >> Original texture no longer referenced, discarding new one!"); + load->pSrc->Release (); + } else { + QueryPerformanceCounter_Original (&pSKTex->last_used); + + pSKTex->pTexOverride = load->pSrc; + pSKTex->override_size = load->SrcDataSize; + + tbf::RenderFix::tex_mgr.addInjected (load->SrcDataSize); + } + + finished_streaming (load->checksum); + + tbf::RenderFix::tex_mgr.updateOSD (); + + ++loads; + + delete load; + } + + // + // If the size changes, check to see if we need a purge - if so, schedule one. + // + static uint64_t last_size = 0ULL; + + if (last_size != tbf::RenderFix::tex_mgr.cacheSizeTotal () ) { + last_size = tbf::RenderFix::tex_mgr.cacheSizeTotal (); + + if ( last_size > + (1024ULL * 1024ULL) * (uint64_t)config.textures.max_cache_in_mib ) + __need_purge = true; + } + + if ((! streaming) && (! resampling) && (! pending_loads ())) { + if (__need_purge) { + tbf::RenderFix::tex_mgr.purge (); + __need_purge = false; + } + } + + tbf::RenderFix::tex_mgr.osdStats (); +} + +#include +std::set resample_blacklist; +bool resample_blacklist_init = false; + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3DXCreateTextureFromFileInMemoryEx_Detour ( + _In_ LPDIRECT3DDEVICE9 pDevice, + _In_ LPCVOID pSrcData, + _In_ UINT SrcDataSize, + _In_ UINT Width, + _In_ UINT Height, + _In_ UINT MipLevels, + _In_ DWORD Usage, + _In_ D3DFORMAT Format, + _In_ D3DPOOL Pool, + _In_ DWORD Filter, + _In_ DWORD MipFilter, + _In_ D3DCOLOR ColorKey, + _Inout_ D3DXIMAGE_INFO *pSrcInfo, + _Out_ PALETTEENTRY *pPalette, + _Out_ LPDIRECT3DTEXTURE9 *ppTexture +) +{ + bool inject_thread = false; + + EnterCriticalSection (&cs_tex_inject); + + if (inject_tids.count (GetCurrentThreadId ())) { + inject_thread = true; + //resample = false; + } + + LeaveCriticalSection (&cs_tex_inject); + + // Injection would recurse slightly and cause impossible to diagnose reference counting problems + // with texture caching if we did not check for this! + if (inject_thread) { + return D3DXCreateTextureFromFileInMemoryEx_Original ( + pDevice, + pSrcData, SrcDataSize, + Width, Height, MipLevels, + Usage, + Format, + Pool, + Filter, MipFilter, ColorKey, + pSrcInfo, pPalette, + ppTexture ); + } + + if (resample_blacklist_init == false) { + // Do Not Resample Logos + resample_blacklist.insert (0xfa3d03df); + resample_blacklist.insert (0x545908bb); + + // Do Not Resample Face and Eyes + resample_blacklist.insert (0x0f924b8e); + resample_blacklist.insert (0xc11ee548); + resample_blacklist.insert (0xde2284ae); + resample_blacklist.insert (0xe6908541); + resample_blacklist.insert (0x09b7b43a); + + // ROSE: + resample_blacklist.insert (0x02c01e2a); + resample_blacklist.insert (0x1564504e); + resample_blacklist.insert (0x307466c4); + resample_blacklist.insert (0xea8c3c76); + + // MIKLEO: + resample_blacklist.insert (0x682835d2); + resample_blacklist.insert (0x9f184556); + resample_blacklist.insert (0xa6a8bd7e); + resample_blacklist.insert (0xc106abf3); + resample_blacklist.insert (0xe9b98d4e); + + // EDNA: + resample_blacklist.insert (0x47f4e5e3); + resample_blacklist.insert (0x53aa4ebd); + resample_blacklist.insert (0xacaa8f7b); + resample_blacklist.insert (0xb91c89e5); + + // ZAVEID: + resample_blacklist.insert (0x342c284f); + resample_blacklist.insert (0x637b52f8); + resample_blacklist.insert (0x8668d212); + resample_blacklist.insert (0x9511723e); + + resample_blacklist_init = true; + } + + // Performance statistics for caching system + LARGE_INTEGER start, end; + + static LARGE_INTEGER freq = { 0LL }; + + if (freq.QuadPart == 0LL) + QueryPerformanceFrequency (&freq); + + QueryPerformanceCounter_Original (&start); + + uint32_t checksum = + crc32 (0, pSrcData, SrcDataSize); + + // Don't dump or cache these + if (Usage == D3DUSAGE_DYNAMIC || Usage == D3DUSAGE_RENDERTARGET) + checksum = 0x00; + + if (config.textures.cache && checksum != 0x00) { + tbf::RenderFix::Texture* pTex = + tbf::RenderFix::tex_mgr.getTexture (checksum); + + if (pTex != nullptr) { + tbf::RenderFix::tex_mgr.refTexture (pTex); + + *ppTexture = pTex->d3d9_tex; + + return S_OK; + } + } + + bool resample = false; + + // Necessary to make D3DX texture write functions work + if ( Pool == D3DPOOL_DEFAULT && config.textures.dump && + (! dumped_textures.count (checksum)) && + (! injectable_textures.count (checksum)) ) + Usage = D3DUSAGE_DYNAMIC; + + // Generate complete mipmap chains for best image quality + // (will increase load-time on uncached textures) + if ((Pool == D3DPOOL_DEFAULT) && config.textures.remaster) { + if (Format == D3DFMT_DXT1 || + Format == D3DFMT_DXT3 || + Format == D3DFMT_DXT5) + if (Width >= 128 && Height >= 128) { + // Don't resample faces + if ( resample_blacklist.count (checksum) == 0 ) + resample = true; + } + } + + HRESULT hr = E_FAIL; + tbf_tex_load_s* load_op = nullptr; + + wchar_t wszInjectFileName [MAX_PATH] = { L'\0' }; + + bool remap_stream = is_streaming (checksum); + + // + // Generic injectable textures + // + if ( (! inject_thread) && + injectable_textures.find (checksum) != + injectable_textures.end () ) + { + tex_log->LogEx ( true, L"[Inject Tex] Injectable texture for checksum (%08x)... ", + checksum ); + + tbf_tex_record_s record = injectable_textures [checksum]; + + if (record.method == DontCare) + record.method = Streaming; + + // If -1, load from disk... + if (record.archive == -1) { + if (record.method == Streaming) + _swprintf ( wszInjectFileName, L"%s\\inject\\textures\\streaming\\%08x%s", + TBFIX_TEXTURE_DIR, + checksum, + TBFIX_TEXTURE_EXT ); + else if (record.method == Blocking) + _swprintf ( wszInjectFileName, L"%s\\inject\\textures\\blocking\\%08x%s", + TBFIX_TEXTURE_DIR, + checksum, + TBFIX_TEXTURE_EXT ); + } + + load_op = new tbf_tex_load_s; + load_op->pDevice = pDevice; + load_op->checksum = checksum; + + if (record.method == Streaming) + load_op->type = tbf_tex_load_s::Stream; + else + load_op->type = tbf_tex_load_s::Immediate; + + wcscpy (load_op->wszFilename, wszInjectFileName); + + if (load_op->type == tbf_tex_load_s::Stream) { + if ((! remap_stream)) + tex_log->LogEx ( false, L"streaming\n" ); + else + tex_log->LogEx ( false, L"in-flight already\n" ); + } else { + tex_log->LogEx ( false, L"blocking (deferred)\n" ); + } + } + + bool will_replace = (load_op != nullptr || resample); + + //tex_log->Log (L"D3DXCreateTextureFromFileInMemoryEx (... MipLevels=%lu ...)", MipLevels); + hr = + D3DXCreateTextureFromFileInMemoryEx_Original ( pDevice, + pSrcData, SrcDataSize, + Width, Height, will_replace ? 1 : MipLevels, + Usage, Format, Pool, + Filter, MipFilter, ColorKey, + pSrcInfo, pPalette, + ppTexture ); + + if (SUCCEEDED (hr)) { + new ISKTextureD3D9 (ppTexture, SrcDataSize, checksum); + + if (checksum == tbf::RenderFix::cutscene_frame.crc32_side) + tbf::RenderFix::cutscene_frame.tex_side = *ppTexture; + + if (checksum == tbf::RenderFix::cutscene_frame.crc32_corner) + tbf::RenderFix::cutscene_frame.tex_corner = *ppTexture; + + if (checksum == tbf::RenderFix::pad_buttons.crc32_ps3) { + tbf::RenderFix::pad_buttons.tex_ps3 = *ppTexture; + } + + if (checksum == tbf::RenderFix::pad_buttons.crc32_xbox) { + tbf::RenderFix::pad_buttons.tex_xbox = *ppTexture; + + if (GetFileAttributesW (L"custom_buttons.dds") != INVALID_FILE_ATTRIBUTES) { + tex_log->LogEx (true, L"[Inject Tex] Injecting custom gamepad buttons... "); + + load_op = new tbf_tex_load_s; + load_op->pDevice = pDevice; + load_op->checksum = checksum; + load_op->type = tbf_tex_load_s::Immediate; + + wcscpy (load_op->wszFilename, L"custom_buttons.dds"); + + if (load_op->type == tbf_tex_load_s::Stream) { + if ((! remap_stream)) + tex_log->LogEx ( false, L"streaming\n" ); + else + tex_log->LogEx ( false, L"in-flight already\n" ); + } else { + tex_log->LogEx ( false, L"blocking (deferred)\n" ); + } + + resample = false; + } + } + + + if ( load_op != nullptr && ( load_op->type == tbf_tex_load_s::Stream || + load_op->type == tbf_tex_load_s::Immediate ) ) { + load_op->SrcDataSize = + injectable_textures.count (checksum) == 0 ? + 0 : injectable_textures [checksum].size; + + load_op->pDest = *ppTexture; + EnterCriticalSection (&cs_tex_stream); + + if (load_op->type == tbf_tex_load_s::Immediate) + ((ISKTextureD3D9 *)*ppTexture)->must_block = true; + + if (is_streaming (load_op->checksum)) { + ISKTextureD3D9* pTexOrig = + (ISKTextureD3D9 *)textures_in_flight [load_op->checksum]->pDest; + + // Remap the output of the in-flight texture + textures_in_flight [load_op->checksum]->pDest = + *ppTexture; + + if (tbf::RenderFix::tex_mgr.getTexture (load_op->checksum) != nullptr) { + for ( int i = 0; + i < tbf::RenderFix::tex_mgr.getTexture (load_op->checksum)->refs; + ++i ) { + (*ppTexture)->AddRef (); + } + } + + ////tsf::RenderFix::tex_mgr.removeTexture (pTexOrig); + } + + else { + textures_in_flight.insert ( std::make_pair ( load_op->checksum, + load_op ) ); + + resample_pool->postJob (load_op); + } + + LeaveCriticalSection (&cs_tex_stream); + } + +#if 0 + // + // TODO: Actually stream these, but block if the game tries to call SetTexture (...) + // while the texture is in-flight. + // + else if (load_op != nullptr && load_op->type == tsf_tex_load_s::Immediate) { + QueryPerformanceFrequency (&load_op->freq); + QueryPerformanceCounter_Original (&load_op->start); + + EnterCriticalSection (&cs_tex_inject); + inject_tids.insert (GetCurrentThreadId ()); + LeaveCriticalSection (&cs_tex_inject); + + load_op->pDest = *ppTexture; + + hr = InjectTexture (load_op); + + EnterCriticalSection (&cs_tex_inject); + inject_tids.erase (GetCurrentThreadId ()); + LeaveCriticalSection (&cs_tex_inject); + + QueryPerformanceCounter_Original (&load_op->end); + + if (SUCCEEDED (hr)) { + tex_log->Log ( L"[Inject Tex] Finished synchronous texture %08x (%5.2f MiB in %9.4f ms)", + load_op->checksum, + (double)load_op->SrcDataSize / (1024.0f * 1024.0f), + 1000.0f * (double)(load_op->end.QuadPart - load_op->start.QuadPart) / + (double) load_op->freq.QuadPart ); + ISKTextureD3D9* pSKTex = + (ISKTextureD3D9 *)*ppTexture; + + pSKTex->pTexOverride = load_op->pSrc; + pSKTex->override_size = load_op->SrcDataSize; + + pSKTex->last_used = load_op->end; + + tsf::RenderFix::tex_mgr.addInjected (load_op->SrcDataSize); + } else { + tex_log->Log ( L"[Inject Tex] *** FAILED synchronous texture %08x", + load_op->checksum ); + } + + delete load_op; + load_op = nullptr; + } +#endif + +// Temporarily disabled while mipmap-related issues are debugged... + else if (resample) { + load_op = new tbf_tex_load_s; + + load_op->pDevice = pDevice; + load_op->checksum = checksum; + load_op->type = tbf_tex_load_s::Resample; + + load_op->pSrcData = new uint8_t [SrcDataSize]; + load_op->SrcDataSize = SrcDataSize; + + memcpy (load_op->pSrcData, pSrcData, SrcDataSize); + + load_op->pDest = *ppTexture; + + resample_pool->postJob (load_op); + } + } + + else if (load_op != nullptr) { + delete load_op; + load_op = nullptr; + } + + QueryPerformanceCounter_Original (&end); + + if (SUCCEEDED (hr)) { + if (config.textures.cache && checksum != 0x00) { + tbf::RenderFix::Texture* pTex = + new tbf::RenderFix::Texture (); + + pTex->crc32 = checksum; + + pTex->d3d9_tex = *(ISKTextureD3D9 **)ppTexture; + pTex->d3d9_tex->AddRef (); + pTex->refs++; + + pTex->load_time = 1000.0f * (double)(end.QuadPart - start.QuadPart) / (double)freq.QuadPart; + + tbf::RenderFix::tex_mgr.addTexture (checksum, pTex, SrcDataSize); + } + + if (false) {//config.textures.log) { + tex_log->Log ( L"[Load Trace] Texture: (%lu x %lu) * - FAST_CRC32: %X", + Width, Height, (*ppTexture)->GetLevelCount (), checksum ); + tex_log->Log ( L"[Load Trace] Usage: %-20s - Format: %-20s", + SK_D3D9_UsageToStr (Usage).c_str (), + SK_D3D9_FormatToStr (Format).c_str () ); + tex_log->Log ( L"[Load Trace] Pool: %s", + SK_D3D9_PoolToStr (Pool) ); + tex_log->Log ( L"[Load Trace] Load Time: %6.4f ms", + 1000.0f * (double)(end.QuadPart - start.QuadPart) / (double)freq.QuadPart ); + } + } + + if ( config.textures.dump && (! inject_thread) && (! injectable_textures.count (checksum)) && + (! dumped_textures.count (checksum)) ) { + D3DXIMAGE_INFO info = { 0 }; + D3DXGetImageInfoFromFileInMemory (pSrcData, SrcDataSize, &info); + + D3DFORMAT fmt_real = info.Format; + + bool compressed = (fmt_real >= D3DFMT_DXT1 && fmt_real <= D3DFMT_DXT5); + + wchar_t wszPath [MAX_PATH]; + _swprintf ( wszPath, L"%s\\dump\\textures\\%s", + TBFIX_TEXTURE_DIR, SK_D3D9_FormatToStr (fmt_real, false).c_str () ); + + if (GetFileAttributesW (wszPath) != FILE_ATTRIBUTE_DIRECTORY) + CreateDirectoryW (wszPath, nullptr); + + wchar_t wszFileName [MAX_PATH] = { L'\0' }; + _swprintf ( wszFileName, L"%s\\dump\\textures\\%s\\%08x%s", + TBFIX_TEXTURE_DIR, + SK_D3D9_FormatToStr (fmt_real, false).c_str (), + checksum, + TBFIX_TEXTURE_EXT ); + + D3DXSaveTextureToFile (wszFileName, D3DXIFF_DDS, (*ppTexture), NULL); + } + + return hr; +} + +std::vector remove_textures; + +tbf::RenderFix::Texture* +tbf::RenderFix::TextureManager::getTexture (uint32_t checksum) +{ + EnterCriticalSection (&cs_cache); + + auto rem = remove_textures.begin (); + + while (rem != remove_textures.end ()) { + if ((*rem)->pTexOverride != nullptr) { + InterlockedDecrement (&injected_count); + InterlockedAdd64 (&injected_size, -(*rem)->override_size); + } + + if ((*rem)->pTex) (*rem)->pTex->Release (); + if ((*rem)->pTexOverride) (*rem)->pTexOverride->Release (); + + (*rem)->pTex = nullptr; + (*rem)->pTexOverride = nullptr; + + InterlockedAdd64 (&basic_size, -(*rem)->tex_size); + { + textures.erase ((*rem)->tex_crc32); + } + + delete *rem; + + ++rem; + } + + remove_textures.clear (); + + auto tex = textures.find (checksum); + + LeaveCriticalSection (&cs_cache); + + if (tex != textures.end ()) + return tex->second; + + return nullptr; +} + +void +tbf::RenderFix::TextureManager::removeTexture (ISKTextureD3D9* pTexD3D9) +{ + EnterCriticalSection (&cs_cache); + + remove_textures.push_back (pTexD3D9); + + LeaveCriticalSection (&cs_cache); +} + +void +tbf::RenderFix::TextureManager::addTexture (uint32_t checksum, tbf::RenderFix::Texture* pTex, size_t size) +{ + pTex->size = size; + + InterlockedAdd64 (&basic_size, pTex->size); + + EnterCriticalSection (&cs_cache); + { + textures [checksum] = pTex; + } + LeaveCriticalSection (&cs_cache); + + updateOSD (); +} + +void +tbf::RenderFix::TextureManager::refTexture (tbf::RenderFix::Texture* pTex) +{ + pTex->d3d9_tex->AddRef (); + pTex->refs++; + + InterlockedIncrement (&hits); + + if (false) {//config.textures.log) { + tex_log->Log ( L"[CacheTrace] Cache hit (%X), saved %2.1f ms", + pTex->crc32, + pTex->load_time ); + } + + time_saved += pTex->load_time; + + updateOSD (); +} + +COM_DECLSPEC_NOTHROW +HRESULT +STDMETHODCALLTYPE +D3D9SetRenderTarget_Detour ( + _In_ IDirect3DDevice9 *This, + _In_ DWORD RenderTargetIndex, + _In_ IDirect3DSurface9 *pRenderTarget +) +{ + static int draw_counter = 0; + + // Ignore anything that's not the primary render device. + if (This != tbf::RenderFix::pDevice) { + return D3D9SetRenderTarget_Original (This, RenderTargetIndex, pRenderTarget); + } + + //if (tsf::RenderFix::tracer.log) { +#ifdef DUMP_RT + if (D3DXSaveSurfaceToFileW == nullptr) { + D3DXSaveSurfaceToFileW = + (D3DXSaveSurfaceToFile_pfn) + GetProcAddress ( tsf::RenderFix::d3dx9_43_dll, + "D3DXSaveSurfaceToFileW" ); + } + + wchar_t wszDumpName [MAX_PATH]; + + if (pRenderTarget != pOld) { + if (pOld != nullptr) { + wsprintf (wszDumpName, L"dump\\%03d_out_%p.png", draw_counter, pOld); + + dll_log->Log ( L"[FrameTrace] >>> Dumped: Output RT to %s >>>", wszDumpName ); + + dumping = true; + //D3DXSaveSurfaceToFile (wszDumpName, D3DXIFF_PNG, pOld, nullptr, nullptr); + } + } +#endif + + //dll_log->Log ( L"[FrameTrace] SetRenderTarget - RenderTargetIndex: %lu, pRenderTarget: %ph", + //RenderTargetIndex, pRenderTarget ); + +#ifdef DUMP_RT + if (pRenderTarget != pOld) { + pOld = pRenderTarget; + + wsprintf (wszDumpName, L"dump\\%03d_in_%p.png", ++draw_counter, pRenderTarget); + + dll_log->Log ( L"[FrameTrace] <<< Dumped: Input RT to %s <<<", wszDumpName ); + + dumping = true; + //D3DXSaveSurfaceToFile (wszDumpName, D3DXIFF_PNG, pRenderTarget, nullptr, nullptr); + } +#endif + //} + + return D3D9SetRenderTarget_Original (This, RenderTargetIndex, pRenderTarget); +} + +void +tbf::RenderFix::TextureManager::Init (void) +{ + InitializeCriticalSectionAndSpinCount (&cs_cache, 16384UL); + + // Create the directory to store dumped textures + if (config.textures.dump) + CreateDirectoryW (TBFIX_TEXTURE_DIR, nullptr); + + tex_log = TBF_CreateLog (L"logs/textures.log"); + + d3dx9_43_dll = LoadLibrary (L"D3DX9_43.DLL"); + + // + // Walk injectable textures so we don't have to query the filesystem on every + // texture load to check if a injectable one exists. + // + if ( GetFileAttributesW (TBFIX_TEXTURE_DIR L"\\inject") != + INVALID_FILE_ATTRIBUTES ) { + WIN32_FIND_DATA fd; + HANDLE hFind = INVALID_HANDLE_VALUE; + int files = 0; + LARGE_INTEGER liSize = { 0 }; + + tex_log->LogEx ( true, L"[Inject Tex] Enumerating injectable textures..." ); + + hFind = FindFirstFileW (TBFIX_TEXTURE_DIR L"\\inject\\textures\\blocking\\*", &fd); + + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (fd.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + if (wcsstr (_wcslwr (fd.cFileName), TBFIX_TEXTURE_EXT)) { + uint32_t checksum; + swscanf (fd.cFileName, L"%x" TBFIX_TEXTURE_EXT, &checksum); + + // Already got this texture... + if (injectable_textures.count (checksum)) + continue; + + ++files; + + LARGE_INTEGER fsize; + + fsize.HighPart = fd.nFileSizeHigh; + fsize.LowPart = fd.nFileSizeLow; + + liSize.QuadPart += fsize.QuadPart; + + tbf_tex_record_s rec; + rec.size = (uint32_t)fsize.QuadPart; + rec.archive = -1; + rec.method = Blocking; + + injectable_textures.insert (std::make_pair (checksum, rec)); + } + } + } while (FindNextFileW (hFind, &fd) != 0); + + FindClose (hFind); + } + + hFind = FindFirstFileW (TBFIX_TEXTURE_DIR L"\\inject\\textures\\streaming\\*", &fd); + + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (fd.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + if (wcsstr (_wcslwr (fd.cFileName), TBFIX_TEXTURE_EXT)) { + uint32_t checksum; + swscanf (fd.cFileName, L"%x" TBFIX_TEXTURE_EXT, &checksum); + + // Already got this texture... + if (injectable_textures.count (checksum)) + continue; + + ++files; + + LARGE_INTEGER fsize; + + fsize.HighPart = fd.nFileSizeHigh; + fsize.LowPart = fd.nFileSizeLow; + + liSize.QuadPart += fsize.QuadPart; + + tbf_tex_record_s rec; + rec.size = (uint32_t)fsize.QuadPart; + rec.archive = -1; + rec.method = Streaming; + + injectable_textures.insert (std::make_pair (checksum, rec)); + } + } + } while (FindNextFileW (hFind, &fd) != 0); + + FindClose (hFind); + } + + hFind = FindFirstFileW (TBFIX_TEXTURE_DIR L"\\inject\\textures\\*", &fd); + + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (fd.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + if (wcsstr (_wcslwr (fd.cFileName), TBFIX_TEXTURE_EXT)) { + uint32_t checksum; + swscanf (fd.cFileName, L"%x" TBFIX_TEXTURE_EXT, &checksum); + + // Already got this texture... + if (injectable_textures.count (checksum)) + continue; + + ++files; + + LARGE_INTEGER fsize; + + fsize.HighPart = fd.nFileSizeHigh; + fsize.LowPart = fd.nFileSizeLow; + + liSize.QuadPart += fsize.QuadPart; + + tbf_tex_record_s rec; + rec.size = (uint32_t)fsize.QuadPart; + rec.archive = -1; + rec.method = DontCare; + + if (! injectable_textures.count (checksum)) + injectable_textures.insert (std::make_pair (checksum, rec)); + } + } + } while (FindNextFileW (hFind, &fd) != 0); + + FindClose (hFind); + } + +#if 0 + hFind = FindFirstFileW (TBFIX_TEXTURE_DIR L"\\inject\\*.*", &fd); + + if (hFind != INVALID_HANDLE_VALUE) { + int archive = 0; + + do { + if (fd.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + wchar_t* wszArchiveNameLwr = + _wcslwr (_wcsdup (fd.cFileName)); + + if ( wcsstr (wszArchiveNameLwr, L".zip") || + wcsstr (wszArchiveNameLwr, L".7z") ) { + struct archive *a; + struct archive_entry *entry; + int r, tex_count = 0; + + a = archive_read_new (); + + archive_read_support_filter_all (a); + archive_read_support_format_all (a); + + wchar_t wszQualifiedArchiveName [MAX_PATH]; + _swprintf ( wszQualifiedArchiveName, + L"%s\\inject\\%s", + TSFIX_TEXTURE_DIR, + fd.cFileName ); + + r = archive_read_open_filename_w (a, wszQualifiedArchiveName, 10240); + + if (r == ARCHIVE_OK) { + int fileno = 0; + + while (archive_read_next_header (a, &entry) == ARCHIVE_OK) { + wchar_t* wszFullName = + _wcslwr (_wcsdup (archive_entry_pathname_w (entry))); + + if ( wcsstr ( wszFullName, TSFIX_TEXTURE_EXT) ) { + tsf_load_method_t method = DontCare; + + uint32_t checksum; + wchar_t* wszUnqualifiedEntry = + wszFullName + wcslen (wszFullName); + + // Strip the path + while ( wszUnqualifiedEntry >= wszFullName && + *wszUnqualifiedEntry != L'/') + wszUnqualifiedEntry--; + + if (*wszUnqualifiedEntry == L'/') + ++wszUnqualifiedEntry; + + swscanf (wszUnqualifiedEntry, L"%x" TSFIX_TEXTURE_EXT, &checksum); + + // Already got this texture... + if (injectable_textures.count (checksum)) { + free (wszFullName); + archive_read_data_skip (a); + ++fileno; + continue; + } + + if (wcsstr (wszFullName, L"streaming")) + method = Streaming; + else if (wcsstr (wszFullName, L"blocking")) + method = Blocking; + + tsf_tex_record_s rec; + rec.size = (uint32_t)archive_entry_size (entry); + rec.archive = archive; + rec.fileno = fileno; + rec.method = method; + + injectable_textures.insert (std::make_pair (checksum, rec)); + + ++tex_count; + ++files; + + liSize.QuadPart += rec.size; + } + + free (wszFullName); + + archive_read_data_skip (a); + ++fileno; + } + + if (tex_count == 0) { + } else { + ++archive; + archives.push_back (wszQualifiedArchiveName); + } + + r = archive_read_free (a); + } + + free (wszArchiveNameLwr); + } + } + } while (FindNextFileW (hFind, &fd) != 0); + + FindClose (hFind); + } +#endif + + tex_log->LogEx ( false, L" %lu files (%3.1f MiB)\n", + files, (double)liSize.QuadPart / (1024.0 * 1024.0) ); + } + + if ( GetFileAttributesW (TBFIX_TEXTURE_DIR L"\\dump\\textures") != + INVALID_FILE_ATTRIBUTES ) { + WIN32_FIND_DATA fd; + WIN32_FIND_DATA fd_sub; + HANDLE hSubFind = INVALID_HANDLE_VALUE; + HANDLE hFind = INVALID_HANDLE_VALUE; + int files = 0; + LARGE_INTEGER liSize = { 0 }; + + tex_log->LogEx ( true, L"[ Dump Tex ] Enumerating dumped textures..." ); + + hFind = FindFirstFileW (TBFIX_TEXTURE_DIR L"\\dump\\textures\\*", &fd); + + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (fd.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + wchar_t wszSubDir [MAX_PATH]; + _swprintf (wszSubDir, L"%s\\dump\\%s\\textures\\*", TBFIX_TEXTURE_DIR, fd.cFileName); + + hSubFind = FindFirstFileW (wszSubDir, &fd_sub); + + if (hSubFind != INVALID_HANDLE_VALUE) { + do { + if (wcsstr (_wcslwr (fd_sub.cFileName), L".dds")) { + uint32_t checksum; + swscanf (fd_sub.cFileName, L"%08x.dds", &checksum); + + ++files; + + LARGE_INTEGER fsize; + + fsize.HighPart = fd_sub.nFileSizeHigh; + fsize.LowPart = fd_sub.nFileSizeLow; + + liSize.QuadPart += fsize.QuadPart; + + dumped_textures.insert (checksum); + } + } while (FindNextFileW (hSubFind, &fd_sub) != 0); + + FindClose (hSubFind); + } + } + } while (FindNextFileW (hFind, &fd) != 0); + + FindClose (hFind); + } + + tex_log->LogEx ( false, L" %lu files (%3.1f MiB)\n", + files, (double)liSize.QuadPart / (1024.0 * 1024.0) ); + } + + TBF_CreateDLLHook ( config.system.injector.c_str (), + "D3D9SetRenderState_Override", + D3D9SetRenderState_Detour, + (LPVOID*)&D3D9SetRenderState_Original ); + + TBF_CreateDLLHook ( config.system.injector.c_str (), + "D3D9BeginScene_Override", + D3D9BeginScene_Detour, + (LPVOID*)&D3D9BeginScene_Original ); + + TBF_CreateDLLHook ( config.system.injector.c_str (), + "D3D9StretchRect_Override", + D3D9StretchRect_Detour, + (LPVOID*)&D3D9StretchRect_Original ); + + TBF_CreateDLLHook ( config.system.injector.c_str (), + "D3D9CreateDepthStencilSurface_Override", + D3D9CreateDepthStencilSurface_Detour, + (LPVOID*)&D3D9CreateDepthStencilSurface_Original ); + + TBF_CreateDLLHook ( config.system.injector.c_str (), + "D3D9CreateTexture_Override", + D3D9CreateTexture_Detour, + (LPVOID*)&D3D9CreateTexture_Original ); + + TBF_CreateDLLHook ( config.system.injector.c_str (), + "D3D9SetTexture_Override", + D3D9SetTexture_Detour, + (LPVOID*)&D3D9SetTexture_Original ); + + TBF_CreateDLLHook ( config.system.injector.c_str (), + "D3D9SetRenderTarget_Override", + D3D9SetRenderTarget_Detour, + (LPVOID*)&D3D9SetRenderTarget_Original ); + + TBF_CreateDLLHook ( config.system.injector.c_str (), + "D3D9SetDepthStencilSurface_Override", + D3D9SetDepthStencilSurface_Detour, + (LPVOID*)&D3D9SetDepthStencilSurface_Original ); + + TBF_CreateDLLHook ( L"D3DX9_43.DLL", + "D3DXCreateTextureFromFileInMemoryEx", + D3DXCreateTextureFromFileInMemoryEx_Detour, + (LPVOID *)&D3DXCreateTextureFromFileInMemoryEx_Original ); + + D3DXSaveTextureToFile = + (D3DXSaveTextureToFile_pfn) + GetProcAddress ( tbf::RenderFix::d3dx9_43_dll, + "D3DXSaveTextureToFileW" ); + + D3DXCreateTextureFromFile = + (D3DXCreateTextureFromFile_pfn) + GetProcAddress ( tbf::RenderFix::d3dx9_43_dll, + "D3DXCreateTextureFromFileW" ); + + D3DXCreateTextureFromFileEx = + (D3DXCreateTextureFromFileEx_pfn) + GetProcAddress ( tbf::RenderFix::d3dx9_43_dll, + "D3DXCreateTextureFromFileExW" ); + + D3DXGetImageInfoFromFileInMemory = + (D3DXGetImageInfoFromFileInMemory_pfn) + GetProcAddress ( tbf::RenderFix::d3dx9_43_dll, + "D3DXGetImageInfoFromFileInMemory" ); + + D3DXGetImageInfoFromFile = + (D3DXGetImageInfoFromFile_pfn) + GetProcAddress ( tbf::RenderFix::d3dx9_43_dll, + "D3DXGetImageInfoFromFileW" ); + + // We don't hook this, but we still use it... + if (D3D9CreateRenderTarget_Original == nullptr) { + static HMODULE hModD3D9 = + GetModuleHandle (config.system.injector.c_str ()); + D3D9CreateRenderTarget_Original = + (CreateRenderTarget_pfn) + GetProcAddress (hModD3D9, "D3D9CreateRenderTarget_Override"); + } + + // We don't hook this, but we still use it... + if (D3D9CreateDepthStencilSurface_Original == nullptr) { + static HMODULE hModD3D9 = + GetModuleHandle (config.system.injector.c_str ()); + D3D9CreateDepthStencilSurface_Original = + (CreateDepthStencilSurface_pfn) + GetProcAddress (hModD3D9, "D3D9CreateDepthStencilSurface_Override"); + } + + time_saved = 0.0f; + + InitializeCriticalSectionAndSpinCount (&cs_tex_inject, 10000000); + InitializeCriticalSectionAndSpinCount (&cs_tex_resample, 100000); + InitializeCriticalSectionAndSpinCount (&cs_tex_stream, 100000); + + decomp_semaphore = + CreateSemaphore ( nullptr, + config.textures.worker_threads, + config.textures.worker_threads, + nullptr ); + + resample_pool = new SK_TextureThreadPool (); + + SK_ICommandProcessor& command = + *SK_GetCommandProcessor (); + + command.AddVariable ( + "Textures.Remap", + TBF_CreateVar (SK_IVariable::Boolean, &__remap_textures) ); + + command.AddVariable ( + "Textures.Purge", + TBF_CreateVar (SK_IVariable::Boolean, &__need_purge) ); + + command.AddVariable ( + "Textures.Trace", + TBF_CreateVar (SK_IVariable::Boolean, &__log_used) ); + + command.AddVariable ( + "Textures.ShowCache", + TBF_CreateVar (SK_IVariable::Boolean, &__show_cache) ); + + command.AddVariable ( + "Textures.MaxCacheSize", + TBF_CreateVar (SK_IVariable::Int, &config.textures.max_cache_in_mib) ); +} + +// Skip the purge step on shutdown +bool shutting_down = false; + +void +tbf::RenderFix::TextureManager::Shutdown (void) +{ + // 16.6 ms per-frame (60 FPS) + const float frame_time = 16.6f; + + while (! textures_to_stream.empty ()) + textures_to_stream.pop (); + + shutting_down = true; + + tex_mgr.reset (); + + DeleteCriticalSection (&cs_tex_stream); + DeleteCriticalSection (&cs_tex_resample); + DeleteCriticalSection (&cs_tex_inject); + + DeleteCriticalSection (&cs_cache); + + CloseHandle (decomp_semaphore); + + tex_log->Log ( L"[Perf Stats] At shutdown: %7.2f seconds (%7.2f frames)" + L" saved by cache", + time_saved / 1000.0f, + time_saved / frame_time ); + tex_log->close (); + + FreeLibrary (d3dx9_43_dll); +} + +void +tbf::RenderFix::TextureManager::purge (void) +{ + if (shutting_down) + return; + + int released = 0; + int released_injected = 0; + uint64_t reclaimed = 0; + uint64_t reclaimed_injected = 0; + + tex_log->Log (L"[ Tex. Mgr ] -- TextureManager::purge (...) -- "); + + // Purge any pending removes + getTexture (0); + + tex_log->Log ( L"[ Tex. Mgr ] *** Current Cache Size: %6.2f MiB " + L"(User Limit: %6.2f MiB)", + (double)cacheSizeTotal () / (1024.0 * 1024.0), + (double)config.textures.max_cache_in_mib ); + + tex_log->Log (L"[ Tex. Mgr ] Releasing textures..."); + + std::unordered_map ::iterator it = + textures.begin (); + + std::vector unreferenced_textures; + + while (it != textures.end ()) { + if ((*it).second->d3d9_tex->can_free) + unreferenced_textures.push_back ((*it).second); + + ++it; + } + + std::sort ( unreferenced_textures.begin (), + unreferenced_textures.end (), + []( tbf::RenderFix::Texture *a, + tbf::RenderFix::Texture *b ) + { + return a->d3d9_tex->last_used.QuadPart < + b->d3d9_tex->last_used.QuadPart; + } + ); + + std::vector ::iterator free_it = + unreferenced_textures.begin (); + + // We need to over-free, or we will likely be purging every other texture load + int64_t target_size = + std::max (128, config.textures.max_cache_in_mib - 64) * 1024LL * 1024LL; + int64_t start_size = + cacheSizeTotal (); + + while ( start_size - reclaimed > target_size && + free_it != unreferenced_textures.end () ) { + int tex_refs = -1; + ISKTextureD3D9* pSKTex = (*free_it)->d3d9_tex; + + // + // Skip loads that are in-flight so that we do not hitch + // + if (is_streaming ((*free_it)->crc32)) { + ++free_it; + continue; + } + + // + // Do not evict blocking loads, they are generally small and + // will cause performance problems if we have to reload them + // again later. + // + if (pSKTex->must_block) { + ++free_it; + continue; + } + + int64_t ovr_size = 0; + int64_t base_size = 0; + + ++free_it; + + base_size = pSKTex->tex_size; + ovr_size = pSKTex->override_size; + tex_refs = pSKTex->Release (); + + if (tex_refs == 0) { + if (ovr_size != 0) { + reclaimed += ovr_size; + + released_injected++; + reclaimed_injected += ovr_size; + } + } else { + tex_log->Log (L"[ Tex. Mgr ] Invalid reference count (%lu)!", tex_refs); + } + + ++released; + reclaimed += base_size; + } + + tex_log->Log ( L"[ Tex. Mgr ] %4d textures (%4d remain)", + released, + textures.size () ); + + tex_log->Log ( L"[ Tex. Mgr ] >> Reclaimed %6.2f MiB of memory (%6.2f MiB from %lu inject)", + (double)reclaimed / (1024.0 * 1024.0), + (double)reclaimed_injected / (1024.0 * 1024.0), + released_injected ); + + updateOSD (); + + tex_log->Log (L"[ Tex. Mgr ] ----------- Finished ------------ "); +} + +void +tbf::RenderFix::TextureManager::reset (void) +{ + int underflows = 0; + + int ext_refs = 0; + int ext_textures = 0; + + int release_count = 0; + int unreleased_count = 0; + int ref_count = 0; + + int released_injected = 0; + uint64_t reclaimed = 0; + uint64_t reclaimed_injected = 0; + + tex_log->Log (L"[ Tex. Mgr ] -- TextureManager::reset (...) -- "); + + // Purge any pending removes + getTexture (0); + + tex_log->Log (L"[ Tex. Mgr ] Releasing textures..."); + + std::unordered_map ::iterator it = + textures.begin (); + + while (it != textures.end ()) { + ISKTextureD3D9* pSKTex = + (*it).second->d3d9_tex; + + ++it; + + bool can_free = false; + int64_t base_size = 0; + int64_t ovr_size = 0; + + if (pSKTex->can_free) { + can_free = true; + base_size = pSKTex->tex_size; + ovr_size = pSKTex->override_size; + } + + else { + ext_refs += pSKTex->refs; + ext_textures ++; + + ++unreleased_count; + continue; + } + + int tex_refs = pSKTex->Release (); + + if (tex_refs == 0) { + if (ovr_size != 0) { + reclaimed += ovr_size; + + released_injected++; + reclaimed_injected += ovr_size; + } + + ++release_count; + reclaimed += base_size; + + ref_count += 1; + } + + else { + ++unreleased_count; + ext_refs += tex_refs; + ext_textures ++; + } + } + + tex_log->Log ( L"[ Tex. Mgr ] %4d textures (%4d references)", + release_count + unreleased_count, + ref_count + ext_refs ); + + if (ext_refs > 0) { + tex_log->Log ( L"[ Tex. Mgr ] >> WARNING: The game is still holding references (%d) to %d textures !!!", + ext_refs, ext_textures ); + } + + tex_log->Log ( L"[ Mem. Mgr ] === Memory Management Summary ==="); + + tex_log->Log ( L"[ Mem. Mgr ] %12.2f MiB Freed", + (double)reclaimed / (1048576.0) ); + tex_log->Log ( L"[ Mem. Mgr ] %12.2f MiB Leaked", + (double)(cacheSizeTotal () - reclaimed) + / (1048576.0) ); + + updateOSD (); + + // Commit this immediately, such that D3D9 Reset will not fail in + // fullscreen mode... + TBFix_LoadQueuedTextures (); + purge (); + + tex_log->Log (L"[ Tex. Mgr ] ----------- Finished ------------ "); +} + +void +tbf::RenderFix::TextureManager::updateOSD (void) +{ + double cache_basic = (double)cacheSizeBasic () / (1048576.0f); + double cache_injected = (double)cacheSizeInjected () / (1048576.0f); + double cache_total = cache_basic + cache_injected; + + osd_stats = ""; + + char szFormatted [64]; + sprintf ( szFormatted, "%6lu Total Textures : %8.2f MiB", + numTextures () + numInjectedTextures (), + cache_total ); + osd_stats += szFormatted; + + if ( tbf::RenderFix::pDevice != nullptr && + tbf::RenderFix::pDevice->GetAvailableTextureMem () / 1048576UL != 4095) + sprintf ( szFormatted, " (%4lu MiB Available)\n", + tbf::RenderFix::pDevice->GetAvailableTextureMem () + / 1048576UL ); + else + sprintf (szFormatted, "\n"); + + osd_stats += szFormatted; + + sprintf ( szFormatted, "%6lu Base Textures : %8.2f MiB %s\n", + numTextures (), + cache_basic, + __remap_textures ? "" : "<----" ); + + osd_stats += szFormatted; + + sprintf ( szFormatted, "%6lu New Textures : %8.2f MiB %s\n", + numInjectedTextures (), + cache_injected, + __remap_textures ? "<----" : "" ); + + osd_stats += szFormatted; + + sprintf ( szFormatted, "%6lu Cache Hits : %8.2f Seconds Saved", + hits, + time_saved / 1000.0f ); + + osd_stats += szFormatted; + + if (debug_tex_id != 0x00) { + osd_stats += "\n\n"; + + sprintf ( szFormatted, " Debug Texture : %08x", + debug_tex_id ); + + osd_stats += szFormatted; + } +} + +std::vector textures_used_last_dump; +int tex_dbg_idx = 0; + +void +TBFix_LogUsedTextures (void) +{ + if (__log_used) + { + textures_used_last_dump.clear (); + tex_dbg_idx = 0; + + tex_log->Log (L"[ Tex. Log ] ---------- FrameTrace ----------- "); + + for ( auto it = textures_used.begin (); + it != textures_used.end (); + ++it ) { + auto tex_record = + tbf::RenderFix::tex_mgr.getTexture (*it); + + // Handle the RARE case where a purge happens immediately following + // the last frame + if ( tex_record != nullptr && + tex_record->d3d9_tex != nullptr ) + { + ISKTextureD3D9* pSKTex = + (ISKTextureD3D9 *)tex_record->d3d9_tex; + + textures_used_last_dump.push_back (*it); + + tex_log->Log ( L"[ Tex. Log ] %08x.dds { Base: %6.2f MiB, " + L"Inject: %6.2f MiB, Load Time: %8.3f ms }", + *it, + (double)pSKTex->tex_size / + (1024.0 * 1024.0), + + pSKTex->override_size != 0 ? + (double)pSKTex->override_size / + (1024.0 * 1024.0) : 0.0, + + tbf::RenderFix::tex_mgr.getTexture (*it)->load_time ); + } + } + + tex_log->Log (L"[ Tex. Log ] ---------- FrameTrace ----------- "); + + __log_used = false; + } + + textures_used.clear (); +} + + +CRITICAL_SECTION SK_TextureWorkerThread::cs_worker_init; +ULONG SK_TextureWorkerThread::num_threads_init = 0UL; + +HRESULT +WINAPI +ResampleTexture (tbf_tex_load_s* load) +{ + QueryPerformanceFrequency (&load->freq); + QueryPerformanceCounter_Original (&load->start); + + D3DXIMAGE_INFO img_info; + + D3DXGetImageInfoFromFileInMemory ( + load->pSrcData, + load->SrcDataSize, + &img_info ); + + HRESULT hr = E_FAIL; + + if (img_info.Depth == 1) { + hr = + D3DXCreateTextureFromFileInMemoryEx_Original ( + load->pDevice, + load->pSrcData, load->SrcDataSize, + img_info.Width, img_info.Height, 0,//D3DX_DEFAULT, + 0, img_info.Format, + D3DPOOL_DEFAULT, + D3DX_FILTER_POINT, D3DX_FILTER_TRIANGLE | D3DX_FILTER_DITHER, + 0, + nullptr, nullptr, + &load->pSrc ); + } else { + tex_log->Log (L"[ Tex. Mgr ] Will not resample cubemap..."); + } + + delete [] load->pSrcData; + + return hr; +} + +unsigned int +__stdcall +SK_TextureWorkerThread::ThreadProc (LPVOID user) +{ + EnterCriticalSection (&cs_worker_init); + { + DWORD dwThreadId = GetCurrentThreadId (); + + if (! streaming_memory::data_len.count (dwThreadId)) { + streaming_memory::data_len [dwThreadId] = 0; + streaming_memory::data [dwThreadId] = nullptr; + streaming_memory::data_age [dwThreadId] = 0; + } + } + LeaveCriticalSection (&cs_worker_init); + + SYSTEM_INFO sysinfo; + GetSystemInfo (&sysinfo); + + ULONG thread_num = InterlockedIncrement (&num_threads_init); + + // If a system has more than 4 CPUs (logical or otherwise), let the last one + // be dedicated to rendering. + ULONG processor_num = thread_num % ( sysinfo.dwNumberOfProcessors > 4 ? + sysinfo.dwNumberOfProcessors - 1 : + sysinfo.dwNumberOfProcessors ); + + // Tales of Symphonia and Zestiria both pin the render thread to the last + // CPU... let's try to keep our worker threads OFF that CPU. + + SetThreadIdealProcessor (GetCurrentThread (), processor_num); + SetThreadAffinityMask (GetCurrentThread (), 1 << processor_num); + + // Ghetto sync. barrier, since Windows 7 does not support them... + while ( InterlockedCompareExchange ( + &num_threads_init, + config.textures.worker_threads, + config.textures.worker_threads + ) < config.textures.worker_threads ) { + Sleep (15); + } + + SK_TextureWorkerThread* pThread = + (SK_TextureWorkerThread *)user; + + DWORD dwWaitStatus = 0; + + struct { + const DWORD job_start = WAIT_OBJECT_0; + const DWORD mem_trim = WAIT_OBJECT_0 + 1; + const DWORD thread_end = WAIT_OBJECT_0 + 2; + } wait; + + do { + dwWaitStatus = + WaitForMultipleObjects ( 3, + pThread->control_.ops, + FALSE, + INFINITE ); + + // New Work Ready + if (dwWaitStatus == wait.job_start) { + tbf_tex_load_s* pStream = pThread->job_; + + start_load (); + { + if (pStream->type == tbf_tex_load_s::Resample) { + InterlockedIncrement (&resampling); + + QueryPerformanceFrequency (&pStream->freq); + QueryPerformanceCounter_Original (&pStream->start); + + HRESULT hr = + ResampleTexture (pStream);//InjectTexture (pStream); + + QueryPerformanceCounter_Original (&pStream->end); + + InterlockedDecrement (&resampling); + + if (SUCCEEDED (hr)) + pThread->pool_->postFinished (pStream); + + pThread->finishJob (); + } else { + InterlockedIncrement (&streaming); + InterlockedExchangeAdd (&streaming_bytes, pStream->SrcDataSize); + + QueryPerformanceFrequency (&pStream->freq); + QueryPerformanceCounter_Original (&pStream->start); + + HRESULT hr = + InjectTexture (pStream); + + QueryPerformanceCounter_Original (&pStream->end); + + InterlockedExchangeSubtract (&streaming_bytes, pStream->SrcDataSize); + InterlockedDecrement (&streaming); + + if (SUCCEEDED (hr)) + pThread->pool_->postFinished (pStream); + + pThread->finishJob (); + } + } + end_load (); + + } + + else if (dwWaitStatus == (wait.mem_trim)) { + const size_t MIN_SIZE = 8192 * 1024; + const uint32_t MIN_AGE = 5000UL; + + size_t before = streaming_memory::data_len [GetCurrentThreadId ()]; + + streaming_memory::trim (MIN_SIZE, timeGetTime () - MIN_AGE); + + size_t now = streaming_memory::data_len [GetCurrentThreadId ()]; + if (before != now) { + tex_log->Log ( L"[ Mem. Mgr ] Trimmed %9lu bytes of temporary memory for tid=%x", + before - now, + GetCurrentThreadId () ); + } + } + + else if (dwWaitStatus != (wait.thread_end)) { + dll_log->Log ( L"[ Tex. Mgr ] Unexpected Worker Thread Wait Status: %X", + dwWaitStatus ); + } + } while (dwWaitStatus != (wait.thread_end)); + + streaming_memory::trim (0, timeGetTime ()); + + //CloseHandle (GetCurrentThread ()); + return 0; +} + +unsigned int +__stdcall +SK_TextureThreadPool::Spooler (LPVOID user) +{ + SK_TextureThreadPool* pPool = + (SK_TextureThreadPool *)user; + + WaitForSingleObject (pPool->events_.jobs_added, INFINITE); + + while (WaitForSingleObject (pPool->events_.shutdown, 0) == WAIT_TIMEOUT) { + tbf_tex_load_s* pJob = + pPool->getNextJob (); + + while (pJob != nullptr) { + auto it = pPool->workers_.begin (); + + bool started = false; + + while (it != pPool->workers_.end ()) { + if (! (*it)->isBusy ()) { + if (! started) { + (*it)->startJob (pJob); + started = true; + } else { + (*it)->trim (); + } + } + + ++it; + } + + // All worker threads are busy, so wait... + if (! started) { + WaitForSingleObject (pPool->events_.results_waiting, INFINITE); + } else { + pJob = + pPool->getNextJob (); + } + } + + const int MAX_TIME_BETWEEN_TRIMS = 1500UL; + while ( WaitForSingleObject ( + pPool->events_.jobs_added, + MAX_TIME_BETWEEN_TRIMS ) == + WAIT_TIMEOUT ) { + auto it = pPool->workers_.begin (); + + while (it != pPool->workers_.end ()) { + if (! (*it)->isBusy ()) { + (*it)->trim (); + } + + ++it; + } + } + } + + //CloseHandle (GetCurrentThread ()); + return 0; +} + +void +SK_TextureWorkerThread::finishJob (void) +{ + job_ = nullptr; +} +HMODULE tbf::RenderFix::d3dx9_43_dll = 0; diff --git a/tbf.VC.db b/tbf.VC.db new file mode 100644 index 0000000..b995257 Binary files /dev/null and b/tbf.VC.db differ diff --git a/tbf.sln b/tbf.sln new file mode 100644 index 0000000..ab1aa81 --- /dev/null +++ b/tbf.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tbf", "tbf.vcxproj", "{66F0B0ED-F640-46EF-9E3A-0C524628D7AE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {66F0B0ED-F640-46EF-9E3A-0C524628D7AE}.Debug|x64.ActiveCfg = Debug|x64 + {66F0B0ED-F640-46EF-9E3A-0C524628D7AE}.Debug|x64.Build.0 = Debug|x64 + {66F0B0ED-F640-46EF-9E3A-0C524628D7AE}.Release|x64.ActiveCfg = Release|x64 + {66F0B0ED-F640-46EF-9E3A-0C524628D7AE}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tbf.vcxproj b/tbf.vcxproj new file mode 100644 index 0000000..56588a6 --- /dev/null +++ b/tbf.vcxproj @@ -0,0 +1,252 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {66F0B0ED-F640-46EF-9E3A-0C524628D7AE} + Win32Proj + tbf + 8.1 + 8.1 + + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + tbfix + + + + + + Level3 + Full + _DEBUG;_WINDOWS;_USRDLL;TBF_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDebug + true + Default + false + false + true + StreamingSIMDExtensions2 + true + AnySuitable + true + Speed + false + true + include\ + + + Windows + true + resource\symbols.def + Enabled + true + true + true + UseFastLinkTimeCodeGeneration + DllMain + false + false + true + false + true + + + + + Level3 + + + Full + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;TBF_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + false + AnySuitable + Speed + true + Sync + false + false + StreamingSIMDExtensions2 + false + true + true + + + Windows + true + true + symbols.def + UseFastLinkTimeCodeGeneration + true + false + false + true + true + false + false + true + false + true + Enabled + + + + + + + Level3 + + + Full + true + true + NDEBUG;_WINDOWS;_USRDLL;TBF_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + Disabled + true + true + false + false + NotSet + false + Speed + false + include\ + + + Windows + true + false + false + resource\symbols.def + true + + + false + false + true + false + Default + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + false + + + false + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/version.ini b/version.ini new file mode 100644 index 0000000..dd59344 Binary files /dev/null and b/version.ini differ