Compare commits

...

4 Commits

Author SHA1 Message Date
TheTechnician27 f015bd96fa
Merge 15fe65c964 into 4a57bd7fd4 2025-01-17 23:36:28 +00:00
Ty 4a57bd7fd4 VMManager / vuJIT: Fix save state loading and saving on ARM64 2025-01-17 18:00:07 -05:00
shockdude fbe0c8b9cc USB: Fix DJ Hero Turntable automatic mapping & turntable multiplier 2025-01-17 10:44:52 -05:00
TheTechnician27 15fe65c964 Settings: Allow user to manually set RTC per-game 2025-01-17 07:22:33 -06:00
10 changed files with 263 additions and 23 deletions

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
@ -12,6 +12,7 @@
#include <QtWidgets/QAbstractButton>
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDateTimeEdit>
#include <QtWidgets/QDoubleSpinBox>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QLabel>
@ -656,6 +657,26 @@ namespace SettingWidgetBinder
}
};
template <>
struct SettingAccessor<QDateTimeEdit>
{
static int getYear(const QDateTimeEdit* widget) { return widget->date().year(); }
static int getMonth(const QDateTimeEdit* widget) { return widget->date().month(); }
static int getDay(const QDateTimeEdit* widget) { return widget->date().day(); }
static int getHour(const QDateTimeEdit* widget) { return widget->time().hour(); }
static int getMinute(const QDateTimeEdit* widget) { return widget->time().minute(); }
static int getSecond(const QDateTimeEdit* widget) { return widget->time().second(); }
static void setDateTime(QDateTimeEdit* widget, const QDate date, const QTime time) { widget->setDateTime(QDateTime(date, time)); }
template <typename F>
static void connectValueChanged(QDateTimeEdit* widget, F func)
{
widget->connect(widget, &QDateTimeEdit::dateTimeChanged, func);
}
};
/// Binds a widget's value to a setting, updating it when the value changes.
template <typename WidgetType>
@ -1239,4 +1260,106 @@ namespace SettingWidgetBinder
widget->connect(widget, &QLineEdit::editingFinished, widget, std::move(value_changed));
}
// No need to pass a section or key since this is only used once and has six keys associated with it
static inline void BindWidgetToDateTimeSetting(SettingsInterface* sif, QDateTimeEdit* widget)
{
using Accessor = SettingAccessor<QDateTimeEdit>;
int YEAR_OFFSET = 2000;
int DEFAULT_YEAR = 0;
int DEFAULT_MONTH = 1;
int DEFAULT_DAY = 1;
int DEFAULT_HOUR = 0;
int DEFAULT_MINUTE = 0;
int DEFAULT_SECOND = 0;
std::string SECTION = "EmuCore";
std::string YEAR_KEY = "RtcYear";
std::string MONTH_KEY = "RtcMonth";
std::string DAY_KEY = "RtcDay";
std::string HOUR_KEY = "RtcHour";
std::string MINUTE_KEY = "RtcMinute";
std::string SECOND_KEY = "RtcSecond";
// Fetch settings from .ini
const s32 year_value =
Host::GetBaseIntSettingValue(SECTION.c_str(), YEAR_KEY.c_str(), static_cast<s32>(DEFAULT_YEAR));
const s32 month_value =
Host::GetBaseIntSettingValue(SECTION.c_str(), MONTH_KEY.c_str(), static_cast<s32>(DEFAULT_MONTH));
const s32 day_value =
Host::GetBaseIntSettingValue(SECTION.c_str(), DAY_KEY.c_str(), static_cast<s32>(DEFAULT_DAY));
const s32 hour_value =
Host::GetBaseIntSettingValue(SECTION.c_str(), HOUR_KEY.c_str(), static_cast<s32>(DEFAULT_HOUR));
const s32 minute_value =
Host::GetBaseIntSettingValue(SECTION.c_str(), MINUTE_KEY.c_str(), static_cast<s32>(DEFAULT_MINUTE));
const s32 second_value =
Host::GetBaseIntSettingValue(SECTION.c_str(), SECOND_KEY.c_str(), static_cast<s32>(DEFAULT_SECOND));
if (sif)
{
int sif_year_value = DEFAULT_YEAR;
int sif_month_value = DEFAULT_MONTH;
int sif_day_value = DEFAULT_DAY;
int sif_hour_value = DEFAULT_HOUR;
int sif_minute_value = DEFAULT_MINUTE;
int sif_second_value = DEFAULT_SECOND;
// Get Settings Interface values or default if that fails
if (!sif->GetIntValue(SECTION.c_str(), YEAR_KEY.c_str(), &sif_year_value)) { sif_year_value = DEFAULT_YEAR; }
if (!sif->GetIntValue(SECTION.c_str(), MONTH_KEY.c_str(), &sif_month_value)) { sif_month_value = DEFAULT_MONTH; }
if (!sif->GetIntValue(SECTION.c_str(), DAY_KEY.c_str(), &sif_day_value)) { sif_day_value = DEFAULT_DAY; }
if (!sif->GetIntValue(SECTION.c_str(), HOUR_KEY.c_str(), &sif_hour_value)) { sif_hour_value = DEFAULT_HOUR; }
if (!sif->GetIntValue(SECTION.c_str(), MINUTE_KEY.c_str(), &sif_minute_value)) { sif_minute_value = DEFAULT_MINUTE; }
if (!sif->GetIntValue(SECTION.c_str(), SECOND_KEY.c_str(), &sif_second_value)) { sif_second_value = DEFAULT_SECOND; }
// No need to check for valid date since QDateTime resets to minimum upon becoming invalid
Accessor::setDateTime(widget, QDate(static_cast<int>(sif_year_value + YEAR_OFFSET), static_cast<int>(sif_month_value), static_cast<int>(sif_day_value)),
QTime(static_cast<int>(sif_hour_value), static_cast<int>(sif_minute_value), static_cast<int>(sif_second_value)));
// Update the settings interface and reload the game settings when changed
Accessor::connectValueChanged(widget, [sif, widget, SECTION = std::move(SECTION), YEAR_KEY = std::move(YEAR_KEY), MONTH_KEY = std::move(MONTH_KEY),
DAY_KEY = std::move(DAY_KEY), HOUR_KEY = std::move(HOUR_KEY), MINUTE_KEY = std::move(MINUTE_KEY), SECOND_KEY = std::move(SECOND_KEY), YEAR_OFFSET = std::move(YEAR_OFFSET)]() {
sif->SetIntValue(SECTION.c_str(), YEAR_KEY.c_str(), Accessor::getYear(widget) - YEAR_OFFSET);
sif->SetIntValue(SECTION.c_str(), MONTH_KEY.c_str(), Accessor::getMonth(widget));
sif->SetIntValue(SECTION.c_str(), DAY_KEY.c_str(), Accessor::getDay(widget));
sif->SetIntValue(SECTION.c_str(), HOUR_KEY.c_str(), Accessor::getHour(widget));
sif->SetIntValue(SECTION.c_str(), MINUTE_KEY.c_str(), Accessor::getMinute(widget));
sif->SetIntValue(SECTION.c_str(), SECOND_KEY.c_str(), Accessor::getSecond(widget));
QtHost::SaveGameSettings(sif, true);
g_emu_thread->reloadGameSettings();
});
}
else
{
// No need to check for valid date since QDateTime resets to minimum upon becoming invalid
Accessor::setDateTime(widget, QDate(static_cast<int>(year_value + YEAR_OFFSET), static_cast<int>(month_value), static_cast<int>(day_value)),
QTime(static_cast<int>(hour_value), static_cast<int>(minute_value), static_cast<int>(second_value)));
// Update and apply base settings with values from widget when user changes it in UI
Accessor::connectValueChanged(widget, [widget, SECTION = std::move(SECTION), YEAR_KEY = std::move(YEAR_KEY), MONTH_KEY = std::move(MONTH_KEY),
DAY_KEY = std::move(DAY_KEY), HOUR_KEY = std::move(HOUR_KEY), MINUTE_KEY = std::move(MINUTE_KEY), SECOND_KEY = std::move(SECOND_KEY), YEAR_OFFSET = std::move(YEAR_OFFSET)]() {
const int new_year_value = Accessor::getYear(widget);
const int new_month_value = Accessor::getMonth(widget);
const int new_day_value = Accessor::getDay(widget);
const int new_hour_value = Accessor::getHour(widget);
const int new_minute_value = Accessor::getMinute(widget);
const int new_second_value = Accessor::getSecond(widget);
Host::SetBaseIntSettingValue(SECTION.c_str(), YEAR_KEY.c_str(), new_year_value - YEAR_OFFSET);
Host::SetBaseIntSettingValue(SECTION.c_str(), MONTH_KEY.c_str(), new_month_value);
Host::SetBaseIntSettingValue(SECTION.c_str(), DAY_KEY.c_str(), new_day_value);
Host::SetBaseIntSettingValue(SECTION.c_str(), HOUR_KEY.c_str(), new_hour_value);
Host::SetBaseIntSettingValue(SECTION.c_str(), MINUTE_KEY.c_str(), new_minute_value);
Host::SetBaseIntSettingValue(SECTION.c_str(), SECOND_KEY.c_str(), new_second_value);
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
});
}
}
} // namespace SettingWidgetBinder

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include <QtWidgets/QInputDialog>
@ -48,6 +48,12 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
if (m_dialog->isPerGameSettings())
{
SettingWidgetBinder::BindWidgetToDateTimeSetting(sif, m_ui.rtcDateTime);
m_ui.rtcDateTime->setDateRange(QDate(2000, 1, 1), QDate(2099, 12, 31));
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.manuallySetRealTimeClock, "EmuCore", "ManuallySetRealTimeClock", false);
connect(m_ui.manuallySetRealTimeClock, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::onManuallySetRealTimeClockChanged);
EmulationSettingsWidget::onManuallySetRealTimeClockChanged();
m_ui.eeCycleRate->insertItem(
0, tr("Use Global Setting [%1]")
.arg(m_ui.eeCycleRate->itemText(
@ -74,6 +80,8 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
}
else
{
m_ui.rtcGroup->hide();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cheats, "EmuCore", "EnableCheats", false);
// Allow for FastCDVD for per-game settings only
@ -146,6 +154,11 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
dialog->registerWidgetHelp(m_ui.useVSyncForTiming, tr("Use Host VSync Timing"), tr("Unchecked"),
tr("When synchronizing with the host refresh rate, this option disable's PCSX2's internal frame timing, and uses the host instead. "
"Can result in smoother frame pacing, <strong>but at the cost of increased input latency</strong>."));
dialog->registerWidgetHelp(m_ui.manuallySetRealTimeClock, tr("Manually Set Real-Time Clock"), tr("Unchecked"),
tr("Manually set a real-time clock to use for the virtual PlayStation 2 instead of using your OS' system clock."));
dialog->registerWidgetHelp(m_ui.rtcDateTime, tr("Real-Time Clock"), tr("Current date and time"),
tr("Real-time clock (RTC) used by the virtual PlayStation 2. NOTE: This assumes you have your PS2 set to the default timezone of GMT+0 and default DST of Summer Time. "
"Some games require an RTC date/time set after their release date."));
updateOptimalFramePacing();
updateUseVSyncForTimingEnabled();
@ -292,3 +305,9 @@ void EmulationSettingsWidget::updateUseVSyncForTimingEnabled()
const bool sync_to_host_refresh = m_dialog->getEffectiveBoolValue("EmuCore/GS", "SyncToHostRefreshRate", false);
m_ui.useVSyncForTiming->setEnabled(vsync && sync_to_host_refresh);
}
void EmulationSettingsWidget::onManuallySetRealTimeClockChanged()
{
const bool enabled = m_dialog->getEffectiveBoolValue("EmuCore", "ManuallySetRealTimeClock", false);
m_ui.rtcDateTime->setEnabled(enabled);
}

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
@ -25,6 +25,7 @@ private:
void handleSpeedComboChange(QComboBox* cb, const char* section, const char* key);
void updateOptimalFramePacing();
void updateUseVSyncForTimingEnabled();
void onManuallySetRealTimeClockChanged();
SettingsWindow* m_dialog;

View File

@ -24,7 +24,7 @@
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_5">
<widget class="QGroupBox" name="speedGroup">
<property name="title">
<string>Speed Control</string>
</property>
@ -195,7 +195,7 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="basicGroupBox">
<widget class="QGroupBox" name="pacingGroup">
<property name="title">
<string>Frame Pacing / Latency Control</string>
</property>
@ -268,6 +268,26 @@
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="rtcGroup">
<property name="title">
<string>Real-Time Clock</string>
</property>
<layout class="QGridLayout" name="gridLayoutRTC">
<item row="0" column="0">
<widget class="QCheckBox" name="manuallySetRealTimeClock">
<property name="text">
<string>Manually Set Real-Time Clock</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDateTimeEdit" name="rtcDateTime">
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources>

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "CDVD/CDVD.h"
@ -28,6 +28,9 @@
#include <cctype>
#include <ctime>
#ifndef _WIN32
#include <time.h>
#endif
#include <memory>
cdvdStruct cdvd;
@ -917,9 +920,38 @@ void cdvdReset()
cdvd.ReadTime = cdvdBlockReadTime(MODE_DVDROM);
cdvd.RotSpeed = cdvdRotationTime(MODE_DVDROM);
if (EmuConfig.ManuallySetRealTimeClock)
{
// Convert to GMT+9 (assumes GMT+0)
std::tm tm{};
tm.tm_sec = EmuConfig.RtcSecond;
tm.tm_min = EmuConfig.RtcMinute;
tm.tm_hour = EmuConfig.RtcHour;
tm.tm_mday = EmuConfig.RtcDay;
tm.tm_mon = EmuConfig.RtcMonth - 1;
tm.tm_year = EmuConfig.RtcYear + 100; // 2000 - 1900
tm.tm_isdst = 1;
// Need this instead of mktime for timezone independence
std::time_t t = 0;
#if defined(_WIN32)
t = _mkgmtime(&tm) + 32400; //60 * 60 * 9 for GMT+9
gmtime_s(&tm, &t);
#else
t = timegm(&tm) + 32400;
gmtime_r(&t, &tm);
#endif
cdvd.RTC.second = tm.tm_sec;
cdvd.RTC.minute = tm.tm_min;
cdvd.RTC.hour = tm.tm_hour;
cdvd.RTC.day = tm.tm_mday;
cdvd.RTC.month = tm.tm_mon + 1;
cdvd.RTC.year = tm.tm_year - 100;
}
// If we are recording, always use the same RTC setting
// for games that use the RTC to seed their RNG -- this is very important to be the same everytime!
if (g_InputRecording.isActive())
else if (g_InputRecording.isActive())
{
Console.WriteLn("Input Recording Active - Using Constant RTC of 04-03-2020 (DD-MM-YYYY)");
// Why not just 0 everything? Some games apparently require the date to be valid in terms of when

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
@ -1282,6 +1282,7 @@ struct Pcsx2Config
InhibitScreensaver : 1,
BackupSavestate : 1,
McdFolderAutoManage : 1,
ManuallySetRealTimeClock : 1,
HostFs : 1,
@ -1315,6 +1316,13 @@ struct Pcsx2Config
int PINESlot;
int RtcYear = 0;
int RtcMonth = 1;
int RtcDay = 1;
int RtcHour = 0;
int RtcMinute = 0;
int RtcSecond = 0;
// Set at runtime, not loaded from config.
std::string CurrentBlockdump;
std::string CurrentIRX;

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "common/CocoaTools.h"
@ -1898,6 +1898,7 @@ Pcsx2Config::Pcsx2Config()
InhibitScreensaver = true;
BackupSavestate = true;
WarnAboutUnsafeSettings = true;
ManuallySetRealTimeClock = false;
// To be moved to FileMemoryCard pluign (someday)
for (uint slot = 0; slot < 8; ++slot)
@ -1910,6 +1911,12 @@ Pcsx2Config::Pcsx2Config()
GzipIsoIndexTemplate = "$(f).pindex.tmp";
PINESlot = 28011;
RtcYear = 0;
RtcMonth = 1;
RtcDay = 1;
RtcHour = 0;
RtcMinute = 0;
RtcSecond = 0;
}
void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap)
@ -1940,6 +1947,8 @@ void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap)
SettingsWrapBitBool(WarnAboutUnsafeSettings);
SettingsWrapBitBool(ManuallySetRealTimeClock);
// Process various sub-components:
Speedhacks.LoadSave(wrap);
@ -1959,6 +1968,12 @@ void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap)
SettingsWrapEntry(GzipIsoIndexTemplate);
SettingsWrapEntry(PINESlot);
SettingsWrapEntry(RtcYear);
SettingsWrapEntry(RtcMonth);
SettingsWrapEntry(RtcDay);
SettingsWrapEntry(RtcHour);
SettingsWrapEntry(RtcMinute);
SettingsWrapEntry(RtcSecond);
// For now, this in the derived config for backwards ini compatibility.
SettingsWrapEntryEx(CurrentBlockdump, "BlockDumpSaveDirectory");

View File

@ -93,19 +93,19 @@ namespace usb_pad
u8 right_turntable = 0x80;
if (data.left_turntable_ccw > 0)
{
left_turntable -= static_cast<u8>(std::min<int>(data.left_turntable_ccw * turntable_multiplier, 0x7F));
left_turntable -= static_cast<u8>(std::min<int>(data.left_turntable_ccw, 0x7F));
}
else
{
left_turntable += static_cast<u8>(std::min<int>(data.left_turntable_cw * turntable_multiplier, 0x7F));
left_turntable += static_cast<u8>(std::min<int>(data.left_turntable_cw, 0x7F));
}
if (data.right_turntable_ccw > 0)
{
right_turntable -= static_cast<u8>(std::min<int>(data.right_turntable_ccw * turntable_multiplier, 0x7F));
right_turntable -= static_cast<u8>(std::min<int>(data.right_turntable_ccw, 0x7F));
}
else
{
right_turntable += static_cast<u8>(std::min<int>(data.right_turntable_cw * turntable_multiplier, 0x7F));
right_turntable += static_cast<u8>(std::min<int>(data.right_turntable_cw, 0x7F));
}
buf[3] = 0x80;
buf[4] = 0x80;
@ -369,19 +369,19 @@ namespace usb_pad
break;
case CID_DJ_LEFT_TURNTABLE_CW:
s->data.left_turntable_cw = static_cast<u32>(std::clamp<long>(std::lroundf(value * 128.0f), 0, 128));
s->data.left_turntable_cw = static_cast<u32>(std::clamp<long>(std::lroundf(value * s->turntable_multiplier * 128.0f), 0, 128));
break;
case CID_DJ_LEFT_TURNTABLE_CCW:
s->data.left_turntable_ccw = static_cast<u32>(std::clamp<long>(std::lroundf(value * 128.0f), 0, 128));
s->data.left_turntable_ccw = static_cast<u32>(std::clamp<long>(std::lroundf(value * s->turntable_multiplier * 128.0f), 0, 128));
break;
case CID_DJ_RIGHT_TURNTABLE_CW:
s->data.right_turntable_cw = static_cast<u32>(std::clamp<long>(std::lroundf(value * 128.0f), 0, 128));
s->data.right_turntable_cw = static_cast<u32>(std::clamp<long>(std::lroundf(value * s->turntable_multiplier * 128.0f), 0, 128));
break;
case CID_DJ_RIGHT_TURNTABLE_CCW:
s->data.right_turntable_ccw = static_cast<u32>(std::clamp<long>(std::lroundf(value * 128.0f), 0, 128));
s->data.right_turntable_ccw = static_cast<u32>(std::clamp<long>(std::lroundf(value * s->turntable_multiplier * 128.0f), 0, 128));
break;
case CID_DJ_DPAD_UP:
@ -446,8 +446,8 @@ namespace usb_pad
{"EffectsKnobRight", TRANSLATE_NOOP("USB", "Effects Knob Right"), nullptr, InputBindingInfo::Type::HalfAxis, CID_DJ_EFFECTSKNOB_RIGHT, GenericInputBinding::RightStickRight},
{"LeftTurntableCW", TRANSLATE_NOOP("USB", "Left Turntable Clockwise"), nullptr, InputBindingInfo::Type::HalfAxis, CID_DJ_LEFT_TURNTABLE_CW, GenericInputBinding::LeftStickRight},
{"LeftTurntableCCW", TRANSLATE_NOOP("USB", "Left Turntable Counterclockwise"), nullptr, InputBindingInfo::Type::HalfAxis, CID_DJ_LEFT_TURNTABLE_CCW, GenericInputBinding::LeftStickLeft},
{"RightTurntableCW", TRANSLATE_NOOP("USB", "Right Turntable Clockwise"), nullptr, InputBindingInfo::Type::HalfAxis, CID_DJ_RIGHT_TURNTABLE_CW, GenericInputBinding::LeftStickDown},
{"RightTurntableCCW", TRANSLATE_NOOP("USB", "Right Turntable Counterclockwise"), nullptr, InputBindingInfo::Type::HalfAxis, CID_DJ_RIGHT_TURNTABLE_CCW, GenericInputBinding::LeftStickUp},
{"RightTurntableCW", TRANSLATE_NOOP("USB", "Right Turntable Clockwise"), nullptr, InputBindingInfo::Type::HalfAxis, CID_DJ_RIGHT_TURNTABLE_CW, GenericInputBinding::LeftStickUp},
{"RightTurntableCCW", TRANSLATE_NOOP("USB", "Right Turntable Counterclockwise"), nullptr, InputBindingInfo::Type::HalfAxis, CID_DJ_RIGHT_TURNTABLE_CCW, GenericInputBinding::LeftStickDown},
{"LeftTurntableGreen", TRANSLATE_NOOP("USB", "Left Turntable Green"), nullptr, InputBindingInfo::Type::Button, CID_DJ_LEFT_GREEN, GenericInputBinding::Unknown},
{"LeftTurntableRed", TRANSLATE_NOOP("USB", "Left Turntable Red"), nullptr, InputBindingInfo::Type::Button, CID_DJ_LEFT_RED, GenericInputBinding::Unknown},
{"LeftTurntableBlue", TRANSLATE_NOOP("USB", "Left Turntable Blue"), nullptr, InputBindingInfo::Type::Button, CID_DJ_LEFT_BLUE, GenericInputBinding::Unknown},
@ -464,8 +464,8 @@ namespace usb_pad
{
static constexpr const SettingInfo info[] = {
{SettingInfo::Type::Float, "TurntableMultiplier", TRANSLATE_NOOP("USB", "Turntable Multiplier"),
TRANSLATE_NOOP("USB", "Apply a multiplier to the turntable"),
"1.00", "0.00", "100.0", "1.0", "%.0fx", nullptr, nullptr, 1.0f}};
TRANSLATE_NOOP("USB", "Apply a sensitivity multiplier to turntable rotation.\nXbox 360 turntables require a 256x multiplier, most other turntables can use the default 1x multiplier."),
"1.00", "0.00", "512.0", "1.0", "%.0fx", nullptr, nullptr, 1.0f}};
return info;
}

View File

@ -2561,6 +2561,11 @@ void VMManager::InitializeCPUProviders()
CpuMicroVU0.Reserve();
CpuMicroVU1.Reserve();
#else
// Despite not having any VU recompilers on ARM64, therefore no MTVU,
// we still need the thread alive. Otherwise the read and write positions
// of the ring buffer wont match, and various systems in the emulator end up deadlocked.
vu1Thread.Open();
#endif
VifUnpackSSE_Init();
@ -2580,6 +2585,11 @@ void VMManager::ShutdownCPUProviders()
psxRec.Shutdown();
recCpu.Shutdown();
#else
// See the comment in the InitializeCPUProviders for an explaination why we
// still need to manage the MTVU thread.
if(vu1Thread.IsOpen())
vu1Thread.WaitVU();
#endif
}

View File

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0
#include "common/Console.h"
#include "MTVU.h"
#include "SaveState.h"
#include "vtlb.h"
@ -13,6 +15,16 @@ void vtlb_DynBackpatchLoadStore(uptr code_address, u32 code_size, u32 guest_pc,
bool SaveStateBase::vuJITFreeze()
{
pxFailRel("Not implemented.");
return false;
if(IsSaving())
vu1Thread.WaitVU();
Console.Warning("recompiler state is stubbed in arm64!");
// HACK!!
// size of microRegInfo structure
std::array<u8,96> empty_data{};
Freeze(empty_data);
Freeze(empty_data);
return true;
}