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.ActivityTracker;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; import org.dolphinemu.dolphinemu.utils.GCAdapter;
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; import org.dolphinemu.dolphinemu.utils.WiimoteAdapter;
import org.dolphinemu.dolphinemu.utils.VolleyUtil; import org.dolphinemu.dolphinemu.utils.VolleyUtil;
public class DolphinApplication extends Application public class DolphinApplication extends Application
@@ -28,8 +28,8 @@ public class DolphinApplication extends Application
VolleyUtil.init(getApplicationContext()); VolleyUtil.init(getApplicationContext());
System.loadLibrary("main"); System.loadLibrary("main");
Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (DirectoryInitialization.shouldStart(getApplicationContext())) if (DirectoryInitialization.shouldStart(getApplicationContext()))
DirectoryInitialization.start(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.HashMap;
import java.util.Map; import java.util.Map;
public class Java_WiimoteAdapter public class WiimoteAdapter
{ {
final static int MAX_PAYLOAD = 23; final static int MAX_PAYLOAD = 23;
final static int MAX_WIIMOTES = 4; final static int MAX_WIIMOTES = 4;
@@ -31,14 +31,14 @@ public class Java_WiimoteAdapter
final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306; final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306;
public static UsbManager manager; public static UsbManager manager;
static UsbDeviceConnection usb_con; static UsbDeviceConnection usbConnection;
static UsbInterface[] usb_intf = new UsbInterface[MAX_WIIMOTES]; static UsbInterface[] usbInterface = new UsbInterface[MAX_WIIMOTES];
static UsbEndpoint[] usb_in = new UsbEndpoint[MAX_WIIMOTES]; static UsbEndpoint[] usbIn = new UsbEndpoint[MAX_WIIMOTES];
@Keep @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(); HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet()) for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
@@ -65,7 +65,7 @@ public class Java_WiimoteAdapter
} }
@Keep @Keep
public static boolean QueryAdapter() public static boolean queryAdapter()
{ {
HashMap<String, UsbDevice> devices = manager.getDeviceList(); HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet()) for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
@@ -77,20 +77,20 @@ public class Java_WiimoteAdapter
if (manager.hasPermission(dev)) if (manager.hasPermission(dev))
return true; return true;
else else
RequestPermission(); requestPermission();
} }
} }
return false; return false;
} }
@Keep @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 @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]; byte report_number = buf[0];
@@ -105,7 +105,7 @@ public class Java_WiimoteAdapter
final int HID_SET_REPORT = 0x9; final int HID_SET_REPORT = 0x9;
final int HID_OUTPUT = (2 << 8); 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, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
HID_SET_REPORT, HID_SET_REPORT,
HID_OUTPUT | report_number, HID_OUTPUT | report_number,
@@ -120,10 +120,10 @@ public class Java_WiimoteAdapter
} }
@Keep @Keep
public static boolean OpenAdapter() public static boolean openAdapter()
{ {
// If the adapter is already open. Don't attempt to do it again // 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; return true;
HashMap<String, UsbDevice> devices = manager.getDeviceList(); HashMap<String, UsbDevice> devices = manager.getDeviceList();
@@ -135,7 +135,7 @@ public class Java_WiimoteAdapter
{ {
if (manager.hasPermission(dev)) if (manager.hasPermission(dev))
{ {
usb_con = manager.openDevice(dev); usbConnection = manager.openDevice(dev);
UsbConfiguration conf = dev.getConfiguration(0); UsbConfiguration conf = dev.getConfiguration(0);
Log.info("Number of configurations: " + dev.getConfigurationCount()); Log.info("Number of configurations: " + dev.getConfigurationCount());
@@ -149,20 +149,20 @@ public class Java_WiimoteAdapter
for (int i = 0; i < MAX_WIIMOTES; ++i) for (int i = 0; i < MAX_WIIMOTES; ++i)
{ {
// One interface per Wii Remote // One interface per Wii Remote
usb_intf[i] = dev.getInterface(i); usbInterface[i] = dev.getInterface(i);
usb_con.claimInterface(usb_intf[i], true); usbConnection.claimInterface(usbInterface[i], true);
// One endpoint per Wii Remote. Input only // One endpoint per Wii Remote. Input only
// Output reports go through the control channel. // Output reports go through the control channel.
usb_in[i] = usb_intf[i].getEndpoint(0); usbIn[i] = usbInterface[i].getEndpoint(0);
Log.info("Interface " + i + " endpoint count:" + usb_intf[i].getEndpointCount()); Log.info("Interface " + i + " endpoint count:" + usbInterface[i].getEndpointCount());
} }
return true; return true;
} }
else else
{ {
// XXX: Message that the device was found, but it needs to be unplugged and plugged back in? // 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) for (auto& code : codes)
{ {
if (code.enabled && !CheckApprovedCode(code, game_id, revision)) if (code.enabled && !IsApprovedCode(code, game_id, revision))
code.enabled = false; code.enabled = false;
} }
} }
template <typename T> template <typename T>
bool AchievementManager::CheckApprovedCode(const T& code, const std::string& game_id, bool AchievementManager::ShouldCodeBeActivated(const T& code, const std::string& game_id,
u16 revision) const u16 revision) const
{ {
if (!code.enabled)
return false;
if (!IsHardcoreModeActive()) if (!IsHardcoreModeActive())
return true; return true;
@@ -468,33 +471,42 @@ bool AchievementManager::CheckApprovedCode(const T& code, const std::string& gam
INFO_LOG_FMT(ACHIEVEMENTS, "Verifying code {}", code.name); 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)) 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)) 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)) if (ini_config.is<picojson::object>() && ini_config.contains(code.name))
{ {
auto ini_code = ini_config.get(code.name); const auto ini_code = ini_config.get(code.name);
if (ini_code.template is<std::string>()) if (ini_code.template is<std::string>() && ini_code.template get<std::string>() == hash)
verified = (ini_code.template get<std::string>() == hash); return true;
} }
} }
} }
return false;
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;
} }
Common::SHA1::Digest AchievementManager::GetCodeHash(const PatchEngine::Patch& patch) const 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); FilterApprovedIni(codes, game_id, revision);
} }
bool AchievementManager::CheckApprovedGeckoCode(const Gecko::GeckoCode& code, bool AchievementManager::ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code,
const std::string& game_id, u16 revision) const 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 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() void AchievementManager::SetSpectatorMode()

View File

@@ -149,10 +149,14 @@ public:
u16 revision) const; u16 revision) const;
void FilterApprovedARCodes(std::vector<ActionReplay::ARCode>& codes, const std::string& game_id, void FilterApprovedARCodes(std::vector<ActionReplay::ARCode>& codes, const std::string& game_id,
u16 revision) const; u16 revision) const;
bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id, bool ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code, const std::string& game_id,
u16 revision) const; u16 revision) const;
bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id, 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; u16 revision) const;
bool IsApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id,
u16 revision) const;
void SetSpectatorMode(); void SetSpectatorMode();
std::string_view GetPlayerDisplayName() const; std::string_view GetPlayerDisplayName() const;
@@ -220,7 +224,9 @@ private:
template <typename T> template <typename T>
void FilterApprovedIni(std::vector<T>& codes, const std::string& game_id, u16 revision) const; void FilterApprovedIni(std::vector<T>& codes, const std::string& game_id, u16 revision) const;
template <typename T> 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 PatchEngine::Patch& patch) const;
Common::SHA1::Digest GetCodeHash(const Gecko::GeckoCode& code) const; Common::SHA1::Digest GetCodeHash(const Gecko::GeckoCode& code) const;
Common::SHA1::Digest GetCodeHash(const ActionReplay::ARCode& code) const; Common::SHA1::Digest GetCodeHash(const ActionReplay::ARCode& code) const;
@@ -333,14 +339,14 @@ public:
constexpr bool IsHardcoreModeActive() { return false; } constexpr bool IsHardcoreModeActive() { return false; }
constexpr bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id, constexpr bool ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code,
u16 revision) const std::string& game_id, u16 revision)
{ {
return true; return true;
} }
constexpr bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id, constexpr bool ShouldARCodeBeActivated(const ActionReplay::ARCode& code,
u16 revision) const std::string& game_id, u16 revision)
{ {
return true; 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); std::lock_guard guard(s_lock);
s_disable_logging = false; s_disable_logging = false;
s_active_codes.clear(); s_active_codes.clear();
std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes),
[&game_id, &revision](const ARCode& code) { const auto should_be_activated = [&game_id, &revision](const ARCode& code) {
return code.enabled && AchievementManager::GetInstance().CheckApprovedARCode( return AchievementManager::GetInstance().ShouldARCodeBeActivated(code, game_id, revision);
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(); 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()); 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), std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes),
[&game_id, &revision](const GeckoCode& code) { should_be_activated);
return code.enabled && AchievementManager::GetInstance().CheckApprovedGeckoCode(
code, game_id, revision);
});
} }
s_active_codes.shrink_to_fit(); s_active_codes.shrink_to_fit();

View File

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

View File

@@ -10,6 +10,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "Common/BitSet.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Core/Core.h" #include "Core/Core.h"
@@ -341,8 +342,13 @@ void MemChecks::Update()
{ {
const Core::CPUThreadGuard guard(m_system); const Core::CPUThreadGuard guard(m_system);
// Clear the JIT cache so it can switch the watchpoint-compatible mode. const bool registers_changed = UpdateRegistersUsedInConditions();
if (m_mem_breakpoints_set != HasAny())
// 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_system.GetJitInterface().ClearCache(guard);
m_mem_breakpoints_set = HasAny(); m_mem_breakpoints_set = HasAny();
@@ -351,6 +357,27 @@ void MemChecks::Update()
m_system.GetMMU().DBATUpdated(); 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) TMemCheck* MemChecks::GetMemCheck(u32 address, size_t size)
{ {
const auto iter = std::ranges::find_if(m_mem_checks, [address, size](const auto& mc) { const auto iter = std::ranges::find_if(m_mem_checks, [address, size](const auto& mc) {

View File

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

View File

@@ -25,6 +25,7 @@ using std::isinf;
using std::isnan; using std::isnan;
#include <expr.h> #include <expr.h>
#include "Common/BitSet.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Core/Core.h" #include "Core/Core.h"
@@ -503,3 +504,26 @@ std::string Expression::GetText() const
{ {
return m_text; 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 <string_view>
#include <vector> #include <vector>
#include "Common/BitSet.h"
struct expr; struct expr;
struct expr_var_list; struct expr_var_list;
@@ -41,6 +43,18 @@ public:
std::string GetText() const; std::string GetText() const;
BitSet32 GetGPRsUsed()
{
ComputeRegistersUsed();
return m_gprs_used;
}
BitSet32 GetFPRsUsed()
{
ComputeRegistersUsed();
return m_fprs_used;
}
private: private:
enum class SynchronizeDirection enum class SynchronizeDirection
{ {
@@ -69,10 +83,16 @@ private:
void SynchronizeBindings(Core::System& system, SynchronizeDirection dir) const; void SynchronizeBindings(Core::System& system, SynchronizeDirection dir) const;
void Reporting(const double result) const; void Reporting(const double result) const;
void ComputeRegistersUsed();
std::string m_text; std::string m_text;
ExprPointer m_expr; ExprPointer m_expr;
ExprVarListPointer m_vars; ExprVarListPointer m_vars;
std::vector<VarBinding> m_binds; 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) 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) bool Jit64::HandleFunctionHooking(u32 address)
{ {
const auto result = HLE::TryReplaceFunction(m_ppc_symbol_db, address, PowerPC::CoreMode::JIT); const auto result = HLE::TryReplaceFunction(m_ppc_symbol_db, address, PowerPC::CoreMode::JIT);

View File

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

View File

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

View File

@@ -30,6 +30,8 @@ void Jit64::lfXXX(UGeckoInstruction inst)
FALLBACK_IF(!indexed && !a); FALLBACK_IF(!indexed && !a);
FlushRegistersBeforeSlowAccess();
s32 offset = 0; s32 offset = 0;
RCOpArg addr = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read); RCOpArg addr = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read);
RegCache::Realize(addr); RegCache::Realize(addr);
@@ -103,6 +105,8 @@ void Jit64::stfXXX(UGeckoInstruction inst)
FALLBACK_IF(update && jo.memcheck && indexed && a == b); FALLBACK_IF(update && jo.memcheck && indexed && a == b);
FlushRegistersBeforeSlowAccess();
if (single) if (single)
{ {
if (js.fpr_is_store_safe[s] && js.op->fprIsSingle[s]) 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 a = inst.RA;
int b = inst.RB; int b = inst.RB;
FlushRegistersBeforeSlowAccess();
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0); RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
RCOpArg Rb = gpr.Use(b, RCMode::Read); RCOpArg Rb = gpr.Use(b, RCMode::Read);
RCOpArg Rs = fpr.Use(s, 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; int w = indexed ? inst.Wx : inst.W;
FALLBACK_IF(!a); FALLBACK_IF(!a);
FlushRegistersBeforeSlowAccess();
RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA); RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA);
RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read); 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); RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset);
@@ -69,8 +71,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
} }
else else
{ {
// Stash PC in case asm routine needs to call into C++ FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
// We know what GQR is here, so we can load RSCRATCH2 and call into the store method directly // We know what GQR is here, so we can load RSCRATCH2 and call into the store method directly
// with just the scale bits. // with just the scale bits.
MOV(32, R(RSCRATCH2), Imm32(gqrValue & 0x3F00)); MOV(32, R(RSCRATCH2), Imm32(gqrValue & 0x3F00));
@@ -83,8 +85,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
} }
else else
{ {
// Stash PC in case asm routine needs to call into C++ FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
// Some games (e.g. Dirt 2) incorrectly set the unused bits which breaks the lookup table code. // 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 // 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 // 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; int w = indexed ? inst.Wx : inst.W;
FALLBACK_IF(!a); FALLBACK_IF(!a);
FlushRegistersBeforeSlowAccess();
RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA); RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA);
RCX64Reg Ra = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read); RCX64Reg Ra = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read);
RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset); RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset);
@@ -144,8 +148,8 @@ void Jit64::psq_lXX(UGeckoInstruction inst)
} }
else else
{ {
// Stash PC in case asm routine needs to call into C++ FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
// Get the high part of the GQR register // Get the high part of the GQR register
OpArg gqr = PPCSTATE_SPR(SPR_GQR0 + i); OpArg gqr = PPCSTATE_SPR(SPR_GQR0 + i);
gqr.AddMemOffset(2); 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); 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) FixupBranch EmuCodeBlock::BATAddressLookup(X64Reg addr, X64Reg tmp, const void* bat_table)
{ {
MOV(64, R(tmp), ImmPtr(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); SetJumpTarget(slow);
} }
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe // In the case of Jit64AsmCommon routines, the state we want to store here isn't known
// interrupt checks, and printing accurate PC locations in debug logs. // when compiling the routine, so the caller has to store it themselves.
//
// In the case of Jit64AsmCommon routines, we don't know the PC here,
// so the caller has to store the PC themselves.
if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC)) 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; 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; return;
} }
// Helps external systems know which instruction triggered the read. FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
// Fall back to general-case code. // Fall back to general-case code.
ABI_PushRegistersAndAdjustStack(registersInUse, 0); ABI_PushRegistersAndAdjustStack(registersInUse, 0);
@@ -560,14 +563,11 @@ void EmuCodeBlock::SafeWriteRegToReg(OpArg reg_value, X64Reg reg_addr, int acces
SetJumpTarget(slow); SetJumpTarget(slow);
} }
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe // In the case of Jit64AsmCommon routines, the state we want to store here isn't known
// interrupt checks, and printing accurate PC locations in debug logs. // when compiling the routine, so the caller has to store it themselves.
//
// In the case of Jit64AsmCommon routines, we don't know the PC here,
// so the caller has to store the PC themselves.
if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC)) 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; 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 else
{ {
// Helps external systems know which instruction triggered the write FlushPCBeforeSlowAccess();
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
ABI_PushRegistersAndAdjustStack(registersInUse, 0); ABI_PushRegistersAndAdjustStack(registersInUse, 0);
switch (accessSize) switch (accessSize)

View File

@@ -49,6 +49,8 @@ public:
return Gen::M(m_const_pool.GetConstant(&value, sizeof(T), N, index)); 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. // 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. // Jumps to the returned FixupBranch if lookup fails.
Gen::FixupBranch BATAddressLookup(Gen::X64Reg addr, Gen::X64Reg tmp, const void* bat_table); 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 != AlwaysSlowAccess && !jo.fastmem: X30
// !emitting_routine && mode == Auto && 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 // Furthermore:
// may be clobbered if mode != AlwaysFastAccess. // - 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, void EmitBackpatchRoutine(u32 flags, MemAccessMode mode, Arm64Gen::ARM64Reg RS,
Arm64Gen::ARM64Reg addr, BitSet32 gprs_to_push = BitSet32(0), Arm64Gen::ARM64Reg addr, BitSet32 gprs_to_push = BitSet32(0),
BitSet32 fprs_to_push = BitSet32(0), bool emitting_routine = false); 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 // Loadstore routines
void SafeLoadToReg(u32 dest, s32 addr, s32 offsetReg, u32 flags, s32 offset, bool update); 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); 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/JitArmCommon/BackPatch.h"
#include "Core/PowerPC/MMU.h" #include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
using namespace Arm64Gen; using namespace Arm64Gen;
@@ -171,6 +172,7 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
const ARM64Reg temp_gpr = ARM64Reg::W1; const ARM64Reg temp_gpr = ARM64Reg::W1;
const int temp_gpr_index = DecodeReg(temp_gpr); 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 = {}; BitSet32 gprs_to_push_early = {};
if (memcheck) if (memcheck)
@@ -189,16 +191,10 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
ABI_PushRegisters(gprs_to_push & ~gprs_to_push_early); ABI_PushRegisters(gprs_to_push & ~gprs_to_push_early);
m_float_emit.ABI_PushRegisters(fprs_to_push, ARM64Reg::X30); m_float_emit.ABI_PushRegisters(fprs_to_push, ARM64Reg::X30);
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe // In the case of JitAsm routines, the state we want to store here isn't known
// interrupt checks, and printing accurate PC locations in debug logs. // when compiling the routine, so the caller has to store it themselves.
//
// In the case of JitAsm routines, we don't know the PC here,
// so the caller has to store the PC themselves.
if (!emitting_routine) if (!emitting_routine)
{ FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, temp_fpr);
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
}
if (flags & BackPatchInfo::FLAG_STORE) if (flags & BackPatchInfo::FLAG_STORE)
{ {
@@ -265,7 +261,6 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
if (memcheck) 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_count = (gprs_to_push & gprs_to_push_early).Count();
const u64 early_push_size = Common::AlignUp(early_push_count, 2) * 8; 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) bool JitArm64::HandleFastmemFault(SContext* ctx)
{ {
const u8* pc = reinterpret_cast<const u8*>(ctx->CTX_PC); 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)); LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i));
// Stash PC in case asm routine needs to call into C++ FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, ARM64Reg::Q1);
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
UBFM(type_reg, scale_reg, 16, 18); // Type UBFM(type_reg, scale_reg, 16, 18); // Type
UBFM(scale_reg, scale_reg, 24, 29); // Scale 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)); LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i));
// Stash PC in case asm routine needs to call into C++ FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, ARM64Reg::Q1);
MOVI2R(ARM64Reg::W30, js.compilerPC);
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
UBFM(type_reg, scale_reg, 0, 2); // Type UBFM(type_reg, scale_reg, 0, 2); // Type
UBFM(scale_reg, scale_reg, 8, 13); // Scale UBFM(scale_reg, scale_reg, 8, 13); // Scale

View File

@@ -388,14 +388,16 @@ public:
BitSet32 GetDirtyGPRs() const; 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); void DiscardCRRegisters(BitSet8 regs);
@@ -459,9 +461,10 @@ public:
void FixSinglePrecision(size_t preg); 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: protected:

View File

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

View File

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

View File

@@ -140,6 +140,16 @@ void HacksWidget::OnBackendChanged(const QString& backend_name)
void HacksWidget::ConnectWidgets() 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, connect(m_store_efb_copies, &QCheckBox::stateChanged,
[this](int) { UpdateDeferEFBCopiesEnabled(); }); [this](int) { UpdateDeferEFBCopiesEnabled(); });
connect(m_store_xfb_copies, &QCheckBox::stateChanged, connect(m_store_xfb_copies, &QCheckBox::stateChanged,
@@ -148,6 +158,7 @@ void HacksWidget::ConnectWidgets()
[this](int) { UpdateSkipPresentingDuplicateFramesEnabled(); }); [this](int) { UpdateSkipPresentingDuplicateFramesEnabled(); });
connect(m_vi_skip, &QCheckBox::stateChanged, connect(m_vi_skip, &QCheckBox::stateChanged,
[this](int) { UpdateSkipPresentingDuplicateFramesEnabled(); }); [this](int) { UpdateSkipPresentingDuplicateFramesEnabled(); });
#endif
} }
void HacksWidget::AddDescriptions() void HacksWidget::AddDescriptions()

View File

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

View File

@@ -5,11 +5,11 @@
#include <memory> #include <memory>
#include <QApplication>
#include <QBitmap> #include <QBitmap>
#include <QBrush> #include <QBrush>
#include <QCursor> #include <QCursor>
#include <QFont> #include <QFont>
#include <QGuiApplication>
#include <QLabel> #include <QLabel>
#include <QPainter> #include <QPainter>
#include <QPainterPath> #include <QPainterPath>
@@ -25,11 +25,17 @@
#include <QToolTip> #include <QToolTip>
#endif #endif
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
namespace namespace
{ {
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr; 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 } // namespace
void BalloonTip::ShowBalloon(const QString& title, const QString& message, 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() void BalloonTip::HideBalloon()
{ {
s_parent = nullptr;
#if defined(__APPLE__) #if defined(__APPLE__)
QToolTip::hideText(); QToolTip::hideText();
#else #else
@@ -66,6 +73,9 @@ void BalloonTip::HideBalloon()
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent) BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
: QWidget(nullptr, Qt::ToolTip) : QWidget(nullptr, Qt::ToolTip)
{ {
s_parent = parent;
setMouseTracking(true);
QColor window_color; QColor window_color;
QColor text_color; QColor text_color;
QColor dolphin_emphasis; QColor dolphin_emphasis;
@@ -113,10 +123,61 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
create_label(message); 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); QPainter painter(this);
painter.drawPixmap(rect(), m_pixmap); painter.drawPixmap(rect(), m_pixmap);
QWidget::paintEvent(event);
} }
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,

View File

@@ -7,6 +7,9 @@
#include <QPixmap> #include <QPixmap>
#include <QWidget> #include <QWidget>
class QEnterEvent;
class QEvent;
class QMouseEvent;
class QPaintEvent; class QPaintEvent;
class QPoint; class QPoint;
class QString; class QString;
@@ -29,17 +32,22 @@ public:
const QPoint& target_arrow_tip_position, QWidget* parent, const QPoint& target_arrow_tip_position, QWidget* parent,
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1); ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
static void HideBalloon(); 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); 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: private:
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow, void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
int border_width); int border_width);
protected:
void paintEvent(QPaintEvent*) override;
private:
QColor m_border_color; QColor m_border_color;
QPixmap m_pixmap; QPixmap m_pixmap;
}; };

View File

@@ -9,6 +9,11 @@
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h" #include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
class QEnterEvent;
class QEvent;
class QHideEvent;
class QTimerEvent;
constexpr int TOOLTIP_DELAY = 300; constexpr int TOOLTIP_DELAY = 300;
template <class Derived> template <class Derived>
@@ -22,28 +27,48 @@ public:
void SetDescription(QString description) { m_description = std::move(description); } void SetDescription(QString description) { m_description = std::move(description); }
private: private:
void enterEvent(QEnterEvent* event) override void enterEvent(QEnterEvent* const event) override
{ {
if (m_timer_id) // If the timer is already running, or the cursor is reentering the ToolTipWidget after having
return; // hovered over the BalloonTip, don't start a new timer.
m_timer_id = this->startTimer(TOOLTIP_DELAY); if (!m_timer_id && !BalloonTip::IsWidgetBalloonTipActive(*this))
m_timer_id = this->startTimer(TOOLTIP_DELAY);
Derived::enterEvent(event);
} }
void leaveEvent(QEvent* event) override { KillAndHide(); } void leaveEvent(QEvent* const event) override
void hideEvent(QHideEvent* event) override { KillAndHide(); } {
// 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); this->killTimer(*m_timer_id);
m_timer_id.reset(); m_timer_id.reset();
BalloonTip::ShowBalloon(m_title, m_description, BalloonTip::ShowBalloon(m_title, m_description,
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this); this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
Derived::timerEvent(event);
} }
virtual QPoint GetToolTipPosition() const = 0; virtual QPoint GetToolTipPosition() const = 0;
void KillAndHide() void KillTimerAndHideBalloon()
{ {
if (m_timer_id) if (m_timer_id)
{ {

View File

@@ -120,8 +120,13 @@ void VerifyWidget::ConnectWidgets()
{ {
connect(m_verify_button, &QPushButton::clicked, this, &VerifyWidget::Verify); 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_md5_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
connect(m_sha1_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) 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 // Confirm it is only hex bytes
const QRegularExpression is_hex(QStringLiteral("^([0-9A-F]{2})*$"), const QRegularExpression is_hex(QStringLiteral("^([0-9A-F]{2})*$"),
QRegularExpression::CaseInsensitiveOption); 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); const QRegularExpressionMatch match = is_hex.match(input_text);
#endif
good = match.hasMatch(); good = match.hasMatch();
if (good) if (good)
{ {

View File

@@ -209,6 +209,26 @@ void NetworkWidget::ConnectWidgets()
{ {
connect(m_dump_format_combo, &QComboBox::currentIndexChanged, this, connect(m_dump_format_combo, &QComboBox::currentIndexChanged, this,
&NetworkWidget::OnDumpFormatComboChanged); &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) { connect(m_dump_ssl_read_checkbox, &QCheckBox::stateChanged, [](int state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked); 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) { connect(m_dump_bba_checkbox, &QCheckBox::stateChanged, [](int state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked); Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked);
}); });
#endif
connect(m_open_dump_folder, &QPushButton::clicked, [] { connect(m_open_dump_folder, &QPushButton::clicked, [] {
const std::string location = File::GetUserPath(D_DUMPSSL_IDX); const std::string location = File::GetUserPath(D_DUMPSSL_IDX);
const QUrl url = QUrl::fromLocalFile(QString::fromStdString(location)); 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); connect(cancel, &QPushButton::clicked, this, &QDialog::reject);
ok->setEnabled(false); 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, connect(checkbox_accept, &QCheckBox::stateChanged,
[ok](int state) { ok->setEnabled(state == Qt::Checked); }); [ok](int state) { ok->setEnabled(state == Qt::Checked); });
#endif
connect(this, &QDialog::accepted, [checkbox_skip] { connect(this, &QDialog::accepted, [checkbox_skip] {
Config::SetBase(Config::MAIN_SKIP_NKIT_WARNING, checkbox_skip->isChecked()); Config::SetBase(Config::MAIN_SKIP_NKIT_WARNING, checkbox_skip->isChecked());

View File

@@ -242,7 +242,11 @@ void NetPlaySetupDialog::ConnectWidgets()
&NetPlaySetupDialog::SaveSettings); &NetPlaySetupDialog::SaveSettings);
#ifdef USE_UPNP #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); connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings);
#endif
#endif #endif
connect(m_connect_button, &QPushButton::clicked, this, &QDialog::accept); 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) 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); connect(checkbox, &QCheckBox::stateChanged, this, &PadMappingDialog::OnMappingChanged);
#endif
} }
} }

View File

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

View File

@@ -241,7 +241,11 @@ void GameCubePane::CreateWidgets()
void GameCubePane::ConnectWidgets() void GameCubePane::ConnectWidgets()
{ {
// IPL Settings // 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); connect(m_skip_main_menu, &QCheckBox::stateChanged, this, &GameCubePane::SaveSettings);
#endif
connect(m_language_combo, &QComboBox::currentIndexChanged, this, &GameCubePane::SaveSettings); connect(m_language_combo, &QComboBox::currentIndexChanged, this, &GameCubePane::SaveSettings);
// Device Settings // Device Settings
@@ -272,10 +276,20 @@ void GameCubePane::ConnectWidgets()
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA
// GBA Settings // 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); connect(m_gba_threads, &QCheckBox::stateChanged, this, &GameCubePane::SaveSettings);
#endif
connect(m_gba_bios_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings); connect(m_gba_bios_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
connect(m_gba_browse_bios, &QPushButton::clicked, this, &GameCubePane::BrowseGBABios); 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); 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_saves_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
connect(m_gba_browse_saves, &QPushButton::clicked, this, &GameCubePane::BrowseGBASaves); connect(m_gba_browse_saves, &QPushButton::clicked, this, &GameCubePane::BrowseGBASaves);
for (size_t i = 0; i < m_gba_browse_roms.size(); ++i) 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); m_checkbox_discord_presence->setEnabled(!running);
#endif #endif
m_combobox_fallback_region->setEnabled(!running); m_combobox_fallback_region->setEnabled(!running);
UpdateDescriptionsUsingHardcoreStatus();
} }
void GeneralPane::ConnectLayout() void GeneralPane::ConnectLayout()
@@ -397,12 +399,6 @@ void GeneralPane::AddDescriptions()
"<br><br>This setting cannot be changed while emulation is active." "<br><br>This setting cannot be changed while emulation is active."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>"); "<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
#endif #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( 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 " "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 " "update is available, Dolphin will show a list of changes made since your current version "
@@ -450,7 +446,6 @@ void GeneralPane::AddDescriptions()
#endif #endif
m_combobox_speedlimit->SetTitle(tr("Speed Limit")); m_combobox_speedlimit->SetTitle(tr("Speed Limit"));
m_combobox_speedlimit->SetDescription(tr(TR_SPEEDLIMIT_DESCRIPTION));
if (AutoUpdateChecker::SystemSupportsAutoUpdates()) if (AutoUpdateChecker::SystemSupportsAutoUpdates())
{ {
@@ -468,3 +463,30 @@ void GeneralPane::AddDescriptions()
m_button_generate_new_identity->SetDescription(tr(TR_GENERATE_NEW_IDENTITY_DESCRIPTION)); m_button_generate_new_identity->SetDescription(tr(TR_GENERATE_NEW_IDENTITY_DESCRIPTION));
#endif #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 LoadConfig();
void OnSaveConfig(); void OnSaveConfig();
void OnEmulationStateChanged(Core::State state); void OnEmulationStateChanged(Core::State state);
void UpdateDescriptionsUsingHardcoreStatus();
// Widgets // Widgets
QVBoxLayout* m_main_layout; QVBoxLayout* m_main_layout;

View File

@@ -15,7 +15,11 @@ TASCheckBox::TASCheckBox(const QString& text, TASInputWindow* parent)
{ {
setTristate(true); 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); connect(this, &TASCheckBox::stateChanged, this, &TASCheckBox::OnUIValueChanged);
#endif
} }
bool TASCheckBox::GetValue() const bool TASCheckBox::GetValue() const
@@ -58,7 +62,11 @@ void TASCheckBox::mousePressEvent(QMouseEvent* event)
setCheckState(Qt::PartiallyChecked); 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) void TASCheckBox::OnUIValueChanged(int new_value)
#endif
{ {
m_state.OnUIValueChanged(new_value); m_state.OnUIValueChanged(new_value);
} }

View File

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

View File

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