diff --git a/include/DLL_VERSION.H b/include/DLL_VERSION.H index 137813c..d38d277 100644 --- a/include/DLL_VERSION.H +++ b/include/DLL_VERSION.H @@ -3,7 +3,7 @@ #define TBF_MAJOR 0 #define TBF_MINOR 3 -#define TBF_BUILD 0 +#define TBF_BUILD 1 #define TBF_REV 0 diff --git a/include/framerate.h b/include/framerate.h index 80864a4..3b98854 100644 --- a/include/framerate.h +++ b/include/framerate.h @@ -39,6 +39,17 @@ namespace tbf void RenderTick (void); + // Call when re-engaging the framerate limiter to make it match + // user prefrences. + void BlipFramerate (void); + + // Disable Special K's framerate limit, generally only useful + // when demonstrating what is wrong with Namco's. + void DisengageLimiter (void); + + float GetTargetFrametime (void); + + #if 0 // // At key points during the game, we need to disable the code that diff --git a/include/render.h b/include/render.h index 0ac7308..67a1cd7 100644 --- a/include/render.h +++ b/include/render.h @@ -40,6 +40,12 @@ namespace tbf void Reset ( IDirect3DDevice9 *This, D3DPRESENT_PARAMETERS *pPresentationParameters ); + // Indicates whether setting changes need a device reset + struct reset_state_s { + bool textures = false; + bool graphics = false; + } extern need_reset; + class CommandProcessor : public SK_IVariableListener { public: CommandProcessor (void); diff --git a/src/framerate.cpp b/src/framerate.cpp index 4a47e72..7b90cb4 100644 --- a/src/framerate.cpp +++ b/src/framerate.cpp @@ -48,6 +48,9 @@ uint32_t tbf::FrameRateFix::target_fps = 0; HMODULE tbf::FrameRateFix::bink_dll = 0; HMODULE tbf::FrameRateFix::kernel32_dll = 0; +typedef void (*NamcoLimiter_pfn)(void); +NamcoLimiter_pfn NamcoLimiter_Original = nullptr; + typedef void* (__stdcall *BinkOpen_pfn)(const char* filename, DWORD unknown0); BinkOpen_pfn BinkOpen_Original = nullptr; @@ -172,6 +175,13 @@ CreateTimerQueueTimer_Override ( return CreateTimerQueueTimer_Original (phNewTimer, TimerQueue, Callback, Parameter, DueTime, Period, Flags); } +void +NamcoLimiter_Detour(void) +{ + if (! config.framerate.replace_limiter) + NamcoLimiter_Original (); +} + void tbf::FrameRateFix::Init (void) { @@ -200,7 +210,7 @@ tbf::FrameRateFix::Init (void) TBF_ApplyQueuedHooks (); - if (config.framerate.replace_limiter) { + //if (config.framerate.replace_limiter) { /* 6D3610 0x48 0x83 0xec 0x38 ; Namco Limiter 6D3610 0xc3 0x90 0x90 0x90 ; No Limiter @@ -230,6 +240,7 @@ tbf::FrameRateFix::Init (void) if (limiter_addr != nullptr) { //dll_log->LogEx (true, L"[FrameLimit] * Installing variable rate simulation... "); +#if 0 uint8_t disable_inst [] = { 0xc3, 0x90, 0x90, 0x90 }; TBF_InjectMachineCode ( limiter_addr, @@ -237,9 +248,18 @@ tbf::FrameRateFix::Init (void) sizeof (disable_inst), PAGE_EXECUTE_READWRITE ); +#endif + + TBF_CreateFuncHook ( L"NamcoLimiter", + limiter_addr, + NamcoLimiter_Detour, + (LPVOID *)&NamcoLimiter_Original ); + TBF_EnableHook (limiter_addr); + + variable_speed_installed = true; } - } + //} } void @@ -249,13 +269,31 @@ tbf::FrameRateFix::Shutdown (void) FreeLibrary (kernel32_dll); FreeLibrary (bink_dll); +} - ////TBF_DisableHook (pfnSK_SetPresentParamsD3D9); +// Force RevMatching when re-engaging the limiter +void +tbf::FrameRateFix::BlipFramerate (void) +{ + tick_scale = 0; - if (variable_speed_installed) { - } + // A value of 0 will cause the game's preference be re-engaged. +} + +void +tbf::FrameRateFix::DisengageLimiter (void) +{ + SK_GetCommandProcessor ()->ProcessCommandLine ("TargetFPS 0.0"); +} + + +float +tbf::FrameRateFix::GetTargetFrametime (void) +{ + uint32_t* pTickScale = + (uint32_t *)(TBF_GetBaseAddr () + TICK_ADDR_BASE); - ////TBF_DisableHook (pfnSleep); + return ( (float)(*pTickScale) * 16.6666666f ); } // diff --git a/src/imgui/imgui.cpp b/src/imgui/imgui.cpp index d5813c9..1790760 100644 --- a/src/imgui/imgui.cpp +++ b/src/imgui/imgui.cpp @@ -817,7 +817,7 @@ ImGuiIO::ImGuiIO() DisplaySize = ImVec2(-1.0f, -1.0f); DeltaTime = 1.0f/60.0f; IniSavingRate = 5.0f; - IniFilename = "TBFix_UI.ini"; + //IniFilename = "TBFix_UI.ini"; LogFilename = "logs/ui.log"; Fonts = &GImDefaultFontAtlas; @@ -2143,7 +2143,7 @@ void ImGui::NewFrame() IM_PLACEMENT_NEW(g.LogClipboard) ImGuiTextBuffer(); IM_ASSERT(g.Settings.empty()); - LoadIniSettingsFromDisk(g.IO.IniFilename); + //LoadIniSettingsFromDisk(g.IO.IniFilename); g.Initialized = true; } @@ -2250,8 +2250,8 @@ void ImGui::NewFrame() if (g.SettingsDirtyTimer > 0.0f) { g.SettingsDirtyTimer -= g.IO.DeltaTime; - if (g.SettingsDirtyTimer <= 0.0f) - SaveIniSettingsToDisk(g.IO.IniFilename); + //if (g.SettingsDirtyTimer <= 0.0f) + //SaveIniSettingsToDisk(g.IO.IniFilename); } // Find the window we are hovering. Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow @@ -2378,7 +2378,7 @@ void ImGui::Shutdown() if (!g.Initialized) return; - SaveIniSettingsToDisk(g.IO.IniFilename); + //SaveIniSettingsToDisk(g.IO.IniFilename); for (int i = 0; i < g.Windows.Size; i++) { diff --git a/src/imgui/imgui_demo.cpp b/src/imgui/imgui_demo.cpp index 8427fb2..971706b 100644 --- a/src/imgui/imgui_demo.cpp +++ b/src/imgui/imgui_demo.cpp @@ -5,7 +5,6 @@ // You can call ImGui::ShowTestWindow() in your code to learn about various features of ImGui. // Everything in this file will be stripped out by the linker if you don't call ImGui::ShowTestWindow(). -#if 0 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif @@ -2636,6 +2635,4 @@ void ImGui::ShowTestWindow(bool*) {} void ImGui::ShowUserGuide() {} void ImGui::ShowStyleEditor(ImGuiStyle*) {} -#endif - #endif \ No newline at end of file diff --git a/src/render.cpp b/src/render.cpp index 7a78607..6075469 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -1464,9 +1464,13 @@ tbf::RenderFix::Reset ( IDirect3DDevice9 *This, if (pending_loads ()) TBFix_LoadQueuedTextures (); - tex_mgr.reset (); + tex_mgr.reset (); + + need_reset.textures = false; } + need_reset.graphics = false; + vs_checksums.clear (); ps_checksums.clear (); @@ -1934,4 +1938,7 @@ volatile ULONG 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 +HMODULE tbf::RenderFix::user32_dll = 0; + +tbf::RenderFix::reset_state_s + tbf::RenderFix::need_reset; \ No newline at end of file diff --git a/src/tbt/main.cpp b/src/tbt/control_panel.cpp similarity index 66% rename from src/tbt/main.cpp rename to src/tbt/control_panel.cpp index 5683a57..e026cfd 100644 --- a/src/tbt/main.cpp +++ b/src/tbt/control_panel.cpp @@ -9,6 +9,8 @@ #include #include "config.h" +#include "render.h" +#include "framerate.h" // Data static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; @@ -62,9 +64,8 @@ WndProc ( HWND hWnd, #include -bool show_config = true; -bool show_test_window = true; -bool show_gamepad_config = false; +bool show_config = true; +bool show_test_window = false; ImVec4 clear_col = ImColor(114, 144, 154); @@ -163,26 +164,27 @@ TBFix_GamepadConfigDlg (void) } } - int orig_sel = gamepads.sel; - - ImGui::SetNextWindowSize (ImVec2 (225, 90), ImGuiSetCond_Appearing); + if (ImGui::BeginPopupModal ("Gamepad Config", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_ShowBorders)) + { + int orig_sel = gamepads.sel; - ImGui::Begin ("Gamepad Config"); - ImGui::ListBox ("Gamepad\nIcons", &gamepads.sel, gamepads.array.data (), gamepads.array.size (), 3); - ImGui::End (); + if (ImGui::ListBox ("Gamepad\nIcons", &gamepads.sel, gamepads.array.data (), gamepads.array.size (), 3)) + { + if (orig_sel != gamepads.sel) + { + wchar_t pad[128] = { L'\0' }; - if (orig_sel != gamepads.sel) - { - wchar_t pad [128] = { L'\0' }; + swprintf(pad, L"%hs", gamepads.array[gamepads.sel]); - swprintf (pad, L"%hs", gamepads.array [gamepads.sel]); + config.input.gamepad.texture_set = pad; - config.input.gamepad.texture_set = pad; + tbf::RenderFix::need_reset.textures = true; + } - //extern void - //TBFix_ReloadPadButtons (void); + ImGui::CloseCurrentPopup (); + } - //TBFix_ReloadPadButtons (); + ImGui::EndPopup(); } } @@ -196,50 +198,117 @@ TBFix_DrawConfigUI (LPDIRECT3DDEVICE9 pDev = nullptr) ImGui_ImplDX9_NewFrame (); - ImGui::SetNextWindowPos (ImVec2 (640, 360), ImGuiSetCond_FirstUseEver); + //ImGui::SetNextWindowPos (ImVec2 ( 640, 360), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowPosCenter (ImGuiSetCond_Always); + ImGui::SetNextWindowSizeConstraints (ImVec2 (50, 50), ImGui::GetIO ().DisplaySize); // 1. Show a simple window // Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets appears in a window automatically called "Debug" - ImGui::Begin ("Tales of Berseria \"Fix\" Control Panel", &show_config); + ImGui::Begin ("Tales of Berseria \"Fix\" Control Panel", &show_config, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_ShowBorders); ImGui::PushItemWidth (ImGui::GetWindowWidth () * 0.65f); - if (ImGui::CollapsingHeader ("Framerate Control")) + if (tbf::RenderFix::need_reset.graphics || tbf::RenderFix::need_reset.textures) { + ImGui::TextColored ( ImVec4 (0.8f, 0.8f, 0.1f, 1.0f), + " You have made changes that will not apply until you change Screen Modes in Graphics Settings,\n" + " or by performing Alt + Tab with the game set to Fullscreen mode." ); + } + + if (ImGui::CollapsingHeader ("Framerate Control", ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Checkbox ("Disable Namco's Limiter (requires restart)", &config.framerate.replace_limiter); + int limiter = + config.framerate.replace_limiter ? 1 : 0; + + const char* szLabel = (limiter == 0 ? "Framerate Limiter (choose something else!)" : "Framerate Limiter"); + + ImGui::Combo (szLabel, &limiter, "Namco (A.K.A. Stutterfest 2017)\0" + "Special K (Precision Timing For The Win!)\0\0" ); static float values [120] = { 0 }; - static int values_offset = 0; - static float refresh_time = ImGui::GetTime (); + static int values_offset = 0; values [values_offset] = 1000.0f * ImGui::GetIO ().DeltaTime; values_offset = (values_offset + 1) % IM_ARRAYSIZE (values); - ImGui::PlotLines ( "Frametimes", + if (limiter != config.framerate.replace_limiter) + { + config.framerate.replace_limiter = limiter; + + if (config.framerate.replace_limiter) + tbf::FrameRateFix::BlipFramerate (); + else + tbf::FrameRateFix::DisengageLimiter (); + + float fZero = 0.0f; + memset (values, *(reinterpret_cast (&fZero)), sizeof (float) * 120); + values_offset = 0; + } + + float sum = 0.0f; + + float min = FLT_MAX; + float max = 0.0f; + + for (float val : values) { + sum += val; + + if (val > max) + max = val; + + if (val < min) + min = val; + } + + static char szAvg [512]; + + sprintf ( szAvg, + "Avg milliseconds per-frame: %6.3f (Target: %6.3f)\n" + " Extreme frametimes: %6.3f min, %6.3f max\n\n\n\n" + "Variation: %8.5f ms ==> %.1f FPS +/- %3.1f frames", + sum / 120.0f, tbf::FrameRateFix::GetTargetFrametime (), + min, max, max - min, + 1000.0f / (sum / 120.0f), (max - min) / (1000.0f / (sum / 120.0f)) ); + + ImGui::PlotLines ( "", values, IM_ARRAYSIZE (values), values_offset, - "Milliseconds per-frame", + szAvg, 0.0f, - 33.0f, + 2.0f * tbf::FrameRateFix::GetTargetFrametime (), ImVec2 (0, 80) ); - ImGui::Text ( "Application average %.3f ms/frame (%.1f FPS)", - 1000.0f / ImGui::GetIO ().Framerate, - ImGui::GetIO ().Framerate ); + ImGui::SameLine (); + + if (! config.framerate.replace_limiter) { + ImGui::TextColored ( ImVec4 (1.0f, 1.0f, 0.0f, 1.0f), + "\n" + "\n" + " ... working limiters do not resemble EKGs!" ); + } else { + ImGui::TextColored ( ImVec4 ( 0.2f, 1.0f, 0.2f, 1.0f), + "\n" + "\n" + "This is how a framerate limiter should work." ); + } + + //ImGui::Text ( "Application average %.3f ms/frame (%.1f FPS)", + //1000.0f / ImGui::GetIO ().Framerate, + //ImGui::GetIO ().Framerate ); } if (ImGui::CollapsingHeader ("Texture Options")) { - ImGui::Checkbox ("Dump Textures", &config.textures.dump); - ImGui::Checkbox ("Generate Mipmaps", &config.textures.remaster); + if (ImGui::Checkbox ("Dump Textures", &config.textures.dump)) tbf::RenderFix::need_reset.graphics = true; + if (ImGui::Checkbox ("Generate Mipmaps", &config.textures.remaster)) tbf::RenderFix::need_reset.graphics = true; if (ImGui::IsItemHovered ()) ImGui::SetTooltip ("Eliminates distant texture aliasing"); if (config.textures.remaster) { ImGui::SameLine (150); - ImGui::Checkbox ("(Uncompressed)", &config.textures.uncompressed); + + if (ImGui::Checkbox ("(Uncompressed)", &config.textures.uncompressed)) tbf::RenderFix::need_reset.graphics = true; if (ImGui::IsItemHovered ()) ImGui::SetTooltip ("Uses more VRAM, but avoids texture compression artifacts on generated mipmaps."); @@ -273,7 +342,8 @@ TBFix_DrawConfigUI (LPDIRECT3DDEVICE9 pDev = nullptr) if (scale > 3) scale = 3; - radio = scale; + radio = scale; + last_sel = radio; } int radio = 0; @@ -286,19 +356,37 @@ TBFix_DrawConfigUI (LPDIRECT3DDEVICE9 pDev = nullptr) ImGui::Combo ("Character Shadow Resolution", &shadows.radio, "Normal\0Enhanced\0High\0Ultra\0\0"); ImGui::Combo ("Environmental Shadow Resolution", &env_shadows.radio, "Normal\0High\0Ultra\0\0"); + ImGui::TextColored ( ImVec4 ( 0.999f, 0.01f, 0.999f, 1.0f), + " * Changes to these settings will produce weird results until you change Screen Mode in-game...\n" ); + if (env_shadows.radio != env_shadows.last_sel) { - config.render.env_shadow_rescale = env_shadows.radio; - env_shadows.last_sel = env_shadows.radio; + config.render.env_shadow_rescale = env_shadows.radio; + env_shadows.last_sel = env_shadows.radio; + tbf::RenderFix::need_reset.graphics = true; } if (shadows.radio != shadows.last_sel) { - config.render.shadow_rescale = -shadows.radio; - shadows.last_sel = shadows.radio; + config.render.shadow_rescale = -shadows.radio; + shadows.last_sel = shadows.radio; + tbf::RenderFix::need_reset.graphics = true; + } + } + + if (ImGui::CollapsingHeader ("Audio Configuration")) + { + ImGui::Checkbox ("Enable 7.1 Channel Audio Fix", &config.audio.enable_fix); + + if (config.audio.enable_fix) { + ImGui::RadioButton ("Stereo", (int *)&config.audio.channels, 2); + ImGui::RadioButton ("Quadraphonic", (int *)&config.audio.channels, 4); + ImGui::RadioButton ("5.1 Surround", (int *)&config.audio.channels, 6); } } if (ImGui::Button ("Gamepad Config")) - show_gamepad_config ^= 1; + ImGui::OpenPopup ("Gamepad Config"); + + TBFix_GamepadConfigDlg (); ImGui::SameLine (); @@ -308,18 +396,18 @@ TBFix_DrawConfigUI (LPDIRECT3DDEVICE9 pDev = nullptr) if ( ImGui::Checkbox ("Pause Game While This Menu Is Open", &config.input.ui.pause) ) TBFix_PauseGame (config.input.ui.pause); + ImGui::SameLine (500); + + if (ImGui::Selectable ("...", show_test_window)) + show_test_window ^= show_test_window; + ImGui::End (); - if (show_gamepad_config) + if (show_test_window) { - TBFix_GamepadConfigDlg (); - } - - //if (show_test_window) - //{ //ImGui::SetNextWindowPos (ImVec2 (650, 20), ImGuiSetCond_FirstUseEver); //ImGui::ShowTestWindow (&show_test_window); - //} + } // Rendering g_pd3dDevice->SetRenderState (D3DRS_ZENABLE, false); diff --git a/src/tbt/imgui_impl_dx9.cpp b/src/tbt/imgui_impl_dx9.cpp index f556aed..f56a3ce 100644 --- a/src/tbt/imgui_impl_dx9.cpp +++ b/src/tbt/imgui_impl_dx9.cpp @@ -15,6 +15,8 @@ #include #include "config.h" +#include "render.h" + // Data static HWND g_hWnd = 0; diff --git a/tbf.vcxproj b/tbf.vcxproj index c052fb4..ef5ea89 100644 --- a/tbf.vcxproj +++ b/tbf.vcxproj @@ -247,7 +247,7 @@ - + diff --git a/tbf.vcxproj.filters b/tbf.vcxproj.filters index bd7d8ae..96b49a1 100644 --- a/tbf.vcxproj.filters +++ b/tbf.vcxproj.filters @@ -55,7 +55,7 @@ Source Files\Tales of Berseria Tweak - + Source Files\Tales of Berseria Tweak diff --git a/version.ini b/version.ini index b798d14..b4ba947 100644 Binary files a/version.ini and b/version.ini differ