From b0ddecfede96ae8c106598f1a5b8a335e6311b54 Mon Sep 17 00:00:00 2001
From: pstef <3462925+pstef@users.noreply.github.com>
Date: Sat, 15 Aug 2026 19:53:08 +0200
Subject: [PATCH] EE/UI: Remove the FPU multiply gamefix
It patched one product, 0.25 * pi, from the correctly-rounded 0x3f490fdb
to the 0x3f490fda the EE's multiplier returns, so Tales of Destiny stops
hanging. That product is one sample of the multiplier's one-ULP deficit,
and eeClampMode 3 models the deficit for every operand pair -- including
the asymmetry the gamefix reproduced by comparing fs and ft against
their own constants, which falls out of a predicate that reads ft alone.
The seven serials carry the clamp mode instead, and iFPU-arm64.cpp's
multiply is a bare Fmul again.
The toggle goes from the Qt, FullscreenUI, Android and iOS front ends,
along with the GameDB schema enum and the harness helper that set it.
Fix_FpuMultiply and the config bit stay: vu_capture's on-disk gamefix
mask is bit-indexed by GamefixId, so dropping ordinal 0 would
reinterpret every capture already recorded. tbl_GamefixNames keeps the
name, so a GameDB that still lists it parses and does nothing.
---
bin/resources/GameIndex.yaml | 28 ++--
pcsx2-qt/Settings/GameFixSettingsWidget.cpp | 2 -
pcsx2-qt/Settings/GameFixSettingsWidget.ui | 8 --
pcsx2/Config.h | 5 +-
pcsx2/Docs/GameIndex.md | 4 -
pcsx2/Docs/gamedb-schema.json | 1 -
pcsx2/ImGui/FullscreenUI_Settings.cpp | 2 -
pcsx2/arm64/iFPU-arm64.cpp | 46 +------
.../android/app/src/main/assets/i18n/ar.json | 1 -
.../android/app/src/main/assets/i18n/de.json | 1 -
.../android/app/src/main/assets/i18n/es.json | 1 -
.../android/app/src/main/assets/i18n/fa.json | 1 -
.../android/app/src/main/assets/i18n/fr.json | 1 -
.../android/app/src/main/assets/i18n/id.json | 1 -
.../android/app/src/main/assets/i18n/it.json | 1 -
.../android/app/src/main/assets/i18n/ja.json | 1 -
.../android/app/src/main/assets/i18n/ko.json | 1 -
.../android/app/src/main/assets/i18n/ku.json | 1 -
.../android/app/src/main/assets/i18n/pl.json | 1 -
.../app/src/main/assets/i18n/pt-BR.json | 1 -
.../android/app/src/main/assets/i18n/ru.json | 1 -
.../android/app/src/main/assets/i18n/th.json | 1 -
.../android/app/src/main/assets/i18n/tr.json | 1 -
.../android/app/src/main/assets/i18n/uk.json | 1 -
.../android/app/src/main/assets/i18n/vi.json | 1 -
.../app/src/main/assets/i18n/zh-CN.json | 1 -
.../app/src/main/assets/i18n/zh-TW.json | 1 -
.../main/java/com/armsx2/config/Settings.kt | 8 --
.../app/src/main/java/com/armsx2/i18n/I18n.kt | 2 -
.../java/com/armsx2/ui/settings/FixesTab.kt | 1 -
.../ui/settingshub/SettingsResetFields.kt | 2 +-
.../ui/settingshub/SettingsSearchIndex.kt | 1 -
.../ios/app/src/main/cpp/ARMSX2Bridge.mm | 2 +-
.../Models/SettingsStore+GameFixes.swift | 1 -
.../core/recompilers/ee_rec_fpu_tests.cpp | 123 ++++++++----------
.../recompilers/harness/EeRecTestHarness.cpp | 13 --
.../recompilers/harness/EeRecTestHarness.h | 3 -
37 files changed, 80 insertions(+), 191 deletions(-)
diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml
index f38863f0b1..a3333f5843 100644
--- a/bin/resources/GameIndex.yaml
+++ b/bin/resources/GameIndex.yaml
@@ -2470,8 +2470,8 @@ SCAJ-20181:
SCAJ-20182:
name: "Tales of Destiny"
region: "NTSC-Unk"
- gameFixes:
- - FpuMulHack
+ clampModes:
+ eeClampMode: 3 # Hangs otherwise: it needs the EE multiplier's one-ULP deficit, which the single-precision tier does not model.
SCAJ-20183:
name: "ワイルドアームズ ザ フィフスヴァンガード"
name-sort: "わいるどあーむず ざ ふぃふすゔぁんがーど"
@@ -2517,8 +2517,8 @@ SCAJ-20192:
SCAJ-20193:
name: "Tales of Destiny [Director's Cut] [Premium Box]"
region: "NTSC-C-J"
- gameFixes:
- - FpuMulHack
+ clampModes:
+ eeClampMode: 3 # Hangs otherwise: it needs the EE multiplier's one-ULP deficit, which the single-precision tier does not model.
SCAJ-20194:
name: "Minna no Golf 4 [PlayStation2 the Best]"
region: "NTSC-Unk"
@@ -7967,8 +7967,8 @@ SCKA-20100:
name: "테일즈 오브 데스티니" # Undumped on ReDump as of 2025-08-28
name-en: "Tales of Destiny"
region: "NTSC-K"
- gameFixes:
- - FpuMulHack
+ clampModes:
+ eeClampMode: 3 # Hangs otherwise: it needs the EE multiplier's one-ULP deficit, which the single-precision tier does not model.
SCKA-20101:
name: "성검전설 4"
name-en: "Seiken Densetsu 4"
@@ -8069,8 +8069,8 @@ SCKA-20119:
name: "테일즈 오브 데스티니 디렉터즈컷 [Premium Box]" # Undumped on ReDump as of 2025-08-28
name-en: "Tales of Destiny - Director's Cut [Premium Box]"
region: "NTSC-K"
- gameFixes:
- - FpuMulHack
+ clampModes:
+ eeClampMode: 3 # Hangs otherwise: it needs the EE multiplier's one-ULP deficit, which the single-precision tier does not model.
SCKA-20120:
name: "라쳇 & 클랭크 - 공구들고 바캉스" # Undumped on ReDump as of 2025-08-28
name-en: "Ratchet & Clank - Vacation with Tools" # Ratchet & Clank - Size Matters
@@ -61771,8 +61771,8 @@ SLPS-25715:
name-sort: "ているず おぶ ですてぃにー"
name-en: "Tales of Destiny"
region: "NTSC-J"
- gameFixes:
- - FpuMulHack
+ clampModes:
+ eeClampMode: 3 # Hangs otherwise: it needs the EE multiplier's one-ULP deficit, which the single-precision tier does not model.
SLPS-25716:
name: "デジモンセイバーズ アナザーミッション"
name-sort: "でじもんせいばーず あなざーみっしょん"
@@ -62558,8 +62558,8 @@ SLPS-25841:
name-sort: "ているず おぶ ですてぃにー でぃれくたーずかっと [ぷれみあむBOX]"
name-en: "Tales of Destiny [Director's Cut] [Premium Box]"
region: "NTSC-J"
- gameFixes:
- - FpuMulHack
+ clampModes:
+ eeClampMode: 3 # Hangs otherwise: it needs the EE multiplier's one-ULP deficit, which the single-precision tier does not model.
memcardFilters: # Allows import of non-DC Tales of Destiny data.
- "SLPS-25841"
- "SLPS-25842"
@@ -62569,9 +62569,9 @@ SLPS-25842:
name-sort: "ているず おぶ ですてぃにー でぃれくたーずかっと"
name-en: "Tales of Destiny [Director's Cut]"
region: "NTSC-J"
+ clampModes:
+ eeClampMode: 3 # Hangs otherwise: it needs the EE multiplier's one-ULP deficit, which the single-precision tier does not model.
compat: 5
- gameFixes:
- - FpuMulHack
memcardFilters:
- "SLPS-25841"
- "SLPS-25842"
diff --git a/pcsx2-qt/Settings/GameFixSettingsWidget.cpp b/pcsx2-qt/Settings/GameFixSettingsWidget.cpp
index 899b5aa142..2796081764 100644
--- a/pcsx2-qt/Settings/GameFixSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/GameFixSettingsWidget.cpp
@@ -17,7 +17,6 @@ GameFixSettingsWidget::GameFixSettingsWidget(SettingsWindow* settings_dialog, QW
setupTab(m_ui);
- SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.FpuMulHack, "EmuCore/Gamefixes", "FpuMulHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.GoemonTlbHack, "EmuCore/Gamefixes", "GoemonTlbHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.SoftwareRendererFMVHack, "EmuCore/Gamefixes", "SoftwareRendererFMVHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.SkipMPEGHack, "EmuCore/Gamefixes", "SkipMPEGHack", false);
@@ -36,7 +35,6 @@ GameFixSettingsWidget::GameFixSettingsWidget(SettingsWindow* settings_dialog, QW
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.XgKickHack, "EmuCore/Gamefixes", "XgKickHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.BlitInternalFPSHack, "EmuCore/Gamefixes", "BlitInternalFPSHack", false);
- dialog()->registerWidgetHelp(m_ui.FpuMulHack, tr("FPU Multiply Hack"), tr("Unchecked"), tr("For Tales of Destiny."));
dialog()->registerWidgetHelp(m_ui.GoemonTlbHack, tr("Preload TLB Hack"), tr("Unchecked"), tr("To avoid TLB miss on Goemon."));
dialog()->registerWidgetHelp(m_ui.SoftwareRendererFMVHack, tr("Use Software Renderer For FMVs"), tr("Unchecked"), tr("Needed for some games with complex FMV rendering."));
dialog()->registerWidgetHelp(m_ui.SkipMPEGHack, tr("Skip MPEG Hack"), tr("Unchecked"), tr("Skips videos/FMVs in games to avoid game hanging/freezes."));
diff --git a/pcsx2-qt/Settings/GameFixSettingsWidget.ui b/pcsx2-qt/Settings/GameFixSettingsWidget.ui
index 393a90821b..1a3839c15e 100644
--- a/pcsx2-qt/Settings/GameFixSettingsWidget.ui
+++ b/pcsx2-qt/Settings/GameFixSettingsWidget.ui
@@ -17,13 +17,6 @@
Game Fixes
- -
-
-
- FPU Multiply Hack
-
-
-
-
@@ -162,7 +155,6 @@
- FpuMulHack
SoftwareRendererFMVHack
SkipMPEGHack
GoemonTlbHack
diff --git a/pcsx2/Config.h b/pcsx2/Config.h
index 60d589aba2..8592632f7c 100644
--- a/pcsx2/Config.h
+++ b/pcsx2/Config.h
@@ -1280,6 +1280,10 @@ struct Pcsx2Config
{
BITFIELD32()
bool
+ // No reader: eeMulRound (FPU.cpp) and emitDefectiveFmul
+ // (iFPUd-arm64.cpp) model the multiplier defect this patched one
+ // product of. The bit stays because its GamefixId indexes
+ // vu_capture's on-disk gamefix mask.
FpuMulHack : 1, // Tales of Destiny hangs.
GoemonTlbHack : 1, // Gomeon tlb miss hack. The game need to access unmapped virtual address. Instead to handle it as exception, tlb are preloaded at startup
SoftwareRendererFMVHack : 1, // Switches to software renderer for FMVs
@@ -1697,7 +1701,6 @@ namespace EmuFolders
//------------ SPECIAL GAME FIXES!!! ---------------
#define CHECK_VUADDSUBHACK (EmuConfig.Gamefixes.VuAddSubHack) // Special Fix for Tri-ace games, they use an encryption algorithm that requires VU addi opcode to be bit-accurate.
-#define CHECK_FPUMULHACK (EmuConfig.Gamefixes.FpuMulHack) // Special Fix for Tales of Destiny hangs.
#define CHECK_XGKICKHACK (EmuConfig.Gamefixes.XgKickHack) // Special Fix for Erementar Gerad, adds more delay to VU XGkick instructions. Corrects the color of some graphics.
#define CHECK_EETIMINGHACK (EmuConfig.Gamefixes.EETimingHack) // Fix all scheduled events to happen in 1 cycle.
#define CHECK_INSTANTDMAHACK (EmuConfig.Gamefixes.InstantDMAHack) // Attempt to finish DMA's instantly, useful for games which rely on cache emulation.
diff --git a/pcsx2/Docs/GameIndex.md b/pcsx2/Docs/GameIndex.md
index a572e7086e..cb152fc41d 100644
--- a/pcsx2/Docs/GameIndex.md
+++ b/pcsx2/Docs/GameIndex.md
@@ -26,7 +26,6 @@ SERIAL-12345: # !required! Serial number for the game, this is how games are loo
# If you'd like to temporarily disable it, either comment out the line, or remove it!
gameFixes:
- VuAddSubHack
- - FpuMulHack
- XGKickHack
- EETimingHack
- SkipMPEGHack
@@ -207,9 +206,6 @@ These values are case-sensitive, so take care. If you incorrectly specify a Gam
### Game Fixes Options
-* `FpuMulHack`
- * For Tales of Destiny: This fix addresses hanging issues.
-
* `SoftwareRendererFMVHack`
* Used for complex FMV rendering in certain games.
diff --git a/pcsx2/Docs/gamedb-schema.json b/pcsx2/Docs/gamedb-schema.json
index b1e1951feb..79a2fa222e 100644
--- a/pcsx2/Docs/gamedb-schema.json
+++ b/pcsx2/Docs/gamedb-schema.json
@@ -101,7 +101,6 @@
"BlitInternalFPSHack",
"DMABusyHack",
"EETimingHack",
- "FpuMulHack",
"GIFFIFOHack",
"GoemonTlbHack",
"IbitHack",
diff --git a/pcsx2/ImGui/FullscreenUI_Settings.cpp b/pcsx2/ImGui/FullscreenUI_Settings.cpp
index 87e45d66bd..8c1101aeec 100644
--- a/pcsx2/ImGui/FullscreenUI_Settings.cpp
+++ b/pcsx2/ImGui/FullscreenUI_Settings.cpp
@@ -5884,7 +5884,6 @@ void FullscreenUI::DrawGameFixesSettingsPage()
FSUI_CSTR("Game fixes should not be modified unless you are aware of what each option does and the implications of doing so."),
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
- DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_WRENCH, "FPU Multiply Hack"), FSUI_CSTR("For Tales of Destiny."), "EmuCore/Gamefixes", "FpuMulHack", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_MICROCHIP, "Use Software Renderer For FMVs"),
FSUI_CSTR("Needed for some games with complex FMV rendering."), "EmuCore/Gamefixes", "SoftwareRendererFMVHack", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FORWARD_FAST, "Skip MPEG Hack"), FSUI_CSTR("Skips videos/FMVs in games to avoid game hanging/freezes."),
@@ -6338,7 +6337,6 @@ TRANSLATE_NOOP("FullscreenUI", "Activating game patches can cause unpredictable
TRANSLATE_NOOP("FullscreenUI", "Use patches at your own risk, the ARMSX2 team will provide no support for users who have enabled game patches.");
TRANSLATE_NOOP("FullscreenUI", "Game Fixes");
TRANSLATE_NOOP("FullscreenUI", "Game fixes should not be modified unless you are aware of what each option does and the implications of doing so.");
-TRANSLATE_NOOP("FullscreenUI", "For Tales of Destiny.");
TRANSLATE_NOOP("FullscreenUI", "Needed for some games with complex FMV rendering.");
TRANSLATE_NOOP("FullscreenUI", "Skips videos/FMVs in games to avoid game hanging/freezes.");
TRANSLATE_NOOP("FullscreenUI", "To avoid TLB miss on Goemon.");
diff --git a/pcsx2/arm64/iFPU-arm64.cpp b/pcsx2/arm64/iFPU-arm64.cpp
index b6115839a8..2717c15855 100644
--- a/pcsx2/arm64/iFPU-arm64.cpp
+++ b/pcsx2/arm64/iFPU-arm64.cpp
@@ -619,51 +619,13 @@ static void fpuEmitGuardedAddSub(const a64::VRegister& dst,
_freeNEONreg(tmp);
}
-// FpuMulHack (Tales of Destiny Remake gamefix, EmuConfig.Gamefixes.FpuMulHack).
-// x86 routes every FPU multiply (MUL/MULA/MADD/MSUB) through FPU_MUL, which —
-// when the gamefix is on — patches the single specific product 0.25 * π
-// (0x3e800000 * 0x40490fdb) from the correctly-rounded 0x3f490fdb to 0x3f490fda
-// so the game stops hanging in one late-game room. Emit
-// `dst = (hit) ? 0x3f490fda : s*t`; callers clamp/accumulate dst as they normally
-// would (the magic value is an ordinary small float, so a following
-// fpuClampResult is a no-op). In the default config (gamefix off) this is a bare
-// Fmul — zero added cost.
-//
-// The patched value is not arbitrary: 0x3f490fda is π/4 one ULP low, which is
-// what the EE's multiplier actually returns. Its Booth recoding drops one ULP
-// when ft's significand has an odd digit pair (ft & 0x2AA) and the exact product
-// has no tail below the single ULP — here fs = 2^-2, so the product is exact and
-// the deficit reaches the result. The general model reproduces this pair (and
-// leaves the swapped operand order alone, exactly as the check below does).
-// It is NOT generalized here: this path has no exact product to test a tail
-// against, so it would need one built out of single-precision pieces on every
-// multiply in every game, against 1 instruction today. Its home is
-// emitDefectiveFmul (iFPUd-arm64.cpp), where the double product is already
-// there and the tail is 29 bits of it — extending it to this path needs its own
-// measured case.
+// The EE multiplier's one-ULP deficit is not modelled here: it needs the exact
+// product's tail below the single's ULP, which a single-precision multiply has
+// already discarded. It lives at emitDefectiveFmul (iFPUd-arm64.cpp), where the
+// product is a double and the tail is 29 bits of it.
static void emitFpuMul(const a64::VRegister& dst, const a64::VRegister& s, const a64::VRegister& t)
{
- if (!CHECK_FPUMULHACK)
- {
- armAsm->Fmul(dst, s, t);
- return;
- }
-
- a64::Label noHack, done;
- armAsm->Fmov(RWARG1, s);
- armAsm->Fmov(RWARG2, t);
- armAsm->Mov(RWSCRATCH, 0x3e800000);
- armAsm->Cmp(RWARG1, RWSCRATCH);
- armAsm->B(&noHack, a64::ne);
- armAsm->Mov(RWSCRATCH, 0x40490fdb);
- armAsm->Cmp(RWARG2, RWSCRATCH);
- armAsm->B(&noHack, a64::ne);
- armAsm->Mov(RWSCRATCH, 0x3f490fda);
- armAsm->Fmov(dst, RWSCRATCH);
- armAsm->B(&done);
- armAsm->Bind(&noHack);
armAsm->Fmul(dst, s, t);
- armAsm->Bind(&done);
}
//------------------------------------------------------------------
diff --git a/platforms/android/app/src/main/assets/i18n/ar.json b/platforms/android/app/src/main/assets/i18n/ar.json
index ef2b0ec744..614b40c6ba 100644
--- a/platforms/android/app/src/main/assets/i18n/ar.json
+++ b/platforms/android/app/src/main/assets/i18n/ar.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/de.json b/platforms/android/app/src/main/assets/i18n/de.json
index 884e191acb..0a88526886 100644
--- a/platforms/android/app/src/main/assets/i18n/de.json
+++ b/platforms/android/app/src/main/assets/i18n/de.json
@@ -564,7 +564,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB-Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/es.json b/platforms/android/app/src/main/assets/i18n/es.json
index 387820ed35..e1a0ae6367 100644
--- a/platforms/android/app/src/main/assets/i18n/es.json
+++ b/platforms/android/app/src/main/assets/i18n/es.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/fa.json b/platforms/android/app/src/main/assets/i18n/fa.json
index f2c52f4afa..4391776439 100644
--- a/platforms/android/app/src/main/assets/i18n/fa.json
+++ b/platforms/android/app/src/main/assets/i18n/fa.json
@@ -579,7 +579,6 @@
"perf.fix.eeTiming": "زمان بندی EE",
"perf.fix.extraXgkick": "XGKICK اضافی",
"perf.fix.fmvSoftware": "نرم افزار FMV",
- "perf.fix.fpuMultiply": "ضرب کردن FPU",
"perf.fix.fullVu0Sync": "همگام سازی کامل VU0",
"perf.fix.gamedbFixes": "رفع GameDB",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/fr.json b/platforms/android/app/src/main/assets/i18n/fr.json
index f49295e2b9..081ecca695 100644
--- a/platforms/android/app/src/main/assets/i18n/fr.json
+++ b/platforms/android/app/src/main/assets/i18n/fr.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/id.json b/platforms/android/app/src/main/assets/i18n/id.json
index 76715964bc..a59fc3cbb4 100644
--- a/platforms/android/app/src/main/assets/i18n/id.json
+++ b/platforms/android/app/src/main/assets/i18n/id.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/it.json b/platforms/android/app/src/main/assets/i18n/it.json
index d0224fc991..982eb5eeaf 100644
--- a/platforms/android/app/src/main/assets/i18n/it.json
+++ b/platforms/android/app/src/main/assets/i18n/it.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "Correzioni GameDB",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/ja.json b/platforms/android/app/src/main/assets/i18n/ja.json
index 48fc6688c2..1806bbd138 100644
--- a/platforms/android/app/src/main/assets/i18n/ja.json
+++ b/platforms/android/app/src/main/assets/i18n/ja.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/ko.json b/platforms/android/app/src/main/assets/i18n/ko.json
index b2926515b3..ba4ab88047 100644
--- a/platforms/android/app/src/main/assets/i18n/ko.json
+++ b/platforms/android/app/src/main/assets/i18n/ko.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/ku.json b/platforms/android/app/src/main/assets/i18n/ku.json
index a89c9eb3aa..032060c850 100644
--- a/platforms/android/app/src/main/assets/i18n/ku.json
+++ b/platforms/android/app/src/main/assets/i18n/ku.json
@@ -565,7 +565,6 @@
"perf.fix.eeTiming": "EE Timeming",
"perf.fix.extraXgkick": "XGKICK Zêdeyî",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Tevahiya VU0 Sync",
"perf.fix.gamedbFixes": "GameDB rast dike",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/pl.json b/platforms/android/app/src/main/assets/i18n/pl.json
index 11133ae82d..60e36ab54a 100644
--- a/platforms/android/app/src/main/assets/i18n/pl.json
+++ b/platforms/android/app/src/main/assets/i18n/pl.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/pt-BR.json b/platforms/android/app/src/main/assets/i18n/pt-BR.json
index b516acf613..5089650e55 100644
--- a/platforms/android/app/src/main/assets/i18n/pt-BR.json
+++ b/platforms/android/app/src/main/assets/i18n/pt-BR.json
@@ -511,7 +511,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "Correções GameDB",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/ru.json b/platforms/android/app/src/main/assets/i18n/ru.json
index 53c08b67e2..449b1b97da 100644
--- a/platforms/android/app/src/main/assets/i18n/ru.json
+++ b/platforms/android/app/src/main/assets/i18n/ru.json
@@ -565,7 +565,6 @@
"perf.fix.eeTiming": "ЭЭ Тайминг",
"perf.fix.extraXgkick": "Дополнительный XGKICK",
"perf.fix.fmvSoftware": "Программное обеспечение FMV",
- "perf.fix.fpuMultiply": "Умножение ФПУ",
"perf.fix.fullVu0Sync": "Полная синхронизация VU0",
"perf.fix.gamedbFixes": "Исправления GameDB",
"perf.fix.gifFifo": "Гиф ФИФО",
diff --git a/platforms/android/app/src/main/assets/i18n/th.json b/platforms/android/app/src/main/assets/i18n/th.json
index 0dd4e503f5..a7b89308af 100644
--- a/platforms/android/app/src/main/assets/i18n/th.json
+++ b/platforms/android/app/src/main/assets/i18n/th.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/tr.json b/platforms/android/app/src/main/assets/i18n/tr.json
index 39eaf1c50f..404cd522aa 100644
--- a/platforms/android/app/src/main/assets/i18n/tr.json
+++ b/platforms/android/app/src/main/assets/i18n/tr.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/uk.json b/platforms/android/app/src/main/assets/i18n/uk.json
index c6cd0b7797..305e0fb7ad 100644
--- a/platforms/android/app/src/main/assets/i18n/uk.json
+++ b/platforms/android/app/src/main/assets/i18n/uk.json
@@ -565,7 +565,6 @@
"perf.fix.eeTiming": "EE Таймінг",
"perf.fix.extraXgkick": "Додатковий XGKICK",
"perf.fix.fmvSoftware": "Програмне забезпечення FMV",
- "perf.fix.fpuMultiply": "Множення FPU",
"perf.fix.fullVu0Sync": "Повна синхронізація VU0",
"perf.fix.gamedbFixes": "Виправлення GameDB",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/vi.json b/platforms/android/app/src/main/assets/i18n/vi.json
index 8747e63677..942d5b5ee5 100644
--- a/platforms/android/app/src/main/assets/i18n/vi.json
+++ b/platforms/android/app/src/main/assets/i18n/vi.json
@@ -512,7 +512,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/zh-CN.json b/platforms/android/app/src/main/assets/i18n/zh-CN.json
index 7f6aaa63f9..faf8e18dd9 100644
--- a/platforms/android/app/src/main/assets/i18n/zh-CN.json
+++ b/platforms/android/app/src/main/assets/i18n/zh-CN.json
@@ -514,7 +514,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/assets/i18n/zh-TW.json b/platforms/android/app/src/main/assets/i18n/zh-TW.json
index 4e1855898b..342559199a 100644
--- a/platforms/android/app/src/main/assets/i18n/zh-TW.json
+++ b/platforms/android/app/src/main/assets/i18n/zh-TW.json
@@ -514,7 +514,6 @@
"perf.fix.eeTiming": "EE Timing",
"perf.fix.extraXgkick": "Extra XGKICK",
"perf.fix.fmvSoftware": "FMV Software",
- "perf.fix.fpuMultiply": "FPU Multiply",
"perf.fix.fullVu0Sync": "Full VU0 Sync",
"perf.fix.gamedbFixes": "GameDB Fixes",
"perf.fix.gifFifo": "GIF FIFO",
diff --git a/platforms/android/app/src/main/java/com/armsx2/config/Settings.kt b/platforms/android/app/src/main/java/com/armsx2/config/Settings.kt
index 00997209ec..218bdf00b1 100644
--- a/platforms/android/app/src/main/java/com/armsx2/config/Settings.kt
+++ b/platforms/android/app/src/main/java/com/armsx2/config/Settings.kt
@@ -207,8 +207,6 @@ data class Settings(
val gamefixInstantDma: Boolean = false,
/** EmuCore/Gamefixes/BlitInternalFPSHack. */
val gamefixBlitInternalFps: Boolean = false,
- /** EmuCore/Gamefixes/FpuMulHack — Tales of Destiny. */
- val gamefixFpuMul: Boolean = false,
/** EmuCore/Gamefixes/OPHFlagHack — Bleach Blade Battlers. */
val gamefixOphFlag: Boolean = false,
/** EmuCore/Gamefixes/GIFFIFOHack — emulate the GIF FIFO (Test Drive Unlimited). */
@@ -821,7 +819,6 @@ data class Settings(
put("EmuCore/Gamefixes", "EETimingHack", "bool", gamefixEETiming.toString())
put("EmuCore/Gamefixes", "InstantDMAHack", "bool", gamefixInstantDma.toString())
put("EmuCore/Gamefixes", "BlitInternalFPSHack", "bool", gamefixBlitInternalFps.toString())
- put("EmuCore/Gamefixes", "FpuMulHack", "bool", gamefixFpuMul.toString())
put("EmuCore/Gamefixes", "OPHFlagHack", "bool", gamefixOphFlag.toString())
put("EmuCore/Gamefixes", "GIFFIFOHack", "bool", gamefixGifFifo.toString())
put("EmuCore/Gamefixes", "DMABusyHack", "bool", gamefixDmaBusy.toString())
@@ -1039,7 +1036,6 @@ data class Settings(
gamefixEETiming = boolAt("EmuCore/Gamefixes/EETimingHack") ?: this.gamefixEETiming,
gamefixInstantDma = boolAt("EmuCore/Gamefixes/InstantDMAHack") ?: this.gamefixInstantDma,
gamefixBlitInternalFps = boolAt("EmuCore/Gamefixes/BlitInternalFPSHack") ?: this.gamefixBlitInternalFps,
- gamefixFpuMul = boolAt("EmuCore/Gamefixes/FpuMulHack") ?: this.gamefixFpuMul,
gamefixOphFlag = boolAt("EmuCore/Gamefixes/OPHFlagHack") ?: this.gamefixOphFlag,
gamefixGifFifo = boolAt("EmuCore/Gamefixes/GIFFIFOHack") ?: this.gamefixGifFifo,
gamefixDmaBusy = boolAt("EmuCore/Gamefixes/DMABusyHack") ?: this.gamefixDmaBusy,
@@ -1631,7 +1627,6 @@ data class Settings(
put("gamefixEETiming", gamefixEETiming)
put("gamefixInstantDma", gamefixInstantDma)
put("gamefixBlitInternalFps", gamefixBlitInternalFps)
- put("gamefixFpuMul", gamefixFpuMul)
put("gamefixOphFlag", gamefixOphFlag)
put("gamefixGifFifo", gamefixGifFifo)
put("gamefixDmaBusy", gamefixDmaBusy)
@@ -1894,7 +1889,6 @@ data class Settings(
gamefixEETiming = json.optBoolean("gamefixEETiming", def.gamefixEETiming),
gamefixInstantDma = json.optBoolean("gamefixInstantDma", def.gamefixInstantDma),
gamefixBlitInternalFps = json.optBoolean("gamefixBlitInternalFps", def.gamefixBlitInternalFps),
- gamefixFpuMul = json.optBoolean("gamefixFpuMul", def.gamefixFpuMul),
gamefixOphFlag = json.optBoolean("gamefixOphFlag", def.gamefixOphFlag),
gamefixGifFifo = json.optBoolean("gamefixGifFifo", def.gamefixGifFifo),
gamefixDmaBusy = json.optBoolean("gamefixDmaBusy", def.gamefixDmaBusy),
@@ -2141,7 +2135,6 @@ data class Settings(
if (current.gamefixEETiming != base.gamefixEETiming) j.put("gamefixEETiming", current.gamefixEETiming)
if (current.gamefixInstantDma != base.gamefixInstantDma) j.put("gamefixInstantDma", current.gamefixInstantDma)
if (current.gamefixBlitInternalFps != base.gamefixBlitInternalFps) j.put("gamefixBlitInternalFps", current.gamefixBlitInternalFps)
- if (current.gamefixFpuMul != base.gamefixFpuMul) j.put("gamefixFpuMul", current.gamefixFpuMul)
if (current.gamefixOphFlag != base.gamefixOphFlag) j.put("gamefixOphFlag", current.gamefixOphFlag)
if (current.gamefixGifFifo != base.gamefixGifFifo) j.put("gamefixGifFifo", current.gamefixGifFifo)
if (current.gamefixDmaBusy != base.gamefixDmaBusy) j.put("gamefixDmaBusy", current.gamefixDmaBusy)
@@ -2376,7 +2369,6 @@ data class Settings(
gamefixEETiming = if (overrides.has("gamefixEETiming")) overrides.getBoolean("gamefixEETiming") else base.gamefixEETiming,
gamefixInstantDma = if (overrides.has("gamefixInstantDma")) overrides.getBoolean("gamefixInstantDma") else base.gamefixInstantDma,
gamefixBlitInternalFps = if (overrides.has("gamefixBlitInternalFps")) overrides.getBoolean("gamefixBlitInternalFps") else base.gamefixBlitInternalFps,
- gamefixFpuMul = if (overrides.has("gamefixFpuMul")) overrides.getBoolean("gamefixFpuMul") else base.gamefixFpuMul,
gamefixOphFlag = if (overrides.has("gamefixOphFlag")) overrides.getBoolean("gamefixOphFlag") else base.gamefixOphFlag,
gamefixGifFifo = if (overrides.has("gamefixGifFifo")) overrides.getBoolean("gamefixGifFifo") else base.gamefixGifFifo,
gamefixDmaBusy = if (overrides.has("gamefixDmaBusy")) overrides.getBoolean("gamefixDmaBusy") else base.gamefixDmaBusy,
diff --git a/platforms/android/app/src/main/java/com/armsx2/i18n/I18n.kt b/platforms/android/app/src/main/java/com/armsx2/i18n/I18n.kt
index 6a1d228e93..52bc605c58 100644
--- a/platforms/android/app/src/main/java/com/armsx2/i18n/I18n.kt
+++ b/platforms/android/app/src/main/java/com/armsx2/i18n/I18n.kt
@@ -1048,7 +1048,6 @@ val EN: Map = mapOf(
"perf.fix.eeTiming" to "EE Timing",
"perf.fix.extraXgkick" to "Extra XGKICK",
"perf.fix.fmvSoftware" to "FMV Software",
- "perf.fix.fpuMultiply" to "FPU Multiply",
"perf.fix.fullVu0Sync" to "Full VU0 Sync",
"perf.fix.compatPatches" to "Compatibility Patches",
"perf.fix.compatPatches.desc" to "Applies the per-game compatibility patches bundled with ARMSX2 — the fixes some games need to boot or render correctly. Same idea as GameDB Fixes above, and safe to leave on. Does NOT enable widescreen or cheats; those live in the Patches screen. Takes effect on the next boot.",
@@ -1073,7 +1072,6 @@ val EN: Map = mapOf(
"perf.fix.eeTiming.desc" to "Tweaks EE timing for the handful of games sensitive to it (e.g. Digital Devil Saga, SSX On Tour). Off by default.",
"perf.fix.instantDma.desc" to "Completes certain DMA transfers instantly, fixing missing text or graphics in games like Fire Pro Wrestling Returns.",
"perf.fix.blitFps.desc" to "Corrects the internal FPS reading so in-game and emulated frame counters are accurate in games that mis-report it.",
- "perf.fix.fpuMultiply.desc" to "Uses a more accurate FPU multiply, fixing games that rely on exact float math (e.g. Tales of Destiny).",
"perf.fix.ophFlag.desc" to "Emulates the VU0 OPH flag, fixing hangs or missing graphics in Bleach Blade Battlers and some Tri-Ace games.",
"perf.fix.gifFifo.desc" to "Emulates the GIF FIFO accurately, fixing graphical glitches in games like FIFA Street 2 and Hot Wheels.",
"perf.fix.dmaBusy.desc" to "Delays the VIF1 DMA busy flag, fixing hangs in games such as Mana Khemia and Metal Saga.",
diff --git a/platforms/android/app/src/main/java/com/armsx2/ui/settings/FixesTab.kt b/platforms/android/app/src/main/java/com/armsx2/ui/settings/FixesTab.kt
index 183797eb30..f042b00e76 100644
--- a/platforms/android/app/src/main/java/com/armsx2/ui/settings/FixesTab.kt
+++ b/platforms/android/app/src/main/java/com/armsx2/ui/settings/FixesTab.kt
@@ -473,7 +473,6 @@ fun FixesTab(state: MutableState) {
ToggleRow(str("perf.fix.eeTiming"), s.gamefixEETiming, description = str("perf.fix.eeTiming.desc")) { apply(s.copy(enableGameFixes = true, gamefixEETiming = it)) }
ToggleRow(str("perf.fix.instantDma"), s.gamefixInstantDma, description = str("perf.fix.instantDma.desc")) { apply(s.copy(enableGameFixes = true, gamefixInstantDma = it)) }
ToggleRow(str("perf.fix.blitFps"), s.gamefixBlitInternalFps, description = str("perf.fix.blitFps.desc")) { apply(s.copy(enableGameFixes = true, gamefixBlitInternalFps = it)) }
- ToggleRow(str("perf.fix.fpuMultiply"), s.gamefixFpuMul, description = str("perf.fix.fpuMultiply.desc")) { apply(s.copy(enableGameFixes = true, gamefixFpuMul = it)) }
ToggleRow(str("perf.fix.ophFlag"), s.gamefixOphFlag, description = str("perf.fix.ophFlag.desc")) { apply(s.copy(enableGameFixes = true, gamefixOphFlag = it)) }
ToggleRow(str("perf.fix.gifFifo"), s.gamefixGifFifo, description = str("perf.fix.gifFifo.desc")) { apply(s.copy(enableGameFixes = true, gamefixGifFifo = it)) }
ToggleRow(str("perf.fix.dmaBusy"), s.gamefixDmaBusy, description = str("perf.fix.dmaBusy.desc")) { apply(s.copy(enableGameFixes = true, gamefixDmaBusy = it)) }
diff --git a/platforms/android/app/src/main/java/com/armsx2/ui/settingshub/SettingsResetFields.kt b/platforms/android/app/src/main/java/com/armsx2/ui/settingshub/SettingsResetFields.kt
index edde443fe4..beafa19d00 100644
--- a/platforms/android/app/src/main/java/com/armsx2/ui/settingshub/SettingsResetFields.kt
+++ b/platforms/android/app/src/main/java/com/armsx2/ui/settingshub/SettingsResetFields.kt
@@ -69,7 +69,7 @@ internal val SETTINGS_CATEGORY_FIELDS: Map> = map
// from Performance and from the retired Recompiler tab.
SettingsCategory.Advanced to listOf(
"enableFastBoot", "enableGameFixes", "pineEnabled", "pineSlot",
- "gamefixBlitInternalFps", "gamefixDmaBusy", "gamefixEETiming", "gamefixFpuMul",
+ "gamefixBlitInternalFps", "gamefixDmaBusy", "gamefixEETiming",
"gamefixFullVu0Sync", "gamefixGifFifo", "gamefixGoemonTlb", "gamefixIbit",
"gamefixInstantDma", "gamefixOphFlag", "gamefixSkipMpeg",
"gamefixSoftwareRendererFmv", "gamefixVif1Stall", "gamefixVuAddSub",
diff --git a/platforms/android/app/src/main/java/com/armsx2/ui/settingshub/SettingsSearchIndex.kt b/platforms/android/app/src/main/java/com/armsx2/ui/settingshub/SettingsSearchIndex.kt
index fe746150ce..53cc002f30 100644
--- a/platforms/android/app/src/main/java/com/armsx2/ui/settingshub/SettingsSearchIndex.kt
+++ b/platforms/android/app/src/main/java/com/armsx2/ui/settingshub/SettingsSearchIndex.kt
@@ -34,7 +34,6 @@ internal val SETTINGS_SEARCH_INDEX: List = listOf(
SettingsSearchEntry("perf.fix.eeTiming", true, SettingsCategory.Advanced),
SettingsSearchEntry("perf.fix.instantDma", true, SettingsCategory.Advanced),
SettingsSearchEntry("perf.fix.blitFps", true, SettingsCategory.Advanced),
- SettingsSearchEntry("perf.fix.fpuMultiply", true, SettingsCategory.Advanced),
SettingsSearchEntry("perf.fix.ophFlag", true, SettingsCategory.Advanced),
SettingsSearchEntry("perf.fix.gifFifo", true, SettingsCategory.Advanced),
SettingsSearchEntry("perf.fix.dmaBusy", true, SettingsCategory.Advanced),
diff --git a/platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm b/platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm
index 012bb0f725..fb53d8ffb0 100644
--- a/platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm
+++ b/platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm
@@ -1945,7 +1945,7 @@ static void ARMSX2ApplyPerGameSettingsOverrides(NSMutableDictionary* perGameFixes = [NSMutableDictionary dictionary];
static constexpr const char* kARMSX2GameFixKeys[] = {
- "VuAddSubHack", "FpuMulHack", "XgKickHack", "EETimingHack", "InstantDMAHack",
+ "VuAddSubHack", "XgKickHack", "EETimingHack", "InstantDMAHack",
"SoftwareRendererFMVHack", "SkipMPEGHack", "OPHFlagHack", "DMABusyHack",
"VIF1StallHack", "GIFFIFOHack", "GoemonTlbHack", "IbitHack", "VUSyncHack",
"VUOverflowHack", "BlitInternalFPSHack", "FullVU0SyncHack"
diff --git a/platforms/ios/app/src/main/swift/Models/SettingsStore+GameFixes.swift b/platforms/ios/app/src/main/swift/Models/SettingsStore+GameFixes.swift
index ffc59e6d42..3c78c6da3e 100644
--- a/platforms/ios/app/src/main/swift/Models/SettingsStore+GameFixes.swift
+++ b/platforms/ios/app/src/main/swift/Models/SettingsStore+GameFixes.swift
@@ -7,7 +7,6 @@ extension SettingsStore {
/// Manual EmuCore/Gamefixes toggles, in display order.
static let gameFixOptions: [GameFixOption] = [
.init(key: "VuAddSubHack", label: "VU Add-Sub Hack"),
- .init(key: "FpuMulHack", label: "FPU Multiply Hack"),
.init(key: "XgKickHack", label: "Extra XGKICK Hack"),
.init(key: "EETimingHack", label: "EE Timing Hack"),
.init(key: "InstantDMAHack", label: "Instant DMA Hack"),
diff --git a/tests/ctest/core/recompilers/ee_rec_fpu_tests.cpp b/tests/ctest/core/recompilers/ee_rec_fpu_tests.cpp
index dc5ef5a3d4..10e92118a3 100644
--- a/tests/ctest/core/recompilers/ee_rec_fpu_tests.cpp
+++ b/tests/ctest/core/recompilers/ee_rec_fpu_tests.cpp
@@ -1375,70 +1375,58 @@ TEST(EeRecFpu, DivSAfterAddSReadsLiveOperands)
h.ExpectFpr(4, FloatBits(5.0f));
}
-// ---- FpuMulHack (Tales of Destiny Remake gamefix) --------------------------
-// JIT-only: the interpreter MUL_S has no hack, so a hack-hit legitimately
-// diverges from interp — assert GetFprBitsJit() under RunJitNoDiff(). The hack
-// patches exactly 0.25 * (π) (0x3e800000 * 0x40490fdb) to 0x3f490fda. The
-// shared emitFpuMul helper wires it into all of MUL/MULA/MADD/MSUB/MADDA/MSUBA.
-
-TEST(EeRecFpu, MulSFpuMulHackPatchesMagicProduct)
+// ---- The FpuMulHack pair (Tales of Destiny Remake gamefix) -----------------
+// Upstream's FpuMulHack patches one product, 0.25 * (pi), from the
+// correctly-rounded 0x3F490FDB to 0x3F490FDA. Measured on SCPH-90000
+// (captures/fpmul/): 0x3E800000 * 0x40490FDB returns 0x3F490FDA, and the same
+// two words reversed return 0x3F490FDB. fs = 2^-2 makes the product zero-tailed
+// and ft fires the Booth predicate, so this is one sample of the multiplier's
+// own deficit, and the gamefix's compare against two fixed constants is why it
+// did not fire reversed.
+//
+// Both orders on every engine: what replaces the gamefix has to reproduce the
+// asymmetry as well as the value. The single-precision tier carries no cut of
+// the law, so modes 1 and 2 are pinned at the IEEE product.
+TEST(EeRecFpu, MulHackPairIsTheMultiplierDeficitFromModeThree)
{
- EeRecTestHarness h;
- h.EnableCop1();
- h.EnableFpuMulHack();
- h.SetFprBits(0, 0x3e800000u);
- h.SetFprBits(1, 0x40490fdbu);
- h.LoadProgram({ee::MUL_S(2, 0, 1)});
- h.RunJitNoDiff();
- EXPECT_EQ(h.GetFprBitsJit(2), 0x3f490fdau);
-}
-
-TEST(EeRecFpu, MulSFpuMulHackOffStillReachesTheConsoleValueOnInterp)
-{
- // This test used to assert the opposite -- that with the gamefix off the
- // "ordinary" product comes out, and that JIT and interp agree on it. Its
- // premise was that the IEEE product is the console's. It is not.
- //
- // Measured on SCPH-90000 (captures/fpmul/): mul.s of 0x3E800000 by
- // 0x40490FDB returns 0x3F490FDA, and the same two words in the reverse
- // operand order return 0x3F490FDB. FpuMulHack is a one-point sample of the
- // multiplier's own one-ULP deficit, not a game kludge -- which is why it
- // compares fs and ft against their own constants and so does not fire
- // reversed, exactly as the console behaves.
- //
- // The interpreter models the deficit itself (eeMulRound in FPU.cpp), so it
- // lands on the console value with the gamefix off. The single-precision fast
- // path does not, so the two legitimately diverge here and this cannot be a
- // Run() diff -- and RunJitNoDiff() never runs the interpreter at all, so
- // each engine needs its own harness.
- EeRecTestHarness hi;
- hi.EnableCop1();
- hi.SetFprBits(0, 0x3e800000u);
- hi.SetFprBits(1, 0x40490fdbu);
- hi.LoadProgram({ee::MUL_S(2, 0, 1)});
- hi.RunInterpOnly();
- EXPECT_EQ(hi.GetFprBitsInterp(2), 0x3f490fdau) << "interp should reach silicon unaided";
-
- // Reversed: the console returns the un-decremented product, because the
- // predicate reads ft alone. This is the half that pins it as an operand
- // order effect rather than a constant fudge.
- EeRecTestHarness hr;
- hr.EnableCop1();
- hr.SetFprBits(0, 0x40490fdbu);
- hr.SetFprBits(1, 0x3e800000u);
- hr.LoadProgram({ee::MUL_S(2, 0, 1)});
- hr.RunInterpOnly();
- EXPECT_EQ(hr.GetFprBitsInterp(2), 0x3f490fdbu) << "reversed operands: no deficit";
-
- // The fast path, gamefix off, still produces the IEEE product. Recorded so
- // the divergence is pinned rather than discovered later as a surprise.
- EeRecTestHarness hj;
- hj.EnableCop1();
- hj.SetFprBits(0, 0x3e800000u);
- hj.SetFprBits(1, 0x40490fdbu);
- hj.LoadProgram({ee::MUL_S(2, 0, 1)});
- hj.RunJitNoDiff();
- EXPECT_EQ(hj.GetFprBitsJit(2), 0x3f490fdbu);
+ struct Leg { u32 fs, ft, want, want_fast; const char* what; };
+ static const Leg legs[] = {
+ {0x3e800000u, 0x40490fdbu, 0x3f490fdau, 0x3f490fdbu, "0.25 * pi"},
+ {0x40490fdbu, 0x3e800000u, 0x3f490fdbu, 0x3f490fdbu, "reversed: predicate off"},
+ };
+ auto run = [](const Leg& l, int mode, bool interp) {
+ EeRecTestHarness h;
+ h.EnableCop1();
+ if (mode >= 4)
+ h.EnableFpuExactMode();
+ else if (mode >= 3)
+ h.EnableFpuFullMode();
+ else if (mode >= 2)
+ h.EnableFpuExtraOverflow();
+ h.SetFprBits(0, l.fs);
+ h.SetFprBits(1, l.ft);
+ h.LoadProgram({ee::MUL_S(2, 0, 1)});
+ if (interp)
+ {
+ h.RunInterpOnly();
+ return h.GetFprBitsInterp(2);
+ }
+ h.RunJitNoDiff();
+ return h.GetFprBitsJit(2);
+ };
+ for (const Leg& l : legs)
+ {
+ SCOPED_TRACE(l.what);
+ EXPECT_EQ(run(l, 1, true), l.want) << "interp";
+ EXPECT_EQ(run(l, 1, false), l.want_fast) << "eeClampMode 1, the fast path";
+ EXPECT_EQ(run(l, 2, false), l.want_fast) << "eeClampMode 2, still the fast path";
+ EXPECT_EQ(run(l, 3, false), l.want) << "eeClampMode 3";
+ EXPECT_EQ(run(l, 4, false), l.want) << "eeClampMode 4";
+ }
+ // Liveness: the two orders have to disagree, or neither the asymmetry nor
+ // the fast path's gap is being tested.
+ ASSERT_NE(legs[0].want, legs[1].want);
+ ASSERT_NE(legs[0].want, legs[0].want_fast);
}
TEST(EeRecFpu, MulSMultiplierDeficitMatchesSilicon)
@@ -1588,13 +1576,14 @@ TEST(EeRecFpu, MulSMultiplierDeficitReachesResultsWithANonZeroTail)
<< "the table stopped covering the band the array call exists for";
}
-TEST(EeRecFpu, MaddSFpuMulHackAppliesToProduct)
+TEST(EeRecFpu, MaddSReachesTheMulHackProductFromModeThree)
{
- // MADD routes its multiply through the same helper: ACC=0 + hack(Fs*Ft) ->
- // the patched product. Proves the family-wide wiring, not just MUL_S.
+ // MADD's multiply stage is a separate emit site from MUL's, so the pair
+ // above is checked through it too: ACC = +0 leaves MADD landing on the
+ // product alone.
EeRecTestHarness h;
h.EnableCop1();
- h.EnableFpuMulHack();
+ h.EnableFpuFullMode();
h.SetAccBits(0x00000000u); // +0
h.SetFprBits(0, 0x3e800000u);
h.SetFprBits(1, 0x40490fdbu);
diff --git a/tests/ctest/core/recompilers/harness/EeRecTestHarness.cpp b/tests/ctest/core/recompilers/harness/EeRecTestHarness.cpp
index eea7d127dc..2d4621ed5e 100644
--- a/tests/ctest/core/recompilers/harness/EeRecTestHarness.cpp
+++ b/tests/ctest/core/recompilers/harness/EeRecTestHarness.cpp
@@ -110,9 +110,6 @@ EeRecTestHarness::~EeRecTestHarness()
if (fpu_full_mode_changed_)
EmuConfig.Cpu.Recompiler.fpuFullMode = prev_fpu_full_mode_;
- if (fpu_mul_hack_changed_)
- EmuConfig.Gamefixes.FpuMulHack = prev_fpu_mul_hack_;
-
if (fpu_guarded_changed_)
EmuConfig.Cpu.Recompiler.fpuGuardedAddSub = prev_fpu_guarded_;
@@ -184,16 +181,6 @@ void EeRecTestHarness::EnableFpuExactMode()
EmuConfig.Cpu.Recompiler.fpuExactMode = true;
}
-void EeRecTestHarness::EnableFpuMulHack()
-{
- if (!fpu_mul_hack_changed_)
- {
- prev_fpu_mul_hack_ = EmuConfig.Gamefixes.FpuMulHack;
- fpu_mul_hack_changed_ = true;
- }
- EmuConfig.Gamefixes.FpuMulHack = true;
-}
-
void EeRecTestHarness::DisableFpuGuarded()
{
if (!fpu_guarded_changed_)
diff --git a/tests/ctest/core/recompilers/harness/EeRecTestHarness.h b/tests/ctest/core/recompilers/harness/EeRecTestHarness.h
index 57fcfd7c70..837dda47b8 100644
--- a/tests/ctest/core/recompilers/harness/EeRecTestHarness.h
+++ b/tests/ctest/core/recompilers/harness/EeRecTestHarness.h
@@ -72,7 +72,6 @@ public:
// rest of the EE multiplier's one-ULP deficit. Implies EnableFpuFullMode();
// restored in the dtor.
void EnableFpuExactMode();
- void EnableFpuMulHack();
// Turns ON the (default-OFF) fpuExtraOverflow Recompiler option — GameDB
// eeClampMode >= 2, CHECK_FPU_EXTRA_OVERFLOW — so the JIT clamps each fpr
@@ -388,8 +387,6 @@ private:
bool prev_fpu_full_mode_ = false;
bool fpu_exact_mode_changed_ = false;
bool prev_fpu_exact_mode_ = false;
- bool fpu_mul_hack_changed_ = false;
- bool prev_fpu_mul_hack_ = false;
bool fpu_guarded_changed_ = false;
bool prev_fpu_guarded_ = false;
bool fpu_extra_overflow_changed_ = false;