Compare commits

...

18 Commits

Author SHA1 Message Date
JMC47
e5fbc74156 Merge pull request #13082 from Dentomologist/generalpane_hardcore_mode_tooltip_clarifications
GeneralPane: Add Hardcore Mode tooltip clarifications
2025-09-28 14:10:06 -04:00
JMC47
e1c7734ee4 Merge pull request #13260 from JosJuice/android-gcadapter-hotplug-callback
Android: Detect GCAdapter hotplug using BroadcastReceiver
2025-09-28 14:03:50 -04:00
JMC47
cbdb7ac38e Merge pull request #12329 from Dentomologist/balloontip_fix_premature_close_on_balloontip_hover
BalloonTip: Don't hide when the BalloonTip blocks the cursor
2025-09-28 14:03:16 -04:00
JMC47
79a98b8235 Merge pull request #13161 from oltolm/fix_qt6_warnings
dolphinQt: fix Qt6 deprecation warnings
2025-09-28 14:02:56 -04:00
JMC47
a93754cb71 Merge pull request #13245 from JosJuice/mbp-condition-flush
Jit: Flush registers used in memory breakpoint conditions
2025-09-28 14:02:29 -04:00
JMC47
72397ccd87 Merge pull request #13909 from Dentomologist/codewidgets_show_code_approval_in_hardcore_mode
CodeWidgets: Show code approval in Hardcore mode
2025-09-28 14:01:59 -04:00
Dentomologist
44f6743a5b CodeWidgets: Show code approval in Hardcore mode
When Hardcore mode is enabled, show an icon for each code in
ARCodeWidget and GeckoCodeWidget indicating whether it's an approved
code or not.
2025-08-25 12:31:38 -07:00
Dentomologist
7b52555a5f BalloonTip: Don't hide when BalloonTip blocks the cursor
Keep the BalloonTip open when the BalloonTip's arrow prevents the cursor
from being inside the spawning ToolTipWidget, which triggers the
ToolTipWidget's leaveEvent and would previously close the BalloonTip.

When that happens track the cursor until it either leaves the
ToolTipWidget's bounding box or leaves the BalloonTip and goes back to
the ToolTipWidget, and respectively close the BalloonTip or leave it
open.
2025-08-01 12:30:05 -07:00
JosJuice
8524e725a8 Android: Add additional null check in GCAdapter
Just in case, since the documentation says this could be null.
2025-07-27 11:37:45 +02:00
JosJuice
1c7df370d9 Revert "Android/GCAdapter: Don't join current thread"
This reverts commit 74ed5e5532.
It solves a problem that no longer exists.
2025-07-27 11:21:04 +02:00
JosJuice
185569778c Android: Detect GCAdapter disconnection using BroadcastReceiver
This lets us get rid of the Reset call in ProcessInputPayload, which was
causing us some threading headaches (see 74ed5e5532). Instead we handle
disconnection in the same way as the libusb implementation does.
2025-07-27 11:21:04 +02:00
JosJuice
e2e33becc9 Android: Detect GCAdapter connection using BroadcastReceiver
We can register a BroadcastReceiver to have Android tell us when a GC
adapter gets connected instead of having a loop where we continuously
call SleepCurrentThread(1000) and poll the current status. When waiting
for a GC adapter to connect, this both reduces power usage and improves
responsiveness.

Note that I made openAdapter get the UsbDevice that's been stored by the
hotplug code instead of having openAdapter find the UsbDevice on its own
like before. This is only because I want to ensure that the UsbDevice
being tracked for disconnection is the same as the UsbDevice actually
being used, in case the user has multiple adapters connected.
2025-07-27 11:21:04 +02:00
JosJuice
7508842859 Android: Clean up naming in Java_GCAdapter and Java_WiimoteAdapter
This isn't how we name things in Java/Kotlin.
2025-07-27 11:13:44 +02:00
JosJuice
02e32ffaaf Jit: Flush registers used in memory breakpoint conditions
Aims to fix https://bugs.dolphin-emu.org/issues/13686.

I'm using a less efficient approach for Jit64 than for JitArm64. In
JitArm64, I'm flushing in the slow access code, but in Jit64, I'm
flushing before the split between the slow access code and the fast
access code, because Jit64 doesn't keep register mappings around for
when it's time to emit the slow access code. But the flushing code is
only emitted when there are memory breakpoints with conditions, so I'd
say that this is a performance loss we can live with.
2025-07-27 08:49:41 +02:00
JosJuice
a06bc5417d JitArm64: Document an assumption we've been making in EmitBackpatchRoutine
The next commit will add another piece of code that depends on this
assumption that we've been making. Good opportunity to document it.

In practice, all callers of EmitBackpatchRoutine are locking X30.
2025-07-27 08:32:44 +02:00
JosJuice
78afea1312 PowerPC: Track registers used in memory breakpoint conditions 2025-07-27 08:32:44 +02:00
oltolm
49c72efcd3 fix Qt6 deprecation warnings 2025-07-19 22:08:15 +02:00
Dentomologist
55cfb958c7 GeneralPane: Add Hardcore Mode tooltip clarification
When Hardcore Mode is active, clarify in the Speed Limit tooltip that
values less than 100% won't slow emulation.
2025-05-08 15:45:15 -07:00
44 changed files with 886 additions and 344 deletions

View File

@@ -9,8 +9,8 @@ import android.hardware.usb.UsbManager;
import org.dolphinemu.dolphinemu.utils.ActivityTracker;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
import org.dolphinemu.dolphinemu.utils.GCAdapter;
import org.dolphinemu.dolphinemu.utils.WiimoteAdapter;
import org.dolphinemu.dolphinemu.utils.VolleyUtil;
public class DolphinApplication extends Application
@@ -28,8 +28,8 @@ public class DolphinApplication extends Application
VolleyUtil.init(getApplicationContext());
System.loadLibrary("main");
Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (DirectoryInitialization.shouldStart(getApplicationContext()))
DirectoryInitialization.start(getApplicationContext());

View File

@@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.widget.Toast;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import org.dolphinemu.dolphinemu.BuildConfig;
import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.R;
import java.util.HashMap;
import java.util.Map;
public class GCAdapter
{
public static UsbManager manager;
@Keep
static byte[] controllerPayload = new byte[37];
static UsbDeviceConnection usbConnection;
static UsbInterface usbInterface;
static UsbEndpoint usbIn;
static UsbEndpoint usbOut;
private static final String ACTION_GC_ADAPTER_PERMISSION_GRANTED =
BuildConfig.APPLICATION_ID + ".GC_ADAPTER_PERMISSION_GRANTED";
private static final Object hotplugCallbackLock = new Object();
private static boolean hotplugCallbackEnabled = false;
private static UsbDevice adapterDevice = null;
private static BroadcastReceiver hotplugBroadcastReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
onUsbDevicesChanged();
}
};
private static void requestPermission()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
{
UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
{
if (!manager.hasPermission(dev))
{
Context context = DolphinApplication.getAppContext();
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_IMMUTABLE : 0;
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
new Intent(ACTION_GC_ADAPTER_PERMISSION_GRANTED), flags);
manager.requestPermission(dev, pendingIntent);
}
}
}
}
public static void shutdown()
{
usbConnection.close();
}
@Keep
public static int getFd()
{
return usbConnection.getFileDescriptor();
}
@Keep
public static boolean isUsbDeviceAvailable()
{
synchronized (hotplugCallbackLock)
{
return adapterDevice != null;
}
}
@Nullable
private static UsbDevice queryAdapter()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
{
UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
{
if (manager.hasPermission(dev))
return dev;
else
requestPermission();
}
}
return null;
}
public static void initAdapter()
{
byte[] init = {0x13};
usbConnection.bulkTransfer(usbOut, init, init.length, 0);
}
@Keep
public static int input()
{
return usbConnection.bulkTransfer(usbIn, controllerPayload, controllerPayload.length, 16);
}
@Keep
public static int output(byte[] rumble)
{
return usbConnection.bulkTransfer(usbOut, rumble, 5, 16);
}
@Keep
public static boolean openAdapter()
{
UsbDevice dev;
synchronized (hotplugCallbackLock)
{
dev = adapterDevice;
}
if (dev == null)
{
return false;
}
usbConnection = manager.openDevice(dev);
if (usbConnection == null)
{
return false;
}
Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount());
Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount());
if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0)
{
UsbConfiguration conf = dev.getConfiguration(0);
usbInterface = conf.getInterface(0);
usbConnection.claimInterface(usbInterface, true);
Log.info("GCAdapter: Number of endpoints: " + usbInterface.getEndpointCount());
if (usbInterface.getEndpointCount() == 2)
{
for (int i = 0; i < usbInterface.getEndpointCount(); ++i)
if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN)
usbIn = usbInterface.getEndpoint(i);
else
usbOut = usbInterface.getEndpoint(i);
initAdapter();
return true;
}
else
{
usbConnection.releaseInterface(usbInterface);
}
}
Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter,
Toast.LENGTH_LONG).show();
usbConnection.close();
return false;
}
@Keep
public static void enableHotplugCallback()
{
synchronized (hotplugCallbackLock)
{
if (hotplugCallbackEnabled)
{
throw new IllegalStateException("enableHotplugCallback was called when already enabled");
}
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(ACTION_GC_ADAPTER_PERMISSION_GRANTED);
ContextCompat.registerReceiver(DolphinApplication.getAppContext(), hotplugBroadcastReceiver,
filter, ContextCompat.RECEIVER_EXPORTED);
hotplugCallbackEnabled = true;
onUsbDevicesChanged();
}
}
@Keep
public static void disableHotplugCallback()
{
synchronized (hotplugCallbackLock)
{
if (hotplugCallbackEnabled)
{
DolphinApplication.getAppContext().unregisterReceiver(hotplugBroadcastReceiver);
hotplugCallbackEnabled = false;
adapterDevice = null;
}
}
}
public static void onUsbDevicesChanged()
{
synchronized (hotplugCallbackLock)
{
if (adapterDevice != null)
{
boolean adapterStillConnected = manager.getDeviceList().entrySet().stream()
.anyMatch(pair -> pair.getValue().getDeviceId() == adapterDevice.getDeviceId());
if (!adapterStillConnected)
{
adapterDevice = null;
onAdapterDisconnected();
}
}
if (adapterDevice == null)
{
UsbDevice newAdapter = queryAdapter();
if (newAdapter != null)
{
adapterDevice = newAdapter;
onAdapterConnected();
}
}
}
}
private static native void onAdapterConnected();
private static native void onAdapterDisconnected();
}

View File

@@ -1,158 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.widget.Toast;
import androidx.annotation.Keep;
import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.services.USBPermService;
import java.util.HashMap;
import java.util.Map;
public class Java_GCAdapter
{
public static UsbManager manager;
@Keep
static byte[] controller_payload = new byte[37];
static UsbDeviceConnection usb_con;
static UsbInterface usb_intf;
static UsbEndpoint usb_in;
static UsbEndpoint usb_out;
private static void RequestPermission()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
{
UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
{
if (!manager.hasPermission(dev))
{
Context context = DolphinApplication.getAppContext();
Intent intent = new Intent(context, USBPermService.class);
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_IMMUTABLE : 0;
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, flags);
manager.requestPermission(dev, pendingIntent);
}
}
}
}
public static void Shutdown()
{
usb_con.close();
}
@Keep
public static int GetFD()
{
return usb_con.getFileDescriptor();
}
@Keep
public static boolean QueryAdapter()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
{
UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
{
if (manager.hasPermission(dev))
return true;
else
RequestPermission();
}
}
return false;
}
public static void InitAdapter()
{
byte[] init = {0x13};
usb_con.bulkTransfer(usb_out, init, init.length, 0);
}
@Keep
public static int Input()
{
return usb_con.bulkTransfer(usb_in, controller_payload, controller_payload.length, 16);
}
@Keep
public static int Output(byte[] rumble)
{
return usb_con.bulkTransfer(usb_out, rumble, 5, 16);
}
@Keep
public static boolean OpenAdapter()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
{
UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
{
if (manager.hasPermission(dev))
{
usb_con = manager.openDevice(dev);
Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount());
Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount());
if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0)
{
UsbConfiguration conf = dev.getConfiguration(0);
usb_intf = conf.getInterface(0);
usb_con.claimInterface(usb_intf, true);
Log.info("GCAdapter: Number of endpoints: " + usb_intf.getEndpointCount());
if (usb_intf.getEndpointCount() == 2)
{
for (int i = 0; i < usb_intf.getEndpointCount(); ++i)
if (usb_intf.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN)
usb_in = usb_intf.getEndpoint(i);
else
usb_out = usb_intf.getEndpoint(i);
InitAdapter();
return true;
}
else
{
usb_con.releaseInterface(usb_intf);
}
}
Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter,
Toast.LENGTH_LONG).show();
usb_con.close();
}
}
}
return false;
}
}

View File

@@ -22,7 +22,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class Java_WiimoteAdapter
public class WiimoteAdapter
{
final static int MAX_PAYLOAD = 23;
final static int MAX_WIIMOTES = 4;
@@ -31,14 +31,14 @@ public class Java_WiimoteAdapter
final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306;
public static UsbManager manager;
static UsbDeviceConnection usb_con;
static UsbInterface[] usb_intf = new UsbInterface[MAX_WIIMOTES];
static UsbEndpoint[] usb_in = new UsbEndpoint[MAX_WIIMOTES];
static UsbDeviceConnection usbConnection;
static UsbInterface[] usbInterface = new UsbInterface[MAX_WIIMOTES];
static UsbEndpoint[] usbIn = new UsbEndpoint[MAX_WIIMOTES];
@Keep
public static byte[][] wiimote_payload = new byte[MAX_WIIMOTES][MAX_PAYLOAD];
public static byte[][] wiimotePayload = new byte[MAX_WIIMOTES][MAX_PAYLOAD];
private static void RequestPermission()
private static void requestPermission()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
@@ -65,7 +65,7 @@ public class Java_WiimoteAdapter
}
@Keep
public static boolean QueryAdapter()
public static boolean queryAdapter()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
@@ -77,20 +77,20 @@ public class Java_WiimoteAdapter
if (manager.hasPermission(dev))
return true;
else
RequestPermission();
requestPermission();
}
}
return false;
}
@Keep
public static int Input(int index)
public static int input(int index)
{
return usb_con.bulkTransfer(usb_in[index], wiimote_payload[index], MAX_PAYLOAD, TIMEOUT);
return usbConnection.bulkTransfer(usbIn[index], wiimotePayload[index], MAX_PAYLOAD, TIMEOUT);
}
@Keep
public static int Output(int index, byte[] buf, int size)
public static int output(int index, byte[] buf, int size)
{
byte report_number = buf[0];
@@ -105,7 +105,7 @@ public class Java_WiimoteAdapter
final int HID_SET_REPORT = 0x9;
final int HID_OUTPUT = (2 << 8);
int write = usb_con.controlTransfer(
int write = usbConnection.controlTransfer(
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
HID_SET_REPORT,
HID_OUTPUT | report_number,
@@ -120,10 +120,10 @@ public class Java_WiimoteAdapter
}
@Keep
public static boolean OpenAdapter()
public static boolean openAdapter()
{
// If the adapter is already open. Don't attempt to do it again
if (usb_con != null && usb_con.getFileDescriptor() != -1)
if (usbConnection != null && usbConnection.getFileDescriptor() != -1)
return true;
HashMap<String, UsbDevice> devices = manager.getDeviceList();
@@ -135,7 +135,7 @@ public class Java_WiimoteAdapter
{
if (manager.hasPermission(dev))
{
usb_con = manager.openDevice(dev);
usbConnection = manager.openDevice(dev);
UsbConfiguration conf = dev.getConfiguration(0);
Log.info("Number of configurations: " + dev.getConfigurationCount());
@@ -149,20 +149,20 @@ public class Java_WiimoteAdapter
for (int i = 0; i < MAX_WIIMOTES; ++i)
{
// One interface per Wii Remote
usb_intf[i] = dev.getInterface(i);
usb_con.claimInterface(usb_intf[i], true);
usbInterface[i] = dev.getInterface(i);
usbConnection.claimInterface(usbInterface[i], true);
// One endpoint per Wii Remote. Input only
// Output reports go through the control channel.
usb_in[i] = usb_intf[i].getEndpoint(0);
Log.info("Interface " + i + " endpoint count:" + usb_intf[i].getEndpointCount());
usbIn[i] = usbInterface[i].getEndpoint(0);
Log.info("Interface " + i + " endpoint count:" + usbInterface[i].getEndpointCount());
}
return true;
}
else
{
// XXX: Message that the device was found, but it needs to be unplugged and plugged back in?
usb_con.close();
usbConnection.close();
}
}
}

View File

@@ -450,15 +450,18 @@ void AchievementManager::FilterApprovedIni(std::vector<T>& codes, const std::str
for (auto& code : codes)
{
if (code.enabled && !CheckApprovedCode(code, game_id, revision))
if (code.enabled && !IsApprovedCode(code, game_id, revision))
code.enabled = false;
}
}
template <typename T>
bool AchievementManager::CheckApprovedCode(const T& code, const std::string& game_id,
u16 revision) const
bool AchievementManager::ShouldCodeBeActivated(const T& code, const std::string& game_id,
u16 revision) const
{
if (!code.enabled)
return false;
if (!IsHardcoreModeActive())
return true;
@@ -468,33 +471,42 @@ bool AchievementManager::CheckApprovedCode(const T& code, const std::string& gam
INFO_LOG_FMT(ACHIEVEMENTS, "Verifying code {}", code.name);
bool verified = false;
if (IsApprovedCode(code, game_id, revision))
return true;
auto hash = Common::SHA1::DigestToString(GetCodeHash(code));
OSD::AddMessage(fmt::format("Failed to verify code {} for game ID {}.", code.name, game_id),
OSD::Duration::VERY_LONG, OSD::Color::RED);
OSD::AddMessage("Disable hardcore mode to enable this code.", OSD::Duration::VERY_LONG,
OSD::Color::RED);
return false;
}
template <typename T>
bool AchievementManager::IsApprovedCode(const T& code, const std::string& game_id,
u16 revision) const
{
// Approved codes list failed to hash
if (!m_ini_root->is<picojson::value::object>())
return false;
const auto hash = Common::SHA1::DigestToString(GetCodeHash(code));
for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(game_id, revision))
{
auto config = filename.substr(0, filename.length() - 4);
const auto config = filename.substr(0, filename.length() - 4);
if (m_ini_root->contains(config))
{
auto ini_config = m_ini_root->get(config);
const auto ini_config = m_ini_root->get(config);
if (ini_config.is<picojson::object>() && ini_config.contains(code.name))
{
auto ini_code = ini_config.get(code.name);
if (ini_code.template is<std::string>())
verified = (ini_code.template get<std::string>() == hash);
const auto ini_code = ini_config.get(code.name);
if (ini_code.template is<std::string>() && ini_code.template get<std::string>() == hash)
return true;
}
}
}
if (!verified)
{
OSD::AddMessage(fmt::format("Failed to verify code {} for game ID {}.", code.name, game_id),
OSD::Duration::VERY_LONG, OSD::Color::RED);
OSD::AddMessage("Disable hardcore mode to enable this code.", OSD::Duration::VERY_LONG,
OSD::Color::RED);
}
return verified;
return false;
}
Common::SHA1::Digest AchievementManager::GetCodeHash(const PatchEngine::Patch& patch) const
@@ -554,16 +566,27 @@ void AchievementManager::FilterApprovedARCodes(std::vector<ActionReplay::ARCode>
FilterApprovedIni(codes, game_id, revision);
}
bool AchievementManager::CheckApprovedGeckoCode(const Gecko::GeckoCode& code,
const std::string& game_id, u16 revision) const
bool AchievementManager::ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code,
const std::string& game_id, u16 revision) const
{
return CheckApprovedCode(code, game_id, revision);
return ShouldCodeBeActivated(code, game_id, revision);
}
bool AchievementManager::CheckApprovedARCode(const ActionReplay::ARCode& code,
bool AchievementManager::ShouldARCodeBeActivated(const ActionReplay::ARCode& code,
const std::string& game_id, u16 revision) const
{
return ShouldCodeBeActivated(code, game_id, revision);
}
bool AchievementManager::IsApprovedGeckoCode(const Gecko::GeckoCode& code,
const std::string& game_id, u16 revision) const
{
return CheckApprovedCode(code, game_id, revision);
return IsApprovedCode(code, game_id, revision);
}
bool AchievementManager::IsApprovedARCode(const ActionReplay::ARCode& code,
const std::string& game_id, u16 revision) const
{
return IsApprovedCode(code, game_id, revision);
}
void AchievementManager::SetSpectatorMode()

View File

@@ -149,10 +149,14 @@ public:
u16 revision) const;
void FilterApprovedARCodes(std::vector<ActionReplay::ARCode>& codes, const std::string& game_id,
u16 revision) const;
bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id,
u16 revision) const;
bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id,
bool ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code, const std::string& game_id,
u16 revision) const;
bool ShouldARCodeBeActivated(const ActionReplay::ARCode& code, const std::string& game_id,
u16 revision) const;
bool IsApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id,
u16 revision) const;
bool IsApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id,
u16 revision) const;
void SetSpectatorMode();
std::string_view GetPlayerDisplayName() const;
@@ -220,7 +224,9 @@ private:
template <typename T>
void FilterApprovedIni(std::vector<T>& codes, const std::string& game_id, u16 revision) const;
template <typename T>
bool CheckApprovedCode(const T& code, const std::string& game_id, u16 revision) const;
bool ShouldCodeBeActivated(const T& code, const std::string& game_id, u16 revision) const;
template <typename T>
bool IsApprovedCode(const T& code, const std::string& game_id, u16 revision) const;
Common::SHA1::Digest GetCodeHash(const PatchEngine::Patch& patch) const;
Common::SHA1::Digest GetCodeHash(const Gecko::GeckoCode& code) const;
Common::SHA1::Digest GetCodeHash(const ActionReplay::ARCode& code) const;
@@ -333,14 +339,14 @@ public:
constexpr bool IsHardcoreModeActive() { return false; }
constexpr bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id,
u16 revision)
constexpr bool ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code,
const std::string& game_id, u16 revision)
{
return true;
}
constexpr bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id,
u16 revision)
constexpr bool ShouldARCodeBeActivated(const ActionReplay::ARCode& code,
const std::string& game_id, u16 revision)
{
return true;
}

View File

@@ -122,11 +122,11 @@ void ApplyCodes(std::span<const ARCode> codes, const std::string& game_id, u16 r
std::lock_guard guard(s_lock);
s_disable_logging = false;
s_active_codes.clear();
std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes),
[&game_id, &revision](const ARCode& code) {
return code.enabled && AchievementManager::GetInstance().CheckApprovedARCode(
code, game_id, revision);
});
const auto should_be_activated = [&game_id, &revision](const ARCode& code) {
return AchievementManager::GetInstance().ShouldARCodeBeActivated(code, game_id, revision);
};
std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes), should_be_activated);
s_active_codes.shrink_to_fit();
}

View File

@@ -68,11 +68,11 @@ void SetActiveCodes(std::span<const GeckoCode> gcodes, const std::string& game_i
{
s_active_codes.reserve(gcodes.size());
const auto should_be_activated = [&game_id, &revision](const GeckoCode& code) {
return AchievementManager::GetInstance().ShouldGeckoCodeBeActivated(code, game_id, revision);
};
std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes),
[&game_id, &revision](const GeckoCode& code) {
return code.enabled && AchievementManager::GetInstance().CheckApprovedGeckoCode(
code, game_id, revision);
});
should_be_activated);
}
s_active_codes.shrink_to_fit();

View File

@@ -30,8 +30,8 @@ void WiimoteScannerAndroid::FindWiimotes(std::vector<Wiimote*>& found_wiimotes,
JNIEnv* env = IDCache::GetEnvForThread();
jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z");
jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z");
jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "queryAdapter", "()Z");
if (env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func) &&
env->CallStaticBooleanMethod(s_adapter_class, openadapter_func))
@@ -55,15 +55,15 @@ bool WiimoteAndroid::ConnectInternal()
{
m_env = IDCache::GetEnvForThread();
jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimote_payload", "[[B");
jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimotePayload", "[[B");
jobjectArray payload_object =
reinterpret_cast<jobjectArray>(m_env->GetStaticObjectField(s_adapter_class, payload_field));
m_java_wiimote_payload =
(jbyteArray)m_env->GetObjectArrayElement(payload_object, m_mayflash_index);
// Get function pointers
m_input_func = m_env->GetStaticMethodID(s_adapter_class, "Input", "(I)I");
m_output_func = m_env->GetStaticMethodID(s_adapter_class, "Output", "(I[BI)I");
m_input_func = m_env->GetStaticMethodID(s_adapter_class, "input", "(I)I");
m_output_func = m_env->GetStaticMethodID(s_adapter_class, "output", "(I[BI)I");
is_connected = true;
@@ -110,7 +110,7 @@ int WiimoteAndroid::IOWrite(u8 const* buf, size_t len)
void InitAdapterClass()
{
JNIEnv* env = IDCache::GetEnvForThread();
jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter");
jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/WiimoteAdapter");
s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
}
} // namespace WiimoteReal

View File

@@ -10,6 +10,7 @@
#include <string>
#include <vector>
#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Core/Core.h"
@@ -341,8 +342,13 @@ void MemChecks::Update()
{
const Core::CPUThreadGuard guard(m_system);
// Clear the JIT cache so it can switch the watchpoint-compatible mode.
if (m_mem_breakpoints_set != HasAny())
const bool registers_changed = UpdateRegistersUsedInConditions();
// If we've added a first memcheck, clear the JIT cache so it can switch to watchpoint-compatible
// code. Or, if we've added a memcheck whose condition wants to read from a new register, clear
// the JIT cache to make the slow memory access code flush that register. And conversely, if the
// aforementioned functionality is no longer needed, clear the JIT cache to switch to faster code.
if (registers_changed || m_mem_breakpoints_set != HasAny())
{
m_system.GetJitInterface().ClearCache(guard);
m_mem_breakpoints_set = HasAny();
@@ -351,6 +357,27 @@ void MemChecks::Update()
m_system.GetMMU().DBATUpdated();
}
bool MemChecks::UpdateRegistersUsedInConditions()
{
BitSet32 gprs_used, fprs_used;
for (TMemCheck& mem_check : m_mem_checks)
{
if (mem_check.condition)
{
gprs_used |= mem_check.condition->GetGPRsUsed();
fprs_used |= mem_check.condition->GetFPRsUsed();
}
}
const bool registers_changed =
gprs_used != m_gprs_used_in_conditions || fprs_used != m_fprs_used_in_conditions;
m_gprs_used_in_conditions = gprs_used;
m_fprs_used_in_conditions = fprs_used;
return registers_changed;
}
TMemCheck* MemChecks::GetMemCheck(u32 address, size_t size)
{
const auto iter = std::ranges::find_if(m_mem_checks, [address, size](const auto& mc) {

View File

@@ -8,6 +8,7 @@
#include <string>
#include <vector>
#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
#include "Core/PowerPC/Expression.h"
@@ -129,9 +130,17 @@ public:
void Clear();
bool HasAny() const { return !m_mem_checks.empty(); }
BitSet32 GetGPRsUsedInConditions() { return m_gprs_used_in_conditions; }
BitSet32 GetFPRsUsedInConditions() { return m_fprs_used_in_conditions; }
private:
// Returns whether any change was made
bool UpdateRegistersUsedInConditions();
TMemChecks m_mem_checks;
Core::System& m_system;
BitSet32 m_gprs_used_in_conditions;
BitSet32 m_fprs_used_in_conditions;
bool m_mem_breakpoints_set = false;
};

View File

@@ -25,6 +25,7 @@ using std::isinf;
using std::isnan;
#include <expr.h>
#include "Common/BitSet.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Core/Core.h"
@@ -503,3 +504,26 @@ std::string Expression::GetText() const
{
return m_text;
}
void Expression::ComputeRegistersUsed()
{
if (m_has_computed_registers_used)
return;
for (const VarBinding& bind : m_binds)
{
switch (bind.type)
{
case VarBindingType::GPR:
m_gprs_used[bind.index] = true;
break;
case VarBindingType::FPR:
m_fprs_used[bind.index] = true;
break;
default:
break;
}
}
m_has_computed_registers_used = true;
}

View File

@@ -9,6 +9,8 @@
#include <string_view>
#include <vector>
#include "Common/BitSet.h"
struct expr;
struct expr_var_list;
@@ -41,6 +43,18 @@ public:
std::string GetText() const;
BitSet32 GetGPRsUsed()
{
ComputeRegistersUsed();
return m_gprs_used;
}
BitSet32 GetFPRsUsed()
{
ComputeRegistersUsed();
return m_fprs_used;
}
private:
enum class SynchronizeDirection
{
@@ -69,10 +83,16 @@ private:
void SynchronizeBindings(Core::System& system, SynchronizeDirection dir) const;
void Reporting(const double result) const;
void ComputeRegistersUsed();
std::string m_text;
ExprPointer m_expr;
ExprVarListPointer m_vars;
std::vector<VarBinding> m_binds;
BitSet32 m_gprs_used;
BitSet32 m_fprs_used;
bool m_has_computed_registers_used = false;
};
inline bool EvaluateCondition(Core::System& system, const std::optional<Expression>& condition)

View File

@@ -1294,6 +1294,25 @@ void Jit64::IntializeSpeculativeConstants()
}
}
void Jit64::FlushRegistersBeforeSlowAccess()
{
// Register values can be used by memory watchpoint conditions.
MemChecks& mem_checks = m_system.GetPowerPC().GetMemChecks();
if (mem_checks.HasAny())
{
BitSet32 gprs = mem_checks.GetGPRsUsedInConditions();
BitSet32 fprs = mem_checks.GetFPRsUsedInConditions();
if (gprs || fprs)
{
RCForkGuard gpr_guard = gpr.Fork();
RCForkGuard fpr_guard = fpr.Fork();
gpr.Flush(gprs);
fpr.Flush(fprs);
}
}
}
bool Jit64::HandleFunctionHooking(u32 address)
{
const auto result = HLE::TryReplaceFunction(m_ppc_symbol_db, address, PowerPC::CoreMode::JIT);

View File

@@ -81,6 +81,8 @@ public:
void IntializeSpeculativeConstants();
void FlushRegistersBeforeSlowAccess();
JitBlockCache* GetBlockCache() override { return &blocks; }
void Trace();

View File

@@ -110,6 +110,8 @@ void Jit64::lXXx(UGeckoInstruction inst)
PanicAlertFmt("Invalid instruction");
}
FlushRegistersBeforeSlowAccess();
// PowerPC has no 8-bit sign extended load, but x86 does, so merge extsb with the load if we find
// it.
if (CanMergeNextInstructions(1) && accessSize == 8 && js.op[1].inst.OPCD == 31 &&
@@ -439,6 +441,8 @@ void Jit64::dcbz(UGeckoInstruction inst)
int a = inst.RA;
int b = inst.RB;
FlushRegistersBeforeSlowAccess();
{
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
RCOpArg Rb = gpr.Use(b, RCMode::Read);
@@ -477,7 +481,7 @@ void Jit64::dcbz(UGeckoInstruction inst)
SwitchToFarCode();
SetJumpTarget(slow);
}
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
FlushPCBeforeSlowAccess();
BitSet32 registersInUse = CallerSavedRegistersInUse();
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
ABI_CallFunctionPR(PowerPC::ClearDCacheLineFromJit, &m_mmu, RSCRATCH);
@@ -524,6 +528,8 @@ void Jit64::stX(UGeckoInstruction inst)
return;
}
FlushRegistersBeforeSlowAccess();
// If we already know the address of the write
if (!a || gpr.IsImm(a))
{
@@ -602,6 +608,8 @@ void Jit64::stXx(UGeckoInstruction inst)
break;
}
FlushRegistersBeforeSlowAccess();
const bool does_clobber = WriteClobbersRegValue(accessSize, /* swap */ !byte_reverse);
RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read);
@@ -634,6 +642,8 @@ void Jit64::lmw(UGeckoInstruction inst)
int a = inst.RA, d = inst.RD;
FlushRegistersBeforeSlowAccess();
// TODO: This doesn't handle rollback on DSI correctly
{
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
@@ -657,6 +667,8 @@ void Jit64::stmw(UGeckoInstruction inst)
int a = inst.RA, d = inst.RD;
FlushRegistersBeforeSlowAccess();
// TODO: This doesn't handle rollback on DSI correctly
for (int i = d; i < 32; i++)
{

View File

@@ -30,6 +30,8 @@ void Jit64::lfXXX(UGeckoInstruction inst)
FALLBACK_IF(!indexed && !a);
FlushRegistersBeforeSlowAccess();
s32 offset = 0;
RCOpArg addr = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read);
RegCache::Realize(addr);
@@ -103,6 +105,8 @@ void Jit64::stfXXX(UGeckoInstruction inst)
FALLBACK_IF(update && jo.memcheck && indexed && a == b);
FlushRegistersBeforeSlowAccess();
if (single)
{
if (js.fpr_is_store_safe[s] && js.op->fprIsSingle[s])
@@ -196,6 +200,8 @@ void Jit64::stfiwx(UGeckoInstruction inst)
int a = inst.RA;
int b = inst.RB;
FlushRegistersBeforeSlowAccess();
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
RCOpArg Rb = gpr.Use(b, RCMode::Read);
RCOpArg Rs = fpr.Use(s, RCMode::Read);

View File

@@ -35,6 +35,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
int w = indexed ? inst.Wx : inst.W;
FALLBACK_IF(!a);
FlushRegistersBeforeSlowAccess();
RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA);
RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read);
RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset);
@@ -69,8 +71,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
}
else
{
// Stash PC in case asm routine needs to call into C++
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
FlushPCBeforeSlowAccess();
// We know what GQR is here, so we can load RSCRATCH2 and call into the store method directly
// with just the scale bits.
MOV(32, R(RSCRATCH2), Imm32(gqrValue & 0x3F00));
@@ -83,8 +85,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
}
else
{
// Stash PC in case asm routine needs to call into C++
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
FlushPCBeforeSlowAccess();
// Some games (e.g. Dirt 2) incorrectly set the unused bits which breaks the lookup table code.
// Hence, we need to mask out the unused bits. The layout of the GQR register is
// UU[SCALE]UUUUU[TYPE] where SCALE is 6 bits and TYPE is 3 bits, so we have to AND with
@@ -124,6 +126,8 @@ void Jit64::psq_lXX(UGeckoInstruction inst)
int w = indexed ? inst.Wx : inst.W;
FALLBACK_IF(!a);
FlushRegistersBeforeSlowAccess();
RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA);
RCX64Reg Ra = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read);
RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset);
@@ -144,8 +148,8 @@ void Jit64::psq_lXX(UGeckoInstruction inst)
}
else
{
// Stash PC in case asm routine needs to call into C++
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
FlushPCBeforeSlowAccess();
// Get the high part of the GQR register
OpArg gqr = PPCSTATE_SPR(SPR_GQR0 + i);
gqr.AddMemOffset(2);

View File

@@ -92,6 +92,13 @@ void EmuCodeBlock::SwitchToNearCode()
SetCodePtr(m_near_code, m_near_code_end, m_near_code_write_failed);
}
void EmuCodeBlock::FlushPCBeforeSlowAccess()
{
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
// interrupt checks, and printing accurate PC locations in debug logs.
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
}
FixupBranch EmuCodeBlock::BATAddressLookup(X64Reg addr, X64Reg tmp, const void* bat_table)
{
MOV(64, R(tmp), ImmPtr(bat_table));
@@ -386,14 +393,11 @@ void EmuCodeBlock::SafeLoadToReg(X64Reg reg_value, const Gen::OpArg& opAddress,
SetJumpTarget(slow);
}
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
// interrupt checks, and printing accurate PC locations in debug logs.
//
// In the case of Jit64AsmCommon routines, we don't know the PC here,
// so the caller has to store the PC themselves.
// In the case of Jit64AsmCommon routines, the state we want to store here isn't known
// when compiling the routine, so the caller has to store it themselves.
if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC))
{
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
FlushPCBeforeSlowAccess();
}
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
@@ -457,8 +461,7 @@ void EmuCodeBlock::SafeLoadToRegImmediate(X64Reg reg_value, u32 address, int acc
return;
}
// Helps external systems know which instruction triggered the read.
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
FlushPCBeforeSlowAccess();
// Fall back to general-case code.
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
@@ -560,14 +563,11 @@ void EmuCodeBlock::SafeWriteRegToReg(OpArg reg_value, X64Reg reg_addr, int acces
SetJumpTarget(slow);
}
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
// interrupt checks, and printing accurate PC locations in debug logs.
//
// In the case of Jit64AsmCommon routines, we don't know the PC here,
// so the caller has to store the PC themselves.
// In the case of Jit64AsmCommon routines, the state we want to store here isn't known
// when compiling the routine, so the caller has to store it themselves.
if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC))
{
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
FlushPCBeforeSlowAccess();
}
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
@@ -663,8 +663,7 @@ bool EmuCodeBlock::WriteToConstAddress(int accessSize, OpArg arg, u32 address,
}
else
{
// Helps external systems know which instruction triggered the write
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
FlushPCBeforeSlowAccess();
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
switch (accessSize)

View File

@@ -49,6 +49,8 @@ public:
return Gen::M(m_const_pool.GetConstant(&value, sizeof(T), N, index));
}
void FlushPCBeforeSlowAccess();
// Writes upper 15 bits of physical address to addr and clobbers the lower 17 bits of addr.
// Jumps to the returned FixupBranch if lookup fails.
Gen::FixupBranch BATAddressLookup(Gen::X64Reg addr, Gen::X64Reg tmp, const void* bat_table);

View File

@@ -273,12 +273,18 @@ protected:
// !emitting_routine && mode != AlwaysSlowAccess && !jo.fastmem: X30
// !emitting_routine && mode == Auto && jo.fastmem: X30
//
// Furthermore, any callee-saved register which isn't marked in gprs_to_push/fprs_to_push
// may be clobbered if mode != AlwaysFastAccess.
// Furthermore:
// - Any callee-saved register which isn't marked in gprs_to_push/fprs_to_push may be
// clobbered if mode != AlwaysFastAccess.
// - If !emitting_routine && mode != AlwaysFastAccess && jo.memcheck, X30 must not
// contain a guest register.
void EmitBackpatchRoutine(u32 flags, MemAccessMode mode, Arm64Gen::ARM64Reg RS,
Arm64Gen::ARM64Reg addr, BitSet32 gprs_to_push = BitSet32(0),
BitSet32 fprs_to_push = BitSet32(0), bool emitting_routine = false);
// temp_gpr must be a valid register, but temp_fpr can be INVALID_REG.
void FlushPPCStateBeforeSlowAccess(Arm64Gen::ARM64Reg temp_gpr, Arm64Gen::ARM64Reg temp_fpr);
// Loadstore routines
void SafeLoadToReg(u32 dest, s32 addr, s32 offsetReg, u32 flags, s32 offset, bool update);
void SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s32 offset, bool update);

View File

@@ -21,6 +21,7 @@
#include "Core/PowerPC/JitArmCommon/BackPatch.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
using namespace Arm64Gen;
@@ -171,6 +172,7 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
const ARM64Reg temp_gpr = ARM64Reg::W1;
const int temp_gpr_index = DecodeReg(temp_gpr);
const ARM64Reg temp_fpr = fprs_to_push[0] ? ARM64Reg::INVALID_REG : ARM64Reg::Q0;
BitSet32 gprs_to_push_early = {};
if (memcheck)
@@ -189,16 +191,10 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
ABI_PushRegisters(gprs_to_push & ~gprs_to_push_early);
m_float_emit.ABI_PushRegisters(fprs_to_push, ARM64Reg::X30);
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
// interrupt checks, and printing accurate PC locations in debug logs.
//
// In the case of JitAsm routines, we don't know the PC here,
// so the caller has to store the PC themselves.
// In the case of JitAsm routines, the state we want to store here isn't known
// when compiling the routine, so the caller has to store it themselves.
if (!emitting_routine)
{
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
}
FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, temp_fpr);
if (flags & BackPatchInfo::FLAG_STORE)
{
@@ -265,7 +261,6 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
if (memcheck)
{
const ARM64Reg temp_fpr = fprs_to_push[0] ? ARM64Reg::INVALID_REG : ARM64Reg::Q0;
const u64 early_push_count = (gprs_to_push & gprs_to_push_early).Count();
const u64 early_push_size = Common::AlignUp(early_push_count, 2) * 8;
@@ -316,6 +311,22 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
}
}
void JitArm64::FlushPPCStateBeforeSlowAccess(ARM64Reg temp_gpr, ARM64Reg temp_fpr)
{
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
// interrupt checks, and printing accurate PC locations in debug logs.
MOVI2R(temp_gpr, js.compilerPC);
STR(IndexType::Unsigned, temp_gpr, PPC_REG, PPCSTATE_OFF(pc));
// Register values can be used by memory watchpoint conditions.
MemChecks& mem_checks = m_system.GetPowerPC().GetMemChecks();
if (mem_checks.HasAny())
{
gpr.StoreRegisters(mem_checks.GetGPRsUsedInConditions(), temp_gpr, FlushMode::MaintainState);
fpr.StoreRegisters(mem_checks.GetFPRsUsedInConditions(), temp_fpr, FlushMode::MaintainState);
}
}
bool JitArm64::HandleFastmemFault(SContext* ctx)
{
const u8* pc = reinterpret_cast<const u8*>(ctx->CTX_PC);

View File

@@ -102,9 +102,7 @@ void JitArm64::psq_lXX(UGeckoInstruction inst)
{
LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i));
// Stash PC in case asm routine needs to call into C++
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, ARM64Reg::Q1);
UBFM(type_reg, scale_reg, 16, 18); // Type
UBFM(scale_reg, scale_reg, 24, 29); // Scale
@@ -260,9 +258,7 @@ void JitArm64::psq_stXX(UGeckoInstruction inst)
{
LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i));
// Stash PC in case asm routine needs to call into C++
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, ARM64Reg::Q1);
UBFM(type_reg, scale_reg, 0, 2); // Type
UBFM(scale_reg, scale_reg, 8, 13); // Scale

View File

@@ -388,14 +388,16 @@ public:
BitSet32 GetDirtyGPRs() const;
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
FlushMode flush_mode = FlushMode::All)
{
FlushRegisters(regs, FlushMode::All, tmp_reg, IgnoreDiscardedRegisters::No);
FlushRegisters(regs, flush_mode, tmp_reg, IgnoreDiscardedRegisters::No);
}
void StoreCRRegisters(BitSet8 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
void StoreCRRegisters(BitSet8 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
FlushMode flush_mode = FlushMode::All)
{
FlushCRRegisters(regs, FlushMode::All, tmp_reg, IgnoreDiscardedRegisters::No);
FlushCRRegisters(regs, flush_mode, tmp_reg, IgnoreDiscardedRegisters::No);
}
void DiscardCRRegisters(BitSet8 regs);
@@ -459,9 +461,10 @@ public:
void FixSinglePrecision(size_t preg);
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
FlushMode flush_mode = FlushMode::All)
{
FlushRegisters(regs, FlushMode::All, tmp_reg);
FlushRegisters(regs, flush_mode, tmp_reg);
}
protected:

View File

@@ -8,14 +8,21 @@
#include <QCursor>
#include <QHBoxLayout>
#ifdef USE_RETRO_ACHIEVEMENTS
#include <QIcon>
#endif // USE_RETRO_ACHIEVEMENTS
#include <QListWidget>
#include <QMenu>
#include <QPushButton>
#ifdef USE_RETRO_ACHIEVEMENTS
#include <QStyle>
#endif // USE_RETRO_ACHIEVEMENTS
#include <QVBoxLayout>
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Core/AchievementManager.h"
#include "Core/ActionReplay.h"
#include "Core/ConfigManager.h"
@@ -23,6 +30,9 @@
#include "DolphinQt/Config/CheatWarningWidget.h"
#include "DolphinQt/Config/HardcoreWarningWidget.h"
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
#ifdef USE_RETRO_ACHIEVEMENTS
#include "DolphinQt/Settings.h"
#endif // USE_RETRO_ACHIEVEMENTS
ARCodeWidget::ARCodeWidget(std::string game_id, u16 game_revision, bool restart_required)
: m_game_id(std::move(game_id)), m_game_revision(game_revision),
@@ -90,6 +100,7 @@ void ARCodeWidget::ConnectWidgets()
#ifdef USE_RETRO_ACHIEVEMENTS
connect(m_hc_warning, &HardcoreWarningWidget::OpenAchievementSettings, this,
&ARCodeWidget::OpenAchievementSettings);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &ARCodeWidget::UpdateList);
#endif // USE_RETRO_ACHIEVEMENTS
connect(m_code_list, &QListWidget::itemChanged, this, &ARCodeWidget::OnItemChanged);
@@ -199,6 +210,21 @@ void ARCodeWidget::UpdateList()
item->setCheckState(ar.enabled ? Qt::Checked : Qt::Unchecked);
item->setData(Qt::UserRole, static_cast<int>(i));
#ifdef USE_RETRO_ACHIEVEMENTS
const AchievementManager& achievement_manager = AchievementManager::GetInstance();
if (achievement_manager.IsHardcoreModeActive())
{
const QIcon approved_icon = style()->standardIcon(QStyle::SP_DialogYesButton);
const QIcon warning_icon = style()->standardIcon(QStyle::SP_MessageBoxWarning);
if (achievement_manager.IsApprovedARCode(ar, m_game_id, m_game_revision))
item->setIcon(approved_icon);
else
item->setIcon(warning_icon);
}
#endif // USE_RETRO_ACHIEVEMENTS
m_code_list->addItem(item);
}

View File

@@ -10,16 +10,23 @@
#include <QFontDatabase>
#include <QFormLayout>
#include <QHBoxLayout>
#ifdef USE_RETRO_ACHIEVEMENTS
#include <QIcon>
#endif // USE_RETRO_ACHIEVEMENTS
#include <QLabel>
#include <QListWidget>
#include <QMenu>
#include <QPushButton>
#ifdef USE_RETRO_ACHIEVEMENTS
#include <QStyle>
#endif // USE_RETRO_ACHIEVEMENTS
#include <QTextEdit>
#include <QVBoxLayout>
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Core/AchievementManager.h"
#include "Core/ConfigManager.h"
#include "Core/GeckoCode.h"
#include "Core/GeckoCodeConfig.h"
@@ -31,6 +38,9 @@
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
#include "DolphinQt/QtUtils/QtUtils.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#ifdef USE_RETRO_ACHIEVEMENTS
#include "DolphinQt/Settings.h"
#endif // USE_RETRO_ACHIEVEMENTS
GeckoCodeWidget::GeckoCodeWidget(std::string game_id, std::string gametdb_id, u16 game_revision,
bool restart_required)
@@ -158,6 +168,8 @@ void GeckoCodeWidget::ConnectWidgets()
#ifdef USE_RETRO_ACHIEVEMENTS
connect(m_hc_warning, &HardcoreWarningWidget::OpenAchievementSettings, this,
&GeckoCodeWidget::OpenAchievementSettings);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
&GeckoCodeWidget::UpdateList);
#endif // USE_RETRO_ACHIEVEMENTS
}
@@ -356,6 +368,21 @@ void GeckoCodeWidget::UpdateList()
item->setCheckState(code.enabled ? Qt::Checked : Qt::Unchecked);
item->setData(Qt::UserRole, static_cast<int>(i));
#ifdef USE_RETRO_ACHIEVEMENTS
const AchievementManager& achievement_manager = AchievementManager::GetInstance();
if (achievement_manager.IsHardcoreModeActive())
{
const QIcon approved_icon = style()->standardIcon(QStyle::SP_DialogYesButton);
const QIcon warning_icon = style()->standardIcon(QStyle::SP_MessageBoxWarning);
if (achievement_manager.IsApprovedGeckoCode(code, m_game_id, m_game_revision))
item->setIcon(approved_icon);
else
item->setIcon(warning_icon);
}
#endif // USE_RETRO_ACHIEVEMENTS
m_code_list->addItem(item);
}

View File

@@ -140,6 +140,16 @@ void HacksWidget::OnBackendChanged(const QString& backend_name)
void HacksWidget::ConnectWidgets()
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(m_store_efb_copies, &QCheckBox::checkStateChanged,
[this](Qt::CheckState) { UpdateDeferEFBCopiesEnabled(); });
connect(m_store_xfb_copies, &QCheckBox::checkStateChanged,
[this](Qt::CheckState) { UpdateDeferEFBCopiesEnabled(); });
connect(m_immediate_xfb, &QCheckBox::checkStateChanged,
[this](Qt::CheckState) { UpdateSkipPresentingDuplicateFramesEnabled(); });
connect(m_vi_skip, &QCheckBox::checkStateChanged,
[this](Qt::CheckState) { UpdateSkipPresentingDuplicateFramesEnabled(); });
#else
connect(m_store_efb_copies, &QCheckBox::stateChanged,
[this](int) { UpdateDeferEFBCopiesEnabled(); });
connect(m_store_xfb_copies, &QCheckBox::stateChanged,
@@ -148,6 +158,7 @@ void HacksWidget::ConnectWidgets()
[this](int) { UpdateSkipPresentingDuplicateFramesEnabled(); });
connect(m_vi_skip, &QCheckBox::stateChanged,
[this](int) { UpdateSkipPresentingDuplicateFramesEnabled(); });
#endif
}
void HacksWidget::AddDescriptions()

View File

@@ -76,7 +76,11 @@ MappingBool::MappingBool(MappingWidget* parent, ControllerEmu::NumericSetting<bo
if (const auto ui_description = m_setting.GetUIDescription())
setToolTip(tr(ui_description));
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(this, &QCheckBox::checkStateChanged, this, [this, parent](Qt::CheckState value) {
#else
connect(this, &QCheckBox::stateChanged, this, [this, parent](int value) {
#endif
m_setting.SetValue(value != 0);
ConfigChanged();
parent->SaveSettings();

View File

@@ -5,11 +5,11 @@
#include <memory>
#include <QApplication>
#include <QBitmap>
#include <QBrush>
#include <QCursor>
#include <QFont>
#include <QGuiApplication>
#include <QLabel>
#include <QPainter>
#include <QPainterPath>
@@ -25,11 +25,17 @@
#include <QToolTip>
#endif
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/Settings.h"
namespace
{
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
// Remember the parent ToolTipWidget so cursor-related events can see whether the cursor is inside
// the parent's bounding box or not. Use this variable instead of BalloonTip's parent() member
// because the ToolTipWidget isn't responsible for deleting the BalloonTip and so doesn't set its
// parent member.
QWidget* s_parent = nullptr;
} // namespace
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
@@ -53,6 +59,7 @@ void BalloonTip::ShowBalloon(const QString& title, const QString& message,
void BalloonTip::HideBalloon()
{
s_parent = nullptr;
#if defined(__APPLE__)
QToolTip::hideText();
#else
@@ -66,6 +73,9 @@ void BalloonTip::HideBalloon()
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
: QWidget(nullptr, Qt::ToolTip)
{
s_parent = parent;
setMouseTracking(true);
QColor window_color;
QColor text_color;
QColor dolphin_emphasis;
@@ -113,10 +123,61 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
create_label(message);
}
void BalloonTip::paintEvent(QPaintEvent*)
bool BalloonTip::IsCursorInsideWidgetBoundingBox(const QWidget& widget)
{
const QPoint local_cursor_position = widget.mapFromGlobal(QCursor::pos());
return widget.rect().contains(local_cursor_position);
}
bool BalloonTip::IsCursorOnBalloonTip()
{
return s_the_balloon_tip != nullptr &&
QApplication::widgetAt(QCursor::pos()) == s_the_balloon_tip.get();
}
bool BalloonTip::IsWidgetBalloonTipActive(const QWidget& widget)
{
return &widget == s_parent;
}
// Hiding the balloon causes the BalloonTip widget to be deleted. Triggering that deletion while
// inside a BalloonTip event handler leads to a use-after-free crash or worse, so queue the deletion
// for later.
static void QueueHideBalloon()
{
QueueOnObject(s_parent, BalloonTip::HideBalloon);
}
void BalloonTip::enterEvent(QEnterEvent* const event)
{
if (!IsCursorInsideWidgetBoundingBox(*s_parent))
QueueHideBalloon();
QWidget::enterEvent(event);
}
void BalloonTip::mouseMoveEvent(QMouseEvent* const event)
{
if (!IsCursorInsideWidgetBoundingBox(*s_parent))
QueueHideBalloon();
QWidget::mouseMoveEvent(event);
}
void BalloonTip::leaveEvent(QEvent* const event)
{
if (QApplication::widgetAt(QCursor::pos()) != s_parent)
QueueHideBalloon();
QWidget::leaveEvent(event);
}
void BalloonTip::paintEvent(QPaintEvent* const event)
{
QPainter painter(this);
painter.drawPixmap(rect(), m_pixmap);
QWidget::paintEvent(event);
}
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,

View File

@@ -7,6 +7,9 @@
#include <QPixmap>
#include <QWidget>
class QEnterEvent;
class QEvent;
class QMouseEvent;
class QPaintEvent;
class QPoint;
class QString;
@@ -29,17 +32,22 @@ public:
const QPoint& target_arrow_tip_position, QWidget* parent,
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
static void HideBalloon();
static bool IsCursorInsideWidgetBoundingBox(const QWidget& widget);
static bool IsCursorOnBalloonTip();
static bool IsWidgetBalloonTipActive(const QWidget& widget);
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent);
protected:
void enterEvent(QEnterEvent* event) override;
void leaveEvent(QEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void paintEvent(QPaintEvent* event) override;
private:
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
int border_width);
protected:
void paintEvent(QPaintEvent*) override;
private:
QColor m_border_color;
QPixmap m_pixmap;
};

View File

@@ -9,6 +9,11 @@
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
class QEnterEvent;
class QEvent;
class QHideEvent;
class QTimerEvent;
constexpr int TOOLTIP_DELAY = 300;
template <class Derived>
@@ -22,28 +27,48 @@ public:
void SetDescription(QString description) { m_description = std::move(description); }
private:
void enterEvent(QEnterEvent* event) override
void enterEvent(QEnterEvent* const event) override
{
if (m_timer_id)
return;
m_timer_id = this->startTimer(TOOLTIP_DELAY);
// If the timer is already running, or the cursor is reentering the ToolTipWidget after having
// hovered over the BalloonTip, don't start a new timer.
if (!m_timer_id && !BalloonTip::IsWidgetBalloonTipActive(*this))
m_timer_id = this->startTimer(TOOLTIP_DELAY);
Derived::enterEvent(event);
}
void leaveEvent(QEvent* event) override { KillAndHide(); }
void hideEvent(QHideEvent* event) override { KillAndHide(); }
void leaveEvent(QEvent* const event) override
{
// If the cursor would still be inside the ToolTipWidget but the BalloonTip is covering that
// part of it, keep the BalloonTip open. In that case the BalloonTip will then track the cursor
// and close itself if it leaves the bounding box of this ToolTipWidget.
if (!BalloonTip::IsCursorInsideWidgetBoundingBox(*this) || !BalloonTip::IsCursorOnBalloonTip())
KillTimerAndHideBalloon();
void timerEvent(QTimerEvent* event) override
Derived::leaveEvent(event);
}
void hideEvent(QHideEvent* const event) override
{
KillTimerAndHideBalloon();
Derived::hideEvent(event);
}
void timerEvent(QTimerEvent* const event) override
{
this->killTimer(*m_timer_id);
m_timer_id.reset();
BalloonTip::ShowBalloon(m_title, m_description,
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
Derived::timerEvent(event);
}
virtual QPoint GetToolTipPosition() const = 0;
void KillAndHide()
void KillTimerAndHideBalloon()
{
if (m_timer_id)
{

View File

@@ -120,8 +120,13 @@ void VerifyWidget::ConnectWidgets()
{
connect(m_verify_button, &QPushButton::clicked, this, &VerifyWidget::Verify);
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(m_md5_checkbox, &QCheckBox::checkStateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
connect(m_sha1_checkbox, &QCheckBox::checkStateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
#else
connect(m_md5_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
connect(m_sha1_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
#endif
}
static void SetHash(QLineEdit* line_edit, const std::vector<u8>& hash)

View File

@@ -935,7 +935,11 @@ std::vector<u8> MemoryViewWidget::ConvertTextToBytes(Type type, QStringView inpu
// Confirm it is only hex bytes
const QRegularExpression is_hex(QStringLiteral("^([0-9A-F]{2})*$"),
QRegularExpression::CaseInsensitiveOption);
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
const QRegularExpressionMatch match = is_hex.matchView(input_text);
#else
const QRegularExpressionMatch match = is_hex.match(input_text);
#endif
good = match.hasMatch();
if (good)
{

View File

@@ -209,6 +209,26 @@ void NetworkWidget::ConnectWidgets()
{
connect(m_dump_format_combo, &QComboBox::currentIndexChanged, this,
&NetworkWidget::OnDumpFormatComboChanged);
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(m_dump_ssl_read_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked);
});
connect(m_dump_ssl_write_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_WRITE, state == Qt::Checked);
});
connect(m_dump_root_ca_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_ROOT_CA, state == Qt::Checked);
});
connect(m_dump_peer_cert_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_PEER_CERT, state == Qt::Checked);
});
connect(m_verify_certificates_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_VERIFY_CERTIFICATES, state == Qt::Checked);
});
connect(m_dump_bba_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked);
});
#else
connect(m_dump_ssl_read_checkbox, &QCheckBox::stateChanged, [](int state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked);
});
@@ -227,6 +247,7 @@ void NetworkWidget::ConnectWidgets()
connect(m_dump_bba_checkbox, &QCheckBox::stateChanged, [](int state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked);
});
#endif
connect(m_open_dump_folder, &QPushButton::clicked, [] {
const std::string location = File::GetUserPath(D_DUMPSSL_IDX);
const QUrl url = QUrl::fromLocalFile(QString::fromStdString(location));

View File

@@ -77,8 +77,13 @@ NKitWarningDialog::NKitWarningDialog(QWidget* parent) : QDialog(parent)
connect(cancel, &QPushButton::clicked, this, &QDialog::reject);
ok->setEnabled(false);
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(checkbox_accept, &QCheckBox::checkStateChanged,
[ok](Qt::CheckState state) { ok->setEnabled(state == Qt::Checked); });
#else
connect(checkbox_accept, &QCheckBox::stateChanged,
[ok](int state) { ok->setEnabled(state == Qt::Checked); });
#endif
connect(this, &QDialog::accepted, [checkbox_skip] {
Config::SetBase(Config::MAIN_SKIP_NKIT_WARNING, checkbox_skip->isChecked());

View File

@@ -242,7 +242,11 @@ void NetPlaySetupDialog::ConnectWidgets()
&NetPlaySetupDialog::SaveSettings);
#ifdef USE_UPNP
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(m_host_upnp, &QCheckBox::checkStateChanged, this, &NetPlaySetupDialog::SaveSettings);
#else
connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings);
#endif
#endif
connect(m_connect_button, &QPushButton::clicked, this, &QDialog::accept);

View File

@@ -60,7 +60,11 @@ void PadMappingDialog::ConnectWidgets()
}
for (const auto& checkbox : m_gba_boxes)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(checkbox, &QCheckBox::checkStateChanged, this, &PadMappingDialog::OnMappingChanged);
#else
connect(checkbox, &QCheckBox::stateChanged, this, &PadMappingDialog::OnMappingChanged);
#endif
}
}

View File

@@ -13,6 +13,7 @@
#include <QLabel>
#include <QRadioButton>
#include <QSignalBlocker>
#include <QTimeZone>
#include <QVBoxLayout>
#include <cmath>
@@ -262,9 +263,18 @@ void AdvancedPane::CreateLayout()
QStringLiteral("mm"), QStringLiteral("mm:ss")));
QtUtils::ShowFourDigitYear(m_custom_rtc_datetime);
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
m_custom_rtc_datetime->setDateTimeRange(QDateTime({2000, 1, 1}, {0, 0, 0}, QTimeZone::UTC),
QDateTime({2099, 12, 31}, {23, 59, 59}, QTimeZone::UTC));
#else
m_custom_rtc_datetime->setDateTimeRange(QDateTime({2000, 1, 1}, {0, 0, 0}, Qt::UTC),
QDateTime({2099, 12, 31}, {23, 59, 59}, Qt::UTC));
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
m_custom_rtc_datetime->setTimeZone(QTimeZone::UTC);
#else
m_custom_rtc_datetime->setTimeSpec(Qt::UTC);
#endif
rtc_options->layout()->addWidget(m_custom_rtc_datetime);
m_custom_rtc_checkbox->SetDescription(

View File

@@ -241,7 +241,11 @@ void GameCubePane::CreateWidgets()
void GameCubePane::ConnectWidgets()
{
// IPL Settings
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(m_skip_main_menu, &QCheckBox::checkStateChanged, this, &GameCubePane::SaveSettings);
#else
connect(m_skip_main_menu, &QCheckBox::stateChanged, this, &GameCubePane::SaveSettings);
#endif
connect(m_language_combo, &QComboBox::currentIndexChanged, this, &GameCubePane::SaveSettings);
// Device Settings
@@ -272,10 +276,20 @@ void GameCubePane::ConnectWidgets()
#ifdef HAS_LIBMGBA
// GBA Settings
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(m_gba_threads, &QCheckBox::checkStateChanged, this, &GameCubePane::SaveSettings);
#else
connect(m_gba_threads, &QCheckBox::stateChanged, this, &GameCubePane::SaveSettings);
#endif
connect(m_gba_bios_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
connect(m_gba_browse_bios, &QPushButton::clicked, this, &GameCubePane::BrowseGBABios);
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(m_gba_save_rom_path, &QCheckBox::checkStateChanged, this,
&GameCubePane::SaveRomPathChanged);
#else
connect(m_gba_save_rom_path, &QCheckBox::stateChanged, this, &GameCubePane::SaveRomPathChanged);
#endif
connect(m_gba_saves_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
connect(m_gba_browse_saves, &QPushButton::clicked, this, &GameCubePane::BrowseGBASaves);
for (size_t i = 0; i < m_gba_browse_roms.size(); ++i)

View File

@@ -95,6 +95,8 @@ void GeneralPane::OnEmulationStateChanged(Core::State state)
m_checkbox_discord_presence->setEnabled(!running);
#endif
m_combobox_fallback_region->setEnabled(!running);
UpdateDescriptionsUsingHardcoreStatus();
}
void GeneralPane::ConnectLayout()
@@ -397,12 +399,6 @@ void GeneralPane::AddDescriptions()
"<br><br>This setting cannot be changed while emulation is active."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
#endif
static constexpr char TR_SPEEDLIMIT_DESCRIPTION[] =
QT_TR_NOOP("Controls how fast emulation runs relative to the original hardware."
"<br><br>Values higher than 100% will emulate faster than the original hardware "
"can run, if your hardware is able to keep up. Values lower than 100% will slow "
"emulation instead. Unlimited will emulate as fast as your hardware is able to."
"<br><br><dolphin_emphasis>If unsure, select 100%.</dolphin_emphasis>");
static constexpr char TR_UPDATE_TRACK_DESCRIPTION[] = QT_TR_NOOP(
"Selects which update track Dolphin uses when checking for updates at startup. If a new "
"update is available, Dolphin will show a list of changes made since your current version "
@@ -450,7 +446,6 @@ void GeneralPane::AddDescriptions()
#endif
m_combobox_speedlimit->SetTitle(tr("Speed Limit"));
m_combobox_speedlimit->SetDescription(tr(TR_SPEEDLIMIT_DESCRIPTION));
if (AutoUpdateChecker::SystemSupportsAutoUpdates())
{
@@ -468,3 +463,30 @@ void GeneralPane::AddDescriptions()
m_button_generate_new_identity->SetDescription(tr(TR_GENERATE_NEW_IDENTITY_DESCRIPTION));
#endif
}
void GeneralPane::UpdateDescriptionsUsingHardcoreStatus()
{
const bool hardcore_enabled = AchievementManager::GetInstance().IsHardcoreModeActive();
static constexpr char TR_SPEEDLIMIT_DESCRIPTION[] =
QT_TR_NOOP("Controls how fast emulation runs relative to the original hardware."
"<br><br>Values higher than 100% will emulate faster than the original hardware "
"can run, if your hardware is able to keep up. Values lower than 100% will slow "
"emulation instead. Unlimited will emulate as fast as your hardware is able to."
"<br><br><dolphin_emphasis>If unsure, select 100%.</dolphin_emphasis>");
static constexpr char TR_SPEEDLIMIT_RESTRICTION_IN_HARDCORE_DESCRIPTION[] =
QT_TR_NOOP("<dolphin_emphasis>When Hardcore Mode is enabled, Speed Limit values less than "
"100% will be treated as 100%.</dolphin_emphasis>");
if (hardcore_enabled)
{
m_combobox_speedlimit->SetDescription(
tr("%1<br><br>%2")
.arg(tr(TR_SPEEDLIMIT_DESCRIPTION))
.arg(tr(TR_SPEEDLIMIT_RESTRICTION_IN_HARDCORE_DESCRIPTION)));
}
else
{
m_combobox_speedlimit->SetDescription(tr(TR_SPEEDLIMIT_DESCRIPTION));
}
}

View File

@@ -39,6 +39,7 @@ private:
void LoadConfig();
void OnSaveConfig();
void OnEmulationStateChanged(Core::State state);
void UpdateDescriptionsUsingHardcoreStatus();
// Widgets
QVBoxLayout* m_main_layout;

View File

@@ -15,7 +15,11 @@ TASCheckBox::TASCheckBox(const QString& text, TASInputWindow* parent)
{
setTristate(true);
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(this, &TASCheckBox::checkStateChanged, this, &TASCheckBox::OnUIValueChanged);
#else
connect(this, &TASCheckBox::stateChanged, this, &TASCheckBox::OnUIValueChanged);
#endif
}
bool TASCheckBox::GetValue() const
@@ -58,7 +62,11 @@ void TASCheckBox::mousePressEvent(QMouseEvent* event)
setCheckState(Qt::PartiallyChecked);
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
void TASCheckBox::OnUIValueChanged(Qt::CheckState new_value)
#else
void TASCheckBox::OnUIValueChanged(int new_value)
#endif
{
m_state.OnUIValueChanged(new_value);
}

View File

@@ -25,7 +25,11 @@ protected:
void mousePressEvent(QMouseEvent* event) override;
private slots:
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
void OnUIValueChanged(Qt::CheckState new_value);
#else
void OnUIValueChanged(int new_value);
#endif
void ApplyControllerValueChange();
private:

View File

@@ -66,13 +66,7 @@ static void AddGCAdapter(libusb_device* device);
static void ResetRumbleLockNeeded();
#endif
enum class CalledFromReadThread
{
No,
Yes,
};
static void Reset(CalledFromReadThread called_from_read_thread);
static void Reset();
static void Setup();
static void ProcessInputPayload(const u8* data, std::size_t size);
static void ReadThreadFunc();
@@ -129,24 +123,22 @@ static std::atomic<int> s_controller_write_payload_size{0};
static std::thread s_read_adapter_thread;
static Common::Flag s_read_adapter_thread_running;
static Common::Flag s_read_adapter_thread_needs_joining;
static std::thread s_write_adapter_thread;
static Common::Flag s_write_adapter_thread_running;
static Common::Event s_write_happened;
static std::mutex s_read_mutex;
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
static std::mutex s_init_mutex;
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
static std::mutex s_read_mutex;
#if GCADAPTER_USE_ANDROID_IMPLEMENTATION
static std::mutex s_write_mutex;
#endif
static std::thread s_adapter_detect_thread;
static Common::Flag s_adapter_detect_thread_running;
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
static Common::Event s_hotplug_event;
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
static std::function<void(void)> s_detect_callback;
#if defined(__FreeBSD__) && __FreeBSD__ >= 11
@@ -180,14 +172,14 @@ static void ReadThreadFunc()
bool first_read = true;
JNIEnv* const env = IDCache::GetEnvForThread();
const jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controller_payload", "[B");
const jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controllerPayload", "[B");
jobject payload_object = env->GetStaticObjectField(s_adapter_class, payload_field);
auto* const java_controller_payload = reinterpret_cast<jbyteArray*>(&payload_object);
// Get function pointers
const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "GetFD", "()I");
const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "Input", "()I");
const jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z");
const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "getFd", "()I");
const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "input", "()I");
const jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z");
const bool connected = env->CallStaticBooleanMethod(s_adapter_class, openadapter_func);
@@ -279,7 +271,7 @@ static void WriteThreadFunc()
int size = 0;
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
const jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "Output", "([B)I");
const jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "output", "([B)I");
#endif
while (s_write_adapter_thread_running.IsSet())
@@ -331,7 +323,7 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
{
if (s_handle != nullptr && libusb_get_device(s_handle) == dev)
Reset(CalledFromReadThread::No);
Reset();
// Reset a potential error status now that the adapter is unplugged
if (s_status == AdapterStatus::Error)
@@ -344,6 +336,25 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
return 0;
}
#endif
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
extern "C" {
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterConnected(JNIEnv* env, jclass)
{
INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter connected");
if (!s_detected)
s_hotplug_event.Set();
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterDisconnected(JNIEnv* env, jclass)
{
INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter disconnected");
if (s_detected)
Reset();
}
}
#endif
static void ScanThreadFunc()
@@ -393,15 +404,23 @@ static void ScanThreadFunc()
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
const jmethodID queryadapter_func =
env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
const jmethodID enable_hotplug_callback_func =
env->GetStaticMethodID(s_adapter_class, "enableHotplugCallback", "()V");
env->CallStaticVoidMethod(s_adapter_class, enable_hotplug_callback_func);
const jmethodID is_usb_device_available_func =
env->GetStaticMethodID(s_adapter_class, "isUsbDeviceAvailable", "()Z");
while (s_adapter_detect_thread_running.IsSet())
{
if (!s_detected && UseAdapter() &&
env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func))
env->CallStaticBooleanMethod(s_adapter_class, is_usb_device_available_func))
{
std::lock_guard lk(s_init_mutex);
Setup();
Common::SleepCurrentThread(1000);
}
s_hotplug_event.Wait();
}
#endif
@@ -456,7 +475,7 @@ void Init()
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
const jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_GCAdapter");
const jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/GCAdapter");
s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
#endif
@@ -484,9 +503,7 @@ void StopScanThread()
{
if (s_adapter_detect_thread_running.TestAndClear())
{
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
s_hotplug_event.Set();
#endif
s_adapter_detect_thread.join();
}
}
@@ -523,11 +540,8 @@ static void Setup()
s_detected = true;
// Make sure the thread isn't in the middle of shutting down while starting a new one
if (s_read_adapter_thread_needs_joining.TestAndClear() ||
s_read_adapter_thread_running.TestAndClear())
{
if (s_read_adapter_thread_running.TestAndClear())
s_read_adapter_thread.join();
}
s_read_adapter_thread_running.Set(true);
s_read_adapter_thread = std::thread(ReadThreadFunc);
@@ -691,8 +705,13 @@ void Shutdown()
if (s_libusb_context && s_libusb_context->IsValid() && s_libusb_hotplug_enabled)
libusb_hotplug_deregister_callback(*s_libusb_context, s_hotplug_handle);
#endif
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
const jmethodID disable_hotplug_callback_func =
env->GetStaticMethodID(s_adapter_class, "disableHotplugCallback", "()V");
env->CallStaticVoidMethod(s_adapter_class, disable_hotplug_callback_func);
#endif
Reset(CalledFromReadThread::No);
Reset();
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
s_libusb_context.reset();
@@ -706,12 +725,12 @@ void Shutdown()
}
}
static void Reset(CalledFromReadThread called_from_read_thread)
static void Reset()
{
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
std::unique_lock lock(s_init_mutex, std::defer_lock);
if (!lock.try_lock())
return;
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
if (s_status != AdapterStatus::Detected)
return;
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
@@ -719,16 +738,8 @@ static void Reset(CalledFromReadThread called_from_read_thread)
return;
#endif
if (called_from_read_thread == CalledFromReadThread::No)
{
if (s_read_adapter_thread_running.TestAndClear())
s_read_adapter_thread.join();
}
else
{
s_read_adapter_thread_needs_joining.Set();
s_read_adapter_thread_running.Clear();
}
if (s_read_adapter_thread_running.TestAndClear())
s_read_adapter_thread.join();
// The read thread will close the write thread
s_port_states.fill({});
@@ -807,9 +818,6 @@ void ProcessInputPayload(const u8* data, std::size_t size)
// This can occur for a few frames on initialization.
ERROR_LOG_FMT(CONTROLLERINTERFACE, "error reading payload (size: {}, type: {:02x})", size,
data[0]);
#if GCADAPTER_USE_ANDROID_IMPLEMENTATION
Reset(CalledFromReadThread::Yes);
#endif
}
else
{