mirror of
https://github.com/dolphin-emu/dolphin
synced 2025-10-05 16:03:02 +02:00
Compare commits
18 Commits
ba2acb872c
...
e5fbc74156
Author | SHA1 | Date | |
---|---|---|---|
|
e5fbc74156 | ||
|
e1c7734ee4 | ||
|
cbdb7ac38e | ||
|
79a98b8235 | ||
|
a93754cb71 | ||
|
72397ccd87 | ||
|
44f6743a5b | ||
|
7b52555a5f | ||
|
8524e725a8 | ||
|
1c7df370d9 | ||
|
185569778c | ||
|
e2e33becc9 | ||
|
7508842859 | ||
|
02e32ffaaf | ||
|
a06bc5417d | ||
|
78afea1312 | ||
|
49c72efcd3 | ||
|
55cfb958c7 |
@@ -9,8 +9,8 @@ import android.hardware.usb.UsbManager;
|
||||
|
||||
import org.dolphinemu.dolphinemu.utils.ActivityTracker;
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
|
||||
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.GCAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.WiimoteAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.VolleyUtil;
|
||||
|
||||
public class DolphinApplication extends Application
|
||||
@@ -28,8 +28,8 @@ public class DolphinApplication extends Application
|
||||
VolleyUtil.init(getApplicationContext());
|
||||
System.loadLibrary("main");
|
||||
|
||||
Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
|
||||
if (DirectoryInitialization.shouldStart(getApplicationContext()))
|
||||
DirectoryInitialization.start(getApplicationContext());
|
||||
|
@@ -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();
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Java_WiimoteAdapter
|
||||
public class WiimoteAdapter
|
||||
{
|
||||
final static int MAX_PAYLOAD = 23;
|
||||
final static int MAX_WIIMOTES = 4;
|
||||
@@ -31,14 +31,14 @@ public class Java_WiimoteAdapter
|
||||
final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306;
|
||||
public static UsbManager manager;
|
||||
|
||||
static UsbDeviceConnection usb_con;
|
||||
static UsbInterface[] usb_intf = new UsbInterface[MAX_WIIMOTES];
|
||||
static UsbEndpoint[] usb_in = new UsbEndpoint[MAX_WIIMOTES];
|
||||
static UsbDeviceConnection usbConnection;
|
||||
static UsbInterface[] usbInterface = new UsbInterface[MAX_WIIMOTES];
|
||||
static UsbEndpoint[] usbIn = new UsbEndpoint[MAX_WIIMOTES];
|
||||
|
||||
@Keep
|
||||
public static byte[][] wiimote_payload = new byte[MAX_WIIMOTES][MAX_PAYLOAD];
|
||||
public static byte[][] wiimotePayload = new byte[MAX_WIIMOTES][MAX_PAYLOAD];
|
||||
|
||||
private static void RequestPermission()
|
||||
private static void requestPermission()
|
||||
{
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
@@ -65,7 +65,7 @@ public class Java_WiimoteAdapter
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static boolean QueryAdapter()
|
||||
public static boolean queryAdapter()
|
||||
{
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
@@ -77,20 +77,20 @@ public class Java_WiimoteAdapter
|
||||
if (manager.hasPermission(dev))
|
||||
return true;
|
||||
else
|
||||
RequestPermission();
|
||||
requestPermission();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static int Input(int index)
|
||||
public static int input(int index)
|
||||
{
|
||||
return usb_con.bulkTransfer(usb_in[index], wiimote_payload[index], MAX_PAYLOAD, TIMEOUT);
|
||||
return usbConnection.bulkTransfer(usbIn[index], wiimotePayload[index], MAX_PAYLOAD, TIMEOUT);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static int Output(int index, byte[] buf, int size)
|
||||
public static int output(int index, byte[] buf, int size)
|
||||
{
|
||||
byte report_number = buf[0];
|
||||
|
||||
@@ -105,7 +105,7 @@ public class Java_WiimoteAdapter
|
||||
final int HID_SET_REPORT = 0x9;
|
||||
final int HID_OUTPUT = (2 << 8);
|
||||
|
||||
int write = usb_con.controlTransfer(
|
||||
int write = usbConnection.controlTransfer(
|
||||
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||
HID_SET_REPORT,
|
||||
HID_OUTPUT | report_number,
|
||||
@@ -120,10 +120,10 @@ public class Java_WiimoteAdapter
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static boolean OpenAdapter()
|
||||
public static boolean openAdapter()
|
||||
{
|
||||
// If the adapter is already open. Don't attempt to do it again
|
||||
if (usb_con != null && usb_con.getFileDescriptor() != -1)
|
||||
if (usbConnection != null && usbConnection.getFileDescriptor() != -1)
|
||||
return true;
|
||||
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
@@ -135,7 +135,7 @@ public class Java_WiimoteAdapter
|
||||
{
|
||||
if (manager.hasPermission(dev))
|
||||
{
|
||||
usb_con = manager.openDevice(dev);
|
||||
usbConnection = manager.openDevice(dev);
|
||||
UsbConfiguration conf = dev.getConfiguration(0);
|
||||
|
||||
Log.info("Number of configurations: " + dev.getConfigurationCount());
|
||||
@@ -149,20 +149,20 @@ public class Java_WiimoteAdapter
|
||||
for (int i = 0; i < MAX_WIIMOTES; ++i)
|
||||
{
|
||||
// One interface per Wii Remote
|
||||
usb_intf[i] = dev.getInterface(i);
|
||||
usb_con.claimInterface(usb_intf[i], true);
|
||||
usbInterface[i] = dev.getInterface(i);
|
||||
usbConnection.claimInterface(usbInterface[i], true);
|
||||
|
||||
// One endpoint per Wii Remote. Input only
|
||||
// Output reports go through the control channel.
|
||||
usb_in[i] = usb_intf[i].getEndpoint(0);
|
||||
Log.info("Interface " + i + " endpoint count:" + usb_intf[i].getEndpointCount());
|
||||
usbIn[i] = usbInterface[i].getEndpoint(0);
|
||||
Log.info("Interface " + i + " endpoint count:" + usbInterface[i].getEndpointCount());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// XXX: Message that the device was found, but it needs to be unplugged and plugged back in?
|
||||
usb_con.close();
|
||||
usbConnection.close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -450,15 +450,18 @@ void AchievementManager::FilterApprovedIni(std::vector<T>& codes, const std::str
|
||||
|
||||
for (auto& code : codes)
|
||||
{
|
||||
if (code.enabled && !CheckApprovedCode(code, game_id, revision))
|
||||
if (code.enabled && !IsApprovedCode(code, game_id, revision))
|
||||
code.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool AchievementManager::CheckApprovedCode(const T& code, const std::string& game_id,
|
||||
u16 revision) const
|
||||
bool AchievementManager::ShouldCodeBeActivated(const T& code, const std::string& game_id,
|
||||
u16 revision) const
|
||||
{
|
||||
if (!code.enabled)
|
||||
return false;
|
||||
|
||||
if (!IsHardcoreModeActive())
|
||||
return true;
|
||||
|
||||
@@ -468,33 +471,42 @@ bool AchievementManager::CheckApprovedCode(const T& code, const std::string& gam
|
||||
|
||||
INFO_LOG_FMT(ACHIEVEMENTS, "Verifying code {}", code.name);
|
||||
|
||||
bool verified = false;
|
||||
if (IsApprovedCode(code, game_id, revision))
|
||||
return true;
|
||||
|
||||
auto hash = Common::SHA1::DigestToString(GetCodeHash(code));
|
||||
OSD::AddMessage(fmt::format("Failed to verify code {} for game ID {}.", code.name, game_id),
|
||||
OSD::Duration::VERY_LONG, OSD::Color::RED);
|
||||
OSD::AddMessage("Disable hardcore mode to enable this code.", OSD::Duration::VERY_LONG,
|
||||
OSD::Color::RED);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool AchievementManager::IsApprovedCode(const T& code, const std::string& game_id,
|
||||
u16 revision) const
|
||||
{
|
||||
// Approved codes list failed to hash
|
||||
if (!m_ini_root->is<picojson::value::object>())
|
||||
return false;
|
||||
|
||||
const auto hash = Common::SHA1::DigestToString(GetCodeHash(code));
|
||||
|
||||
for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(game_id, revision))
|
||||
{
|
||||
auto config = filename.substr(0, filename.length() - 4);
|
||||
const auto config = filename.substr(0, filename.length() - 4);
|
||||
if (m_ini_root->contains(config))
|
||||
{
|
||||
auto ini_config = m_ini_root->get(config);
|
||||
const auto ini_config = m_ini_root->get(config);
|
||||
if (ini_config.is<picojson::object>() && ini_config.contains(code.name))
|
||||
{
|
||||
auto ini_code = ini_config.get(code.name);
|
||||
if (ini_code.template is<std::string>())
|
||||
verified = (ini_code.template get<std::string>() == hash);
|
||||
const auto ini_code = ini_config.get(code.name);
|
||||
if (ini_code.template is<std::string>() && ini_code.template get<std::string>() == hash)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!verified)
|
||||
{
|
||||
OSD::AddMessage(fmt::format("Failed to verify code {} for game ID {}.", code.name, game_id),
|
||||
OSD::Duration::VERY_LONG, OSD::Color::RED);
|
||||
OSD::AddMessage("Disable hardcore mode to enable this code.", OSD::Duration::VERY_LONG,
|
||||
OSD::Color::RED);
|
||||
}
|
||||
return verified;
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::SHA1::Digest AchievementManager::GetCodeHash(const PatchEngine::Patch& patch) const
|
||||
@@ -554,16 +566,27 @@ void AchievementManager::FilterApprovedARCodes(std::vector<ActionReplay::ARCode>
|
||||
FilterApprovedIni(codes, game_id, revision);
|
||||
}
|
||||
|
||||
bool AchievementManager::CheckApprovedGeckoCode(const Gecko::GeckoCode& code,
|
||||
const std::string& game_id, u16 revision) const
|
||||
bool AchievementManager::ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code,
|
||||
const std::string& game_id, u16 revision) const
|
||||
{
|
||||
return CheckApprovedCode(code, game_id, revision);
|
||||
return ShouldCodeBeActivated(code, game_id, revision);
|
||||
}
|
||||
|
||||
bool AchievementManager::CheckApprovedARCode(const ActionReplay::ARCode& code,
|
||||
bool AchievementManager::ShouldARCodeBeActivated(const ActionReplay::ARCode& code,
|
||||
const std::string& game_id, u16 revision) const
|
||||
{
|
||||
return ShouldCodeBeActivated(code, game_id, revision);
|
||||
}
|
||||
|
||||
bool AchievementManager::IsApprovedGeckoCode(const Gecko::GeckoCode& code,
|
||||
const std::string& game_id, u16 revision) const
|
||||
{
|
||||
return CheckApprovedCode(code, game_id, revision);
|
||||
return IsApprovedCode(code, game_id, revision);
|
||||
}
|
||||
bool AchievementManager::IsApprovedARCode(const ActionReplay::ARCode& code,
|
||||
const std::string& game_id, u16 revision) const
|
||||
{
|
||||
return IsApprovedCode(code, game_id, revision);
|
||||
}
|
||||
|
||||
void AchievementManager::SetSpectatorMode()
|
||||
|
@@ -149,10 +149,14 @@ public:
|
||||
u16 revision) const;
|
||||
void FilterApprovedARCodes(std::vector<ActionReplay::ARCode>& codes, const std::string& game_id,
|
||||
u16 revision) const;
|
||||
bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id,
|
||||
u16 revision) const;
|
||||
bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id,
|
||||
bool ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code, const std::string& game_id,
|
||||
u16 revision) const;
|
||||
bool ShouldARCodeBeActivated(const ActionReplay::ARCode& code, const std::string& game_id,
|
||||
u16 revision) const;
|
||||
bool IsApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id,
|
||||
u16 revision) const;
|
||||
bool IsApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id,
|
||||
u16 revision) const;
|
||||
|
||||
void SetSpectatorMode();
|
||||
std::string_view GetPlayerDisplayName() const;
|
||||
@@ -220,7 +224,9 @@ private:
|
||||
template <typename T>
|
||||
void FilterApprovedIni(std::vector<T>& codes, const std::string& game_id, u16 revision) const;
|
||||
template <typename T>
|
||||
bool CheckApprovedCode(const T& code, const std::string& game_id, u16 revision) const;
|
||||
bool ShouldCodeBeActivated(const T& code, const std::string& game_id, u16 revision) const;
|
||||
template <typename T>
|
||||
bool IsApprovedCode(const T& code, const std::string& game_id, u16 revision) const;
|
||||
Common::SHA1::Digest GetCodeHash(const PatchEngine::Patch& patch) const;
|
||||
Common::SHA1::Digest GetCodeHash(const Gecko::GeckoCode& code) const;
|
||||
Common::SHA1::Digest GetCodeHash(const ActionReplay::ARCode& code) const;
|
||||
@@ -333,14 +339,14 @@ public:
|
||||
|
||||
constexpr bool IsHardcoreModeActive() { return false; }
|
||||
|
||||
constexpr bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id,
|
||||
u16 revision)
|
||||
constexpr bool ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code,
|
||||
const std::string& game_id, u16 revision)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_id,
|
||||
u16 revision)
|
||||
constexpr bool ShouldARCodeBeActivated(const ActionReplay::ARCode& code,
|
||||
const std::string& game_id, u16 revision)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@@ -122,11 +122,11 @@ void ApplyCodes(std::span<const ARCode> codes, const std::string& game_id, u16 r
|
||||
std::lock_guard guard(s_lock);
|
||||
s_disable_logging = false;
|
||||
s_active_codes.clear();
|
||||
std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes),
|
||||
[&game_id, &revision](const ARCode& code) {
|
||||
return code.enabled && AchievementManager::GetInstance().CheckApprovedARCode(
|
||||
code, game_id, revision);
|
||||
});
|
||||
|
||||
const auto should_be_activated = [&game_id, &revision](const ARCode& code) {
|
||||
return AchievementManager::GetInstance().ShouldARCodeBeActivated(code, game_id, revision);
|
||||
};
|
||||
std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes), should_be_activated);
|
||||
s_active_codes.shrink_to_fit();
|
||||
}
|
||||
|
||||
|
@@ -68,11 +68,11 @@ void SetActiveCodes(std::span<const GeckoCode> gcodes, const std::string& game_i
|
||||
{
|
||||
s_active_codes.reserve(gcodes.size());
|
||||
|
||||
const auto should_be_activated = [&game_id, &revision](const GeckoCode& code) {
|
||||
return AchievementManager::GetInstance().ShouldGeckoCodeBeActivated(code, game_id, revision);
|
||||
};
|
||||
std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes),
|
||||
[&game_id, &revision](const GeckoCode& code) {
|
||||
return code.enabled && AchievementManager::GetInstance().CheckApprovedGeckoCode(
|
||||
code, game_id, revision);
|
||||
});
|
||||
should_be_activated);
|
||||
}
|
||||
s_active_codes.shrink_to_fit();
|
||||
|
||||
|
@@ -30,8 +30,8 @@ void WiimoteScannerAndroid::FindWiimotes(std::vector<Wiimote*>& found_wiimotes,
|
||||
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
|
||||
jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z");
|
||||
jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
|
||||
jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z");
|
||||
jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "queryAdapter", "()Z");
|
||||
|
||||
if (env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func) &&
|
||||
env->CallStaticBooleanMethod(s_adapter_class, openadapter_func))
|
||||
@@ -55,15 +55,15 @@ bool WiimoteAndroid::ConnectInternal()
|
||||
{
|
||||
m_env = IDCache::GetEnvForThread();
|
||||
|
||||
jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimote_payload", "[[B");
|
||||
jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimotePayload", "[[B");
|
||||
jobjectArray payload_object =
|
||||
reinterpret_cast<jobjectArray>(m_env->GetStaticObjectField(s_adapter_class, payload_field));
|
||||
m_java_wiimote_payload =
|
||||
(jbyteArray)m_env->GetObjectArrayElement(payload_object, m_mayflash_index);
|
||||
|
||||
// Get function pointers
|
||||
m_input_func = m_env->GetStaticMethodID(s_adapter_class, "Input", "(I)I");
|
||||
m_output_func = m_env->GetStaticMethodID(s_adapter_class, "Output", "(I[BI)I");
|
||||
m_input_func = m_env->GetStaticMethodID(s_adapter_class, "input", "(I)I");
|
||||
m_output_func = m_env->GetStaticMethodID(s_adapter_class, "output", "(I[BI)I");
|
||||
|
||||
is_connected = true;
|
||||
|
||||
@@ -110,7 +110,7 @@ int WiimoteAndroid::IOWrite(u8 const* buf, size_t len)
|
||||
void InitAdapterClass()
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter");
|
||||
jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/WiimoteAdapter");
|
||||
s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
|
||||
}
|
||||
} // namespace WiimoteReal
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/BitSet.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/Core.h"
|
||||
@@ -341,8 +342,13 @@ void MemChecks::Update()
|
||||
{
|
||||
const Core::CPUThreadGuard guard(m_system);
|
||||
|
||||
// Clear the JIT cache so it can switch the watchpoint-compatible mode.
|
||||
if (m_mem_breakpoints_set != HasAny())
|
||||
const bool registers_changed = UpdateRegistersUsedInConditions();
|
||||
|
||||
// If we've added a first memcheck, clear the JIT cache so it can switch to watchpoint-compatible
|
||||
// code. Or, if we've added a memcheck whose condition wants to read from a new register, clear
|
||||
// the JIT cache to make the slow memory access code flush that register. And conversely, if the
|
||||
// aforementioned functionality is no longer needed, clear the JIT cache to switch to faster code.
|
||||
if (registers_changed || m_mem_breakpoints_set != HasAny())
|
||||
{
|
||||
m_system.GetJitInterface().ClearCache(guard);
|
||||
m_mem_breakpoints_set = HasAny();
|
||||
@@ -351,6 +357,27 @@ void MemChecks::Update()
|
||||
m_system.GetMMU().DBATUpdated();
|
||||
}
|
||||
|
||||
bool MemChecks::UpdateRegistersUsedInConditions()
|
||||
{
|
||||
BitSet32 gprs_used, fprs_used;
|
||||
for (TMemCheck& mem_check : m_mem_checks)
|
||||
{
|
||||
if (mem_check.condition)
|
||||
{
|
||||
gprs_used |= mem_check.condition->GetGPRsUsed();
|
||||
fprs_used |= mem_check.condition->GetFPRsUsed();
|
||||
}
|
||||
}
|
||||
|
||||
const bool registers_changed =
|
||||
gprs_used != m_gprs_used_in_conditions || fprs_used != m_fprs_used_in_conditions;
|
||||
|
||||
m_gprs_used_in_conditions = gprs_used;
|
||||
m_fprs_used_in_conditions = fprs_used;
|
||||
|
||||
return registers_changed;
|
||||
}
|
||||
|
||||
TMemCheck* MemChecks::GetMemCheck(u32 address, size_t size)
|
||||
{
|
||||
const auto iter = std::ranges::find_if(m_mem_checks, [address, size](const auto& mc) {
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/BitSet.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/PowerPC/Expression.h"
|
||||
|
||||
@@ -129,9 +130,17 @@ public:
|
||||
void Clear();
|
||||
bool HasAny() const { return !m_mem_checks.empty(); }
|
||||
|
||||
BitSet32 GetGPRsUsedInConditions() { return m_gprs_used_in_conditions; }
|
||||
BitSet32 GetFPRsUsedInConditions() { return m_fprs_used_in_conditions; }
|
||||
|
||||
private:
|
||||
// Returns whether any change was made
|
||||
bool UpdateRegistersUsedInConditions();
|
||||
|
||||
TMemChecks m_mem_checks;
|
||||
Core::System& m_system;
|
||||
BitSet32 m_gprs_used_in_conditions;
|
||||
BitSet32 m_fprs_used_in_conditions;
|
||||
bool m_mem_breakpoints_set = false;
|
||||
};
|
||||
|
||||
|
@@ -25,6 +25,7 @@ using std::isinf;
|
||||
using std::isnan;
|
||||
#include <expr.h>
|
||||
|
||||
#include "Common/BitSet.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/Core.h"
|
||||
@@ -503,3 +504,26 @@ std::string Expression::GetText() const
|
||||
{
|
||||
return m_text;
|
||||
}
|
||||
|
||||
void Expression::ComputeRegistersUsed()
|
||||
{
|
||||
if (m_has_computed_registers_used)
|
||||
return;
|
||||
|
||||
for (const VarBinding& bind : m_binds)
|
||||
{
|
||||
switch (bind.type)
|
||||
{
|
||||
case VarBindingType::GPR:
|
||||
m_gprs_used[bind.index] = true;
|
||||
break;
|
||||
case VarBindingType::FPR:
|
||||
m_fprs_used[bind.index] = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_has_computed_registers_used = true;
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/BitSet.h"
|
||||
|
||||
struct expr;
|
||||
struct expr_var_list;
|
||||
|
||||
@@ -41,6 +43,18 @@ public:
|
||||
|
||||
std::string GetText() const;
|
||||
|
||||
BitSet32 GetGPRsUsed()
|
||||
{
|
||||
ComputeRegistersUsed();
|
||||
return m_gprs_used;
|
||||
}
|
||||
|
||||
BitSet32 GetFPRsUsed()
|
||||
{
|
||||
ComputeRegistersUsed();
|
||||
return m_fprs_used;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class SynchronizeDirection
|
||||
{
|
||||
@@ -69,10 +83,16 @@ private:
|
||||
void SynchronizeBindings(Core::System& system, SynchronizeDirection dir) const;
|
||||
void Reporting(const double result) const;
|
||||
|
||||
void ComputeRegistersUsed();
|
||||
|
||||
std::string m_text;
|
||||
ExprPointer m_expr;
|
||||
ExprVarListPointer m_vars;
|
||||
std::vector<VarBinding> m_binds;
|
||||
|
||||
BitSet32 m_gprs_used;
|
||||
BitSet32 m_fprs_used;
|
||||
bool m_has_computed_registers_used = false;
|
||||
};
|
||||
|
||||
inline bool EvaluateCondition(Core::System& system, const std::optional<Expression>& condition)
|
||||
|
@@ -1294,6 +1294,25 @@ void Jit64::IntializeSpeculativeConstants()
|
||||
}
|
||||
}
|
||||
|
||||
void Jit64::FlushRegistersBeforeSlowAccess()
|
||||
{
|
||||
// Register values can be used by memory watchpoint conditions.
|
||||
MemChecks& mem_checks = m_system.GetPowerPC().GetMemChecks();
|
||||
if (mem_checks.HasAny())
|
||||
{
|
||||
BitSet32 gprs = mem_checks.GetGPRsUsedInConditions();
|
||||
BitSet32 fprs = mem_checks.GetFPRsUsedInConditions();
|
||||
if (gprs || fprs)
|
||||
{
|
||||
RCForkGuard gpr_guard = gpr.Fork();
|
||||
RCForkGuard fpr_guard = fpr.Fork();
|
||||
|
||||
gpr.Flush(gprs);
|
||||
fpr.Flush(fprs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Jit64::HandleFunctionHooking(u32 address)
|
||||
{
|
||||
const auto result = HLE::TryReplaceFunction(m_ppc_symbol_db, address, PowerPC::CoreMode::JIT);
|
||||
|
@@ -81,6 +81,8 @@ public:
|
||||
|
||||
void IntializeSpeculativeConstants();
|
||||
|
||||
void FlushRegistersBeforeSlowAccess();
|
||||
|
||||
JitBlockCache* GetBlockCache() override { return &blocks; }
|
||||
void Trace();
|
||||
|
||||
|
@@ -110,6 +110,8 @@ void Jit64::lXXx(UGeckoInstruction inst)
|
||||
PanicAlertFmt("Invalid instruction");
|
||||
}
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
// PowerPC has no 8-bit sign extended load, but x86 does, so merge extsb with the load if we find
|
||||
// it.
|
||||
if (CanMergeNextInstructions(1) && accessSize == 8 && js.op[1].inst.OPCD == 31 &&
|
||||
@@ -439,6 +441,8 @@ void Jit64::dcbz(UGeckoInstruction inst)
|
||||
int a = inst.RA;
|
||||
int b = inst.RB;
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
{
|
||||
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
|
||||
RCOpArg Rb = gpr.Use(b, RCMode::Read);
|
||||
@@ -477,7 +481,7 @@ void Jit64::dcbz(UGeckoInstruction inst)
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(slow);
|
||||
}
|
||||
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
|
||||
FlushPCBeforeSlowAccess();
|
||||
BitSet32 registersInUse = CallerSavedRegistersInUse();
|
||||
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
|
||||
ABI_CallFunctionPR(PowerPC::ClearDCacheLineFromJit, &m_mmu, RSCRATCH);
|
||||
@@ -524,6 +528,8 @@ void Jit64::stX(UGeckoInstruction inst)
|
||||
return;
|
||||
}
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
// If we already know the address of the write
|
||||
if (!a || gpr.IsImm(a))
|
||||
{
|
||||
@@ -602,6 +608,8 @@ void Jit64::stXx(UGeckoInstruction inst)
|
||||
break;
|
||||
}
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
const bool does_clobber = WriteClobbersRegValue(accessSize, /* swap */ !byte_reverse);
|
||||
|
||||
RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read);
|
||||
@@ -634,6 +642,8 @@ void Jit64::lmw(UGeckoInstruction inst)
|
||||
|
||||
int a = inst.RA, d = inst.RD;
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
// TODO: This doesn't handle rollback on DSI correctly
|
||||
{
|
||||
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
|
||||
@@ -657,6 +667,8 @@ void Jit64::stmw(UGeckoInstruction inst)
|
||||
|
||||
int a = inst.RA, d = inst.RD;
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
// TODO: This doesn't handle rollback on DSI correctly
|
||||
for (int i = d; i < 32; i++)
|
||||
{
|
||||
|
@@ -30,6 +30,8 @@ void Jit64::lfXXX(UGeckoInstruction inst)
|
||||
|
||||
FALLBACK_IF(!indexed && !a);
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
s32 offset = 0;
|
||||
RCOpArg addr = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read);
|
||||
RegCache::Realize(addr);
|
||||
@@ -103,6 +105,8 @@ void Jit64::stfXXX(UGeckoInstruction inst)
|
||||
|
||||
FALLBACK_IF(update && jo.memcheck && indexed && a == b);
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
if (single)
|
||||
{
|
||||
if (js.fpr_is_store_safe[s] && js.op->fprIsSingle[s])
|
||||
@@ -196,6 +200,8 @@ void Jit64::stfiwx(UGeckoInstruction inst)
|
||||
int a = inst.RA;
|
||||
int b = inst.RB;
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
RCOpArg Ra = a ? gpr.Use(a, RCMode::Read) : RCOpArg::Imm32(0);
|
||||
RCOpArg Rb = gpr.Use(b, RCMode::Read);
|
||||
RCOpArg Rs = fpr.Use(s, RCMode::Read);
|
||||
|
@@ -35,6 +35,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
|
||||
int w = indexed ? inst.Wx : inst.W;
|
||||
FALLBACK_IF(!a);
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA);
|
||||
RCOpArg Ra = update ? gpr.Bind(a, RCMode::ReadWrite) : gpr.Use(a, RCMode::Read);
|
||||
RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset);
|
||||
@@ -69,8 +71,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stash PC in case asm routine needs to call into C++
|
||||
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
|
||||
FlushPCBeforeSlowAccess();
|
||||
|
||||
// We know what GQR is here, so we can load RSCRATCH2 and call into the store method directly
|
||||
// with just the scale bits.
|
||||
MOV(32, R(RSCRATCH2), Imm32(gqrValue & 0x3F00));
|
||||
@@ -83,8 +85,8 @@ void Jit64::psq_stXX(UGeckoInstruction inst)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stash PC in case asm routine needs to call into C++
|
||||
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
|
||||
FlushPCBeforeSlowAccess();
|
||||
|
||||
// Some games (e.g. Dirt 2) incorrectly set the unused bits which breaks the lookup table code.
|
||||
// Hence, we need to mask out the unused bits. The layout of the GQR register is
|
||||
// UU[SCALE]UUUUU[TYPE] where SCALE is 6 bits and TYPE is 3 bits, so we have to AND with
|
||||
@@ -124,6 +126,8 @@ void Jit64::psq_lXX(UGeckoInstruction inst)
|
||||
int w = indexed ? inst.Wx : inst.W;
|
||||
FALLBACK_IF(!a);
|
||||
|
||||
FlushRegistersBeforeSlowAccess();
|
||||
|
||||
RCX64Reg scratch_guard = gpr.Scratch(RSCRATCH_EXTRA);
|
||||
RCX64Reg Ra = gpr.Bind(a, update ? RCMode::ReadWrite : RCMode::Read);
|
||||
RCOpArg Rb = indexed ? gpr.Use(b, RCMode::Read) : RCOpArg::Imm32((u32)offset);
|
||||
@@ -144,8 +148,8 @@ void Jit64::psq_lXX(UGeckoInstruction inst)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stash PC in case asm routine needs to call into C++
|
||||
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
|
||||
FlushPCBeforeSlowAccess();
|
||||
|
||||
// Get the high part of the GQR register
|
||||
OpArg gqr = PPCSTATE_SPR(SPR_GQR0 + i);
|
||||
gqr.AddMemOffset(2);
|
||||
|
@@ -92,6 +92,13 @@ void EmuCodeBlock::SwitchToNearCode()
|
||||
SetCodePtr(m_near_code, m_near_code_end, m_near_code_write_failed);
|
||||
}
|
||||
|
||||
void EmuCodeBlock::FlushPCBeforeSlowAccess()
|
||||
{
|
||||
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
|
||||
// interrupt checks, and printing accurate PC locations in debug logs.
|
||||
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
|
||||
}
|
||||
|
||||
FixupBranch EmuCodeBlock::BATAddressLookup(X64Reg addr, X64Reg tmp, const void* bat_table)
|
||||
{
|
||||
MOV(64, R(tmp), ImmPtr(bat_table));
|
||||
@@ -386,14 +393,11 @@ void EmuCodeBlock::SafeLoadToReg(X64Reg reg_value, const Gen::OpArg& opAddress,
|
||||
SetJumpTarget(slow);
|
||||
}
|
||||
|
||||
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
|
||||
// interrupt checks, and printing accurate PC locations in debug logs.
|
||||
//
|
||||
// In the case of Jit64AsmCommon routines, we don't know the PC here,
|
||||
// so the caller has to store the PC themselves.
|
||||
// In the case of Jit64AsmCommon routines, the state we want to store here isn't known
|
||||
// when compiling the routine, so the caller has to store it themselves.
|
||||
if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC))
|
||||
{
|
||||
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
|
||||
FlushPCBeforeSlowAccess();
|
||||
}
|
||||
|
||||
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
|
||||
@@ -457,8 +461,7 @@ void EmuCodeBlock::SafeLoadToRegImmediate(X64Reg reg_value, u32 address, int acc
|
||||
return;
|
||||
}
|
||||
|
||||
// Helps external systems know which instruction triggered the read.
|
||||
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
|
||||
FlushPCBeforeSlowAccess();
|
||||
|
||||
// Fall back to general-case code.
|
||||
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
|
||||
@@ -560,14 +563,11 @@ void EmuCodeBlock::SafeWriteRegToReg(OpArg reg_value, X64Reg reg_addr, int acces
|
||||
SetJumpTarget(slow);
|
||||
}
|
||||
|
||||
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
|
||||
// interrupt checks, and printing accurate PC locations in debug logs.
|
||||
//
|
||||
// In the case of Jit64AsmCommon routines, we don't know the PC here,
|
||||
// so the caller has to store the PC themselves.
|
||||
// In the case of Jit64AsmCommon routines, the state we want to store here isn't known
|
||||
// when compiling the routine, so the caller has to store it themselves.
|
||||
if (!(flags & SAFE_LOADSTORE_NO_UPDATE_PC))
|
||||
{
|
||||
MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
|
||||
FlushPCBeforeSlowAccess();
|
||||
}
|
||||
|
||||
size_t rsp_alignment = (flags & SAFE_LOADSTORE_NO_PROLOG) ? 8 : 0;
|
||||
@@ -663,8 +663,7 @@ bool EmuCodeBlock::WriteToConstAddress(int accessSize, OpArg arg, u32 address,
|
||||
}
|
||||
else
|
||||
{
|
||||
// Helps external systems know which instruction triggered the write
|
||||
MOV(32, PPCSTATE(pc), Imm32(m_jit.js.compilerPC));
|
||||
FlushPCBeforeSlowAccess();
|
||||
|
||||
ABI_PushRegistersAndAdjustStack(registersInUse, 0);
|
||||
switch (accessSize)
|
||||
|
@@ -49,6 +49,8 @@ public:
|
||||
return Gen::M(m_const_pool.GetConstant(&value, sizeof(T), N, index));
|
||||
}
|
||||
|
||||
void FlushPCBeforeSlowAccess();
|
||||
|
||||
// Writes upper 15 bits of physical address to addr and clobbers the lower 17 bits of addr.
|
||||
// Jumps to the returned FixupBranch if lookup fails.
|
||||
Gen::FixupBranch BATAddressLookup(Gen::X64Reg addr, Gen::X64Reg tmp, const void* bat_table);
|
||||
|
@@ -273,12 +273,18 @@ protected:
|
||||
// !emitting_routine && mode != AlwaysSlowAccess && !jo.fastmem: X30
|
||||
// !emitting_routine && mode == Auto && jo.fastmem: X30
|
||||
//
|
||||
// Furthermore, any callee-saved register which isn't marked in gprs_to_push/fprs_to_push
|
||||
// may be clobbered if mode != AlwaysFastAccess.
|
||||
// Furthermore:
|
||||
// - Any callee-saved register which isn't marked in gprs_to_push/fprs_to_push may be
|
||||
// clobbered if mode != AlwaysFastAccess.
|
||||
// - If !emitting_routine && mode != AlwaysFastAccess && jo.memcheck, X30 must not
|
||||
// contain a guest register.
|
||||
void EmitBackpatchRoutine(u32 flags, MemAccessMode mode, Arm64Gen::ARM64Reg RS,
|
||||
Arm64Gen::ARM64Reg addr, BitSet32 gprs_to_push = BitSet32(0),
|
||||
BitSet32 fprs_to_push = BitSet32(0), bool emitting_routine = false);
|
||||
|
||||
// temp_gpr must be a valid register, but temp_fpr can be INVALID_REG.
|
||||
void FlushPPCStateBeforeSlowAccess(Arm64Gen::ARM64Reg temp_gpr, Arm64Gen::ARM64Reg temp_fpr);
|
||||
|
||||
// Loadstore routines
|
||||
void SafeLoadToReg(u32 dest, s32 addr, s32 offsetReg, u32 flags, s32 offset, bool update);
|
||||
void SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s32 offset, bool update);
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include "Core/PowerPC/JitArmCommon/BackPatch.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
using namespace Arm64Gen;
|
||||
|
||||
@@ -171,6 +172,7 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
|
||||
|
||||
const ARM64Reg temp_gpr = ARM64Reg::W1;
|
||||
const int temp_gpr_index = DecodeReg(temp_gpr);
|
||||
const ARM64Reg temp_fpr = fprs_to_push[0] ? ARM64Reg::INVALID_REG : ARM64Reg::Q0;
|
||||
|
||||
BitSet32 gprs_to_push_early = {};
|
||||
if (memcheck)
|
||||
@@ -189,16 +191,10 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
|
||||
ABI_PushRegisters(gprs_to_push & ~gprs_to_push_early);
|
||||
m_float_emit.ABI_PushRegisters(fprs_to_push, ARM64Reg::X30);
|
||||
|
||||
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
|
||||
// interrupt checks, and printing accurate PC locations in debug logs.
|
||||
//
|
||||
// In the case of JitAsm routines, we don't know the PC here,
|
||||
// so the caller has to store the PC themselves.
|
||||
// In the case of JitAsm routines, the state we want to store here isn't known
|
||||
// when compiling the routine, so the caller has to store it themselves.
|
||||
if (!emitting_routine)
|
||||
{
|
||||
MOVI2R(ARM64Reg::W30, js.compilerPC);
|
||||
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
|
||||
}
|
||||
FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, temp_fpr);
|
||||
|
||||
if (flags & BackPatchInfo::FLAG_STORE)
|
||||
{
|
||||
@@ -265,7 +261,6 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
|
||||
|
||||
if (memcheck)
|
||||
{
|
||||
const ARM64Reg temp_fpr = fprs_to_push[0] ? ARM64Reg::INVALID_REG : ARM64Reg::Q0;
|
||||
const u64 early_push_count = (gprs_to_push & gprs_to_push_early).Count();
|
||||
const u64 early_push_size = Common::AlignUp(early_push_count, 2) * 8;
|
||||
|
||||
@@ -316,6 +311,22 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
|
||||
}
|
||||
}
|
||||
|
||||
void JitArm64::FlushPPCStateBeforeSlowAccess(ARM64Reg temp_gpr, ARM64Reg temp_fpr)
|
||||
{
|
||||
// PC is used by memory watchpoints (if enabled), profiling where to insert gather pipe
|
||||
// interrupt checks, and printing accurate PC locations in debug logs.
|
||||
MOVI2R(temp_gpr, js.compilerPC);
|
||||
STR(IndexType::Unsigned, temp_gpr, PPC_REG, PPCSTATE_OFF(pc));
|
||||
|
||||
// Register values can be used by memory watchpoint conditions.
|
||||
MemChecks& mem_checks = m_system.GetPowerPC().GetMemChecks();
|
||||
if (mem_checks.HasAny())
|
||||
{
|
||||
gpr.StoreRegisters(mem_checks.GetGPRsUsedInConditions(), temp_gpr, FlushMode::MaintainState);
|
||||
fpr.StoreRegisters(mem_checks.GetFPRsUsedInConditions(), temp_fpr, FlushMode::MaintainState);
|
||||
}
|
||||
}
|
||||
|
||||
bool JitArm64::HandleFastmemFault(SContext* ctx)
|
||||
{
|
||||
const u8* pc = reinterpret_cast<const u8*>(ctx->CTX_PC);
|
||||
|
@@ -102,9 +102,7 @@ void JitArm64::psq_lXX(UGeckoInstruction inst)
|
||||
{
|
||||
LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i));
|
||||
|
||||
// Stash PC in case asm routine needs to call into C++
|
||||
MOVI2R(ARM64Reg::W30, js.compilerPC);
|
||||
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
|
||||
FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, ARM64Reg::Q1);
|
||||
|
||||
UBFM(type_reg, scale_reg, 16, 18); // Type
|
||||
UBFM(scale_reg, scale_reg, 24, 29); // Scale
|
||||
@@ -260,9 +258,7 @@ void JitArm64::psq_stXX(UGeckoInstruction inst)
|
||||
{
|
||||
LDR(IndexType::Unsigned, scale_reg, PPC_REG, PPCSTATE_OFF_SPR(SPR_GQR0 + i));
|
||||
|
||||
// Stash PC in case asm routine needs to call into C++
|
||||
MOVI2R(ARM64Reg::W30, js.compilerPC);
|
||||
STR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(pc));
|
||||
FlushPPCStateBeforeSlowAccess(ARM64Reg::W30, ARM64Reg::Q1);
|
||||
|
||||
UBFM(type_reg, scale_reg, 0, 2); // Type
|
||||
UBFM(scale_reg, scale_reg, 8, 13); // Scale
|
||||
|
@@ -388,14 +388,16 @@ public:
|
||||
|
||||
BitSet32 GetDirtyGPRs() const;
|
||||
|
||||
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
|
||||
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
|
||||
FlushMode flush_mode = FlushMode::All)
|
||||
{
|
||||
FlushRegisters(regs, FlushMode::All, tmp_reg, IgnoreDiscardedRegisters::No);
|
||||
FlushRegisters(regs, flush_mode, tmp_reg, IgnoreDiscardedRegisters::No);
|
||||
}
|
||||
|
||||
void StoreCRRegisters(BitSet8 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
|
||||
void StoreCRRegisters(BitSet8 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
|
||||
FlushMode flush_mode = FlushMode::All)
|
||||
{
|
||||
FlushCRRegisters(regs, FlushMode::All, tmp_reg, IgnoreDiscardedRegisters::No);
|
||||
FlushCRRegisters(regs, flush_mode, tmp_reg, IgnoreDiscardedRegisters::No);
|
||||
}
|
||||
|
||||
void DiscardCRRegisters(BitSet8 regs);
|
||||
@@ -459,9 +461,10 @@ public:
|
||||
|
||||
void FixSinglePrecision(size_t preg);
|
||||
|
||||
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
|
||||
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG,
|
||||
FlushMode flush_mode = FlushMode::All)
|
||||
{
|
||||
FlushRegisters(regs, FlushMode::All, tmp_reg);
|
||||
FlushRegisters(regs, flush_mode, tmp_reg);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@@ -8,14 +8,21 @@
|
||||
|
||||
#include <QCursor>
|
||||
#include <QHBoxLayout>
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
#include <QIcon>
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
#include <QStyle>
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
|
||||
#include "Core/AchievementManager.h"
|
||||
#include "Core/ActionReplay.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
@@ -23,6 +30,9 @@
|
||||
#include "DolphinQt/Config/CheatWarningWidget.h"
|
||||
#include "DolphinQt/Config/HardcoreWarningWidget.h"
|
||||
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
#include "DolphinQt/Settings.h"
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
|
||||
ARCodeWidget::ARCodeWidget(std::string game_id, u16 game_revision, bool restart_required)
|
||||
: m_game_id(std::move(game_id)), m_game_revision(game_revision),
|
||||
@@ -90,6 +100,7 @@ void ARCodeWidget::ConnectWidgets()
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
connect(m_hc_warning, &HardcoreWarningWidget::OpenAchievementSettings, this,
|
||||
&ARCodeWidget::OpenAchievementSettings);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &ARCodeWidget::UpdateList);
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
|
||||
connect(m_code_list, &QListWidget::itemChanged, this, &ARCodeWidget::OnItemChanged);
|
||||
@@ -199,6 +210,21 @@ void ARCodeWidget::UpdateList()
|
||||
item->setCheckState(ar.enabled ? Qt::Checked : Qt::Unchecked);
|
||||
item->setData(Qt::UserRole, static_cast<int>(i));
|
||||
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
const AchievementManager& achievement_manager = AchievementManager::GetInstance();
|
||||
|
||||
if (achievement_manager.IsHardcoreModeActive())
|
||||
{
|
||||
const QIcon approved_icon = style()->standardIcon(QStyle::SP_DialogYesButton);
|
||||
const QIcon warning_icon = style()->standardIcon(QStyle::SP_MessageBoxWarning);
|
||||
|
||||
if (achievement_manager.IsApprovedARCode(ar, m_game_id, m_game_revision))
|
||||
item->setIcon(approved_icon);
|
||||
else
|
||||
item->setIcon(warning_icon);
|
||||
}
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
|
||||
m_code_list->addItem(item);
|
||||
}
|
||||
|
||||
|
@@ -10,16 +10,23 @@
|
||||
#include <QFontDatabase>
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
#include <QIcon>
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
#include <QStyle>
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
|
||||
#include "Core/AchievementManager.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/GeckoCode.h"
|
||||
#include "Core/GeckoCodeConfig.h"
|
||||
@@ -31,6 +38,9 @@
|
||||
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
|
||||
#include "DolphinQt/QtUtils/QtUtils.h"
|
||||
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
#include "DolphinQt/Settings.h"
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
|
||||
GeckoCodeWidget::GeckoCodeWidget(std::string game_id, std::string gametdb_id, u16 game_revision,
|
||||
bool restart_required)
|
||||
@@ -158,6 +168,8 @@ void GeckoCodeWidget::ConnectWidgets()
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
connect(m_hc_warning, &HardcoreWarningWidget::OpenAchievementSettings, this,
|
||||
&GeckoCodeWidget::OpenAchievementSettings);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
&GeckoCodeWidget::UpdateList);
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
}
|
||||
|
||||
@@ -356,6 +368,21 @@ void GeckoCodeWidget::UpdateList()
|
||||
item->setCheckState(code.enabled ? Qt::Checked : Qt::Unchecked);
|
||||
item->setData(Qt::UserRole, static_cast<int>(i));
|
||||
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
const AchievementManager& achievement_manager = AchievementManager::GetInstance();
|
||||
|
||||
if (achievement_manager.IsHardcoreModeActive())
|
||||
{
|
||||
const QIcon approved_icon = style()->standardIcon(QStyle::SP_DialogYesButton);
|
||||
const QIcon warning_icon = style()->standardIcon(QStyle::SP_MessageBoxWarning);
|
||||
|
||||
if (achievement_manager.IsApprovedGeckoCode(code, m_game_id, m_game_revision))
|
||||
item->setIcon(approved_icon);
|
||||
else
|
||||
item->setIcon(warning_icon);
|
||||
}
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
|
||||
m_code_list->addItem(item);
|
||||
}
|
||||
|
||||
|
@@ -140,6 +140,16 @@ void HacksWidget::OnBackendChanged(const QString& backend_name)
|
||||
|
||||
void HacksWidget::ConnectWidgets()
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_store_efb_copies, &QCheckBox::checkStateChanged,
|
||||
[this](Qt::CheckState) { UpdateDeferEFBCopiesEnabled(); });
|
||||
connect(m_store_xfb_copies, &QCheckBox::checkStateChanged,
|
||||
[this](Qt::CheckState) { UpdateDeferEFBCopiesEnabled(); });
|
||||
connect(m_immediate_xfb, &QCheckBox::checkStateChanged,
|
||||
[this](Qt::CheckState) { UpdateSkipPresentingDuplicateFramesEnabled(); });
|
||||
connect(m_vi_skip, &QCheckBox::checkStateChanged,
|
||||
[this](Qt::CheckState) { UpdateSkipPresentingDuplicateFramesEnabled(); });
|
||||
#else
|
||||
connect(m_store_efb_copies, &QCheckBox::stateChanged,
|
||||
[this](int) { UpdateDeferEFBCopiesEnabled(); });
|
||||
connect(m_store_xfb_copies, &QCheckBox::stateChanged,
|
||||
@@ -148,6 +158,7 @@ void HacksWidget::ConnectWidgets()
|
||||
[this](int) { UpdateSkipPresentingDuplicateFramesEnabled(); });
|
||||
connect(m_vi_skip, &QCheckBox::stateChanged,
|
||||
[this](int) { UpdateSkipPresentingDuplicateFramesEnabled(); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void HacksWidget::AddDescriptions()
|
||||
|
@@ -76,7 +76,11 @@ MappingBool::MappingBool(MappingWidget* parent, ControllerEmu::NumericSetting<bo
|
||||
if (const auto ui_description = m_setting.GetUIDescription())
|
||||
setToolTip(tr(ui_description));
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(this, &QCheckBox::checkStateChanged, this, [this, parent](Qt::CheckState value) {
|
||||
#else
|
||||
connect(this, &QCheckBox::stateChanged, this, [this, parent](int value) {
|
||||
#endif
|
||||
m_setting.SetValue(value != 0);
|
||||
ConfigChanged();
|
||||
parent->SaveSettings();
|
||||
|
@@ -5,11 +5,11 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBitmap>
|
||||
#include <QBrush>
|
||||
#include <QCursor>
|
||||
#include <QFont>
|
||||
#include <QGuiApplication>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
@@ -25,11 +25,17 @@
|
||||
#include <QToolTip>
|
||||
#endif
|
||||
|
||||
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
|
||||
// Remember the parent ToolTipWidget so cursor-related events can see whether the cursor is inside
|
||||
// the parent's bounding box or not. Use this variable instead of BalloonTip's parent() member
|
||||
// because the ToolTipWidget isn't responsible for deleting the BalloonTip and so doesn't set its
|
||||
// parent member.
|
||||
QWidget* s_parent = nullptr;
|
||||
} // namespace
|
||||
|
||||
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||
@@ -53,6 +59,7 @@ void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||
|
||||
void BalloonTip::HideBalloon()
|
||||
{
|
||||
s_parent = nullptr;
|
||||
#if defined(__APPLE__)
|
||||
QToolTip::hideText();
|
||||
#else
|
||||
@@ -66,6 +73,9 @@ void BalloonTip::HideBalloon()
|
||||
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
|
||||
: QWidget(nullptr, Qt::ToolTip)
|
||||
{
|
||||
s_parent = parent;
|
||||
setMouseTracking(true);
|
||||
|
||||
QColor window_color;
|
||||
QColor text_color;
|
||||
QColor dolphin_emphasis;
|
||||
@@ -113,10 +123,61 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
|
||||
create_label(message);
|
||||
}
|
||||
|
||||
void BalloonTip::paintEvent(QPaintEvent*)
|
||||
bool BalloonTip::IsCursorInsideWidgetBoundingBox(const QWidget& widget)
|
||||
{
|
||||
const QPoint local_cursor_position = widget.mapFromGlobal(QCursor::pos());
|
||||
return widget.rect().contains(local_cursor_position);
|
||||
}
|
||||
|
||||
bool BalloonTip::IsCursorOnBalloonTip()
|
||||
{
|
||||
return s_the_balloon_tip != nullptr &&
|
||||
QApplication::widgetAt(QCursor::pos()) == s_the_balloon_tip.get();
|
||||
}
|
||||
|
||||
bool BalloonTip::IsWidgetBalloonTipActive(const QWidget& widget)
|
||||
{
|
||||
return &widget == s_parent;
|
||||
}
|
||||
|
||||
// Hiding the balloon causes the BalloonTip widget to be deleted. Triggering that deletion while
|
||||
// inside a BalloonTip event handler leads to a use-after-free crash or worse, so queue the deletion
|
||||
// for later.
|
||||
static void QueueHideBalloon()
|
||||
{
|
||||
QueueOnObject(s_parent, BalloonTip::HideBalloon);
|
||||
}
|
||||
|
||||
void BalloonTip::enterEvent(QEnterEvent* const event)
|
||||
{
|
||||
if (!IsCursorInsideWidgetBoundingBox(*s_parent))
|
||||
QueueHideBalloon();
|
||||
|
||||
QWidget::enterEvent(event);
|
||||
}
|
||||
|
||||
void BalloonTip::mouseMoveEvent(QMouseEvent* const event)
|
||||
{
|
||||
if (!IsCursorInsideWidgetBoundingBox(*s_parent))
|
||||
QueueHideBalloon();
|
||||
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void BalloonTip::leaveEvent(QEvent* const event)
|
||||
{
|
||||
if (QApplication::widgetAt(QCursor::pos()) != s_parent)
|
||||
QueueHideBalloon();
|
||||
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
void BalloonTip::paintEvent(QPaintEvent* const event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.drawPixmap(rect(), m_pixmap);
|
||||
|
||||
QWidget::paintEvent(event);
|
||||
}
|
||||
|
||||
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
|
@@ -7,6 +7,9 @@
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
|
||||
class QEnterEvent;
|
||||
class QEvent;
|
||||
class QMouseEvent;
|
||||
class QPaintEvent;
|
||||
class QPoint;
|
||||
class QString;
|
||||
@@ -29,17 +32,22 @@ public:
|
||||
const QPoint& target_arrow_tip_position, QWidget* parent,
|
||||
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
|
||||
static void HideBalloon();
|
||||
static bool IsCursorInsideWidgetBoundingBox(const QWidget& widget);
|
||||
static bool IsCursorOnBalloonTip();
|
||||
static bool IsWidgetBalloonTipActive(const QWidget& widget);
|
||||
|
||||
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent);
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent* event) override;
|
||||
void leaveEvent(QEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
|
||||
int border_width);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
QColor m_border_color;
|
||||
QPixmap m_pixmap;
|
||||
};
|
||||
|
@@ -9,6 +9,11 @@
|
||||
|
||||
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
|
||||
|
||||
class QEnterEvent;
|
||||
class QEvent;
|
||||
class QHideEvent;
|
||||
class QTimerEvent;
|
||||
|
||||
constexpr int TOOLTIP_DELAY = 300;
|
||||
|
||||
template <class Derived>
|
||||
@@ -22,28 +27,48 @@ public:
|
||||
void SetDescription(QString description) { m_description = std::move(description); }
|
||||
|
||||
private:
|
||||
void enterEvent(QEnterEvent* event) override
|
||||
void enterEvent(QEnterEvent* const event) override
|
||||
{
|
||||
if (m_timer_id)
|
||||
return;
|
||||
m_timer_id = this->startTimer(TOOLTIP_DELAY);
|
||||
// If the timer is already running, or the cursor is reentering the ToolTipWidget after having
|
||||
// hovered over the BalloonTip, don't start a new timer.
|
||||
if (!m_timer_id && !BalloonTip::IsWidgetBalloonTipActive(*this))
|
||||
m_timer_id = this->startTimer(TOOLTIP_DELAY);
|
||||
|
||||
Derived::enterEvent(event);
|
||||
}
|
||||
|
||||
void leaveEvent(QEvent* event) override { KillAndHide(); }
|
||||
void hideEvent(QHideEvent* event) override { KillAndHide(); }
|
||||
void leaveEvent(QEvent* const event) override
|
||||
{
|
||||
// If the cursor would still be inside the ToolTipWidget but the BalloonTip is covering that
|
||||
// part of it, keep the BalloonTip open. In that case the BalloonTip will then track the cursor
|
||||
// and close itself if it leaves the bounding box of this ToolTipWidget.
|
||||
if (!BalloonTip::IsCursorInsideWidgetBoundingBox(*this) || !BalloonTip::IsCursorOnBalloonTip())
|
||||
KillTimerAndHideBalloon();
|
||||
|
||||
void timerEvent(QTimerEvent* event) override
|
||||
Derived::leaveEvent(event);
|
||||
}
|
||||
|
||||
void hideEvent(QHideEvent* const event) override
|
||||
{
|
||||
KillTimerAndHideBalloon();
|
||||
|
||||
Derived::hideEvent(event);
|
||||
}
|
||||
|
||||
void timerEvent(QTimerEvent* const event) override
|
||||
{
|
||||
this->killTimer(*m_timer_id);
|
||||
m_timer_id.reset();
|
||||
|
||||
BalloonTip::ShowBalloon(m_title, m_description,
|
||||
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
|
||||
|
||||
Derived::timerEvent(event);
|
||||
}
|
||||
|
||||
virtual QPoint GetToolTipPosition() const = 0;
|
||||
|
||||
void KillAndHide()
|
||||
void KillTimerAndHideBalloon()
|
||||
{
|
||||
if (m_timer_id)
|
||||
{
|
||||
|
@@ -120,8 +120,13 @@ void VerifyWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_verify_button, &QPushButton::clicked, this, &VerifyWidget::Verify);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_md5_checkbox, &QCheckBox::checkStateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
|
||||
connect(m_sha1_checkbox, &QCheckBox::checkStateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
|
||||
#else
|
||||
connect(m_md5_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
|
||||
connect(m_sha1_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetHash(QLineEdit* line_edit, const std::vector<u8>& hash)
|
||||
|
@@ -935,7 +935,11 @@ std::vector<u8> MemoryViewWidget::ConvertTextToBytes(Type type, QStringView inpu
|
||||
// Confirm it is only hex bytes
|
||||
const QRegularExpression is_hex(QStringLiteral("^([0-9A-F]{2})*$"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
const QRegularExpressionMatch match = is_hex.matchView(input_text);
|
||||
#else
|
||||
const QRegularExpressionMatch match = is_hex.match(input_text);
|
||||
#endif
|
||||
good = match.hasMatch();
|
||||
if (good)
|
||||
{
|
||||
|
@@ -209,6 +209,26 @@ void NetworkWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_dump_format_combo, &QComboBox::currentIndexChanged, this,
|
||||
&NetworkWidget::OnDumpFormatComboChanged);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_dump_ssl_read_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_ssl_write_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_WRITE, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_root_ca_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_ROOT_CA, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_peer_cert_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_PEER_CERT, state == Qt::Checked);
|
||||
});
|
||||
connect(m_verify_certificates_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_VERIFY_CERTIFICATES, state == Qt::Checked);
|
||||
});
|
||||
connect(m_dump_bba_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked);
|
||||
});
|
||||
#else
|
||||
connect(m_dump_ssl_read_checkbox, &QCheckBox::stateChanged, [](int state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked);
|
||||
});
|
||||
@@ -227,6 +247,7 @@ void NetworkWidget::ConnectWidgets()
|
||||
connect(m_dump_bba_checkbox, &QCheckBox::stateChanged, [](int state) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked);
|
||||
});
|
||||
#endif
|
||||
connect(m_open_dump_folder, &QPushButton::clicked, [] {
|
||||
const std::string location = File::GetUserPath(D_DUMPSSL_IDX);
|
||||
const QUrl url = QUrl::fromLocalFile(QString::fromStdString(location));
|
||||
|
@@ -77,8 +77,13 @@ NKitWarningDialog::NKitWarningDialog(QWidget* parent) : QDialog(parent)
|
||||
connect(cancel, &QPushButton::clicked, this, &QDialog::reject);
|
||||
|
||||
ok->setEnabled(false);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(checkbox_accept, &QCheckBox::checkStateChanged,
|
||||
[ok](Qt::CheckState state) { ok->setEnabled(state == Qt::Checked); });
|
||||
#else
|
||||
connect(checkbox_accept, &QCheckBox::stateChanged,
|
||||
[ok](int state) { ok->setEnabled(state == Qt::Checked); });
|
||||
#endif
|
||||
|
||||
connect(this, &QDialog::accepted, [checkbox_skip] {
|
||||
Config::SetBase(Config::MAIN_SKIP_NKIT_WARNING, checkbox_skip->isChecked());
|
||||
|
@@ -242,7 +242,11 @@ void NetPlaySetupDialog::ConnectWidgets()
|
||||
&NetPlaySetupDialog::SaveSettings);
|
||||
|
||||
#ifdef USE_UPNP
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_host_upnp, &QCheckBox::checkStateChanged, this, &NetPlaySetupDialog::SaveSettings);
|
||||
#else
|
||||
connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
connect(m_connect_button, &QPushButton::clicked, this, &QDialog::accept);
|
||||
|
@@ -60,7 +60,11 @@ void PadMappingDialog::ConnectWidgets()
|
||||
}
|
||||
for (const auto& checkbox : m_gba_boxes)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(checkbox, &QCheckBox::checkStateChanged, this, &PadMappingDialog::OnMappingChanged);
|
||||
#else
|
||||
connect(checkbox, &QCheckBox::stateChanged, this, &PadMappingDialog::OnMappingChanged);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#include <QLabel>
|
||||
#include <QRadioButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QTimeZone>
|
||||
#include <QVBoxLayout>
|
||||
#include <cmath>
|
||||
|
||||
@@ -262,9 +263,18 @@ void AdvancedPane::CreateLayout()
|
||||
QStringLiteral("mm"), QStringLiteral("mm:ss")));
|
||||
|
||||
QtUtils::ShowFourDigitYear(m_custom_rtc_datetime);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
m_custom_rtc_datetime->setDateTimeRange(QDateTime({2000, 1, 1}, {0, 0, 0}, QTimeZone::UTC),
|
||||
QDateTime({2099, 12, 31}, {23, 59, 59}, QTimeZone::UTC));
|
||||
#else
|
||||
m_custom_rtc_datetime->setDateTimeRange(QDateTime({2000, 1, 1}, {0, 0, 0}, Qt::UTC),
|
||||
QDateTime({2099, 12, 31}, {23, 59, 59}, Qt::UTC));
|
||||
#endif
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
m_custom_rtc_datetime->setTimeZone(QTimeZone::UTC);
|
||||
#else
|
||||
m_custom_rtc_datetime->setTimeSpec(Qt::UTC);
|
||||
#endif
|
||||
rtc_options->layout()->addWidget(m_custom_rtc_datetime);
|
||||
|
||||
m_custom_rtc_checkbox->SetDescription(
|
||||
|
@@ -241,7 +241,11 @@ void GameCubePane::CreateWidgets()
|
||||
void GameCubePane::ConnectWidgets()
|
||||
{
|
||||
// IPL Settings
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_skip_main_menu, &QCheckBox::checkStateChanged, this, &GameCubePane::SaveSettings);
|
||||
#else
|
||||
connect(m_skip_main_menu, &QCheckBox::stateChanged, this, &GameCubePane::SaveSettings);
|
||||
#endif
|
||||
connect(m_language_combo, &QComboBox::currentIndexChanged, this, &GameCubePane::SaveSettings);
|
||||
|
||||
// Device Settings
|
||||
@@ -272,10 +276,20 @@ void GameCubePane::ConnectWidgets()
|
||||
|
||||
#ifdef HAS_LIBMGBA
|
||||
// GBA Settings
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_gba_threads, &QCheckBox::checkStateChanged, this, &GameCubePane::SaveSettings);
|
||||
#else
|
||||
connect(m_gba_threads, &QCheckBox::stateChanged, this, &GameCubePane::SaveSettings);
|
||||
#endif
|
||||
connect(m_gba_bios_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
|
||||
connect(m_gba_browse_bios, &QPushButton::clicked, this, &GameCubePane::BrowseGBABios);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_gba_save_rom_path, &QCheckBox::checkStateChanged, this,
|
||||
&GameCubePane::SaveRomPathChanged);
|
||||
#else
|
||||
connect(m_gba_save_rom_path, &QCheckBox::stateChanged, this, &GameCubePane::SaveRomPathChanged);
|
||||
#endif
|
||||
connect(m_gba_saves_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
|
||||
connect(m_gba_browse_saves, &QPushButton::clicked, this, &GameCubePane::BrowseGBASaves);
|
||||
for (size_t i = 0; i < m_gba_browse_roms.size(); ++i)
|
||||
|
@@ -95,6 +95,8 @@ void GeneralPane::OnEmulationStateChanged(Core::State state)
|
||||
m_checkbox_discord_presence->setEnabled(!running);
|
||||
#endif
|
||||
m_combobox_fallback_region->setEnabled(!running);
|
||||
|
||||
UpdateDescriptionsUsingHardcoreStatus();
|
||||
}
|
||||
|
||||
void GeneralPane::ConnectLayout()
|
||||
@@ -397,12 +399,6 @@ void GeneralPane::AddDescriptions()
|
||||
"<br><br>This setting cannot be changed while emulation is active."
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
|
||||
#endif
|
||||
static constexpr char TR_SPEEDLIMIT_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Controls how fast emulation runs relative to the original hardware."
|
||||
"<br><br>Values higher than 100% will emulate faster than the original hardware "
|
||||
"can run, if your hardware is able to keep up. Values lower than 100% will slow "
|
||||
"emulation instead. Unlimited will emulate as fast as your hardware is able to."
|
||||
"<br><br><dolphin_emphasis>If unsure, select 100%.</dolphin_emphasis>");
|
||||
static constexpr char TR_UPDATE_TRACK_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Selects which update track Dolphin uses when checking for updates at startup. If a new "
|
||||
"update is available, Dolphin will show a list of changes made since your current version "
|
||||
@@ -450,7 +446,6 @@ void GeneralPane::AddDescriptions()
|
||||
#endif
|
||||
|
||||
m_combobox_speedlimit->SetTitle(tr("Speed Limit"));
|
||||
m_combobox_speedlimit->SetDescription(tr(TR_SPEEDLIMIT_DESCRIPTION));
|
||||
|
||||
if (AutoUpdateChecker::SystemSupportsAutoUpdates())
|
||||
{
|
||||
@@ -468,3 +463,30 @@ void GeneralPane::AddDescriptions()
|
||||
m_button_generate_new_identity->SetDescription(tr(TR_GENERATE_NEW_IDENTITY_DESCRIPTION));
|
||||
#endif
|
||||
}
|
||||
|
||||
void GeneralPane::UpdateDescriptionsUsingHardcoreStatus()
|
||||
{
|
||||
const bool hardcore_enabled = AchievementManager::GetInstance().IsHardcoreModeActive();
|
||||
|
||||
static constexpr char TR_SPEEDLIMIT_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Controls how fast emulation runs relative to the original hardware."
|
||||
"<br><br>Values higher than 100% will emulate faster than the original hardware "
|
||||
"can run, if your hardware is able to keep up. Values lower than 100% will slow "
|
||||
"emulation instead. Unlimited will emulate as fast as your hardware is able to."
|
||||
"<br><br><dolphin_emphasis>If unsure, select 100%.</dolphin_emphasis>");
|
||||
static constexpr char TR_SPEEDLIMIT_RESTRICTION_IN_HARDCORE_DESCRIPTION[] =
|
||||
QT_TR_NOOP("<dolphin_emphasis>When Hardcore Mode is enabled, Speed Limit values less than "
|
||||
"100% will be treated as 100%.</dolphin_emphasis>");
|
||||
|
||||
if (hardcore_enabled)
|
||||
{
|
||||
m_combobox_speedlimit->SetDescription(
|
||||
tr("%1<br><br>%2")
|
||||
.arg(tr(TR_SPEEDLIMIT_DESCRIPTION))
|
||||
.arg(tr(TR_SPEEDLIMIT_RESTRICTION_IN_HARDCORE_DESCRIPTION)));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_combobox_speedlimit->SetDescription(tr(TR_SPEEDLIMIT_DESCRIPTION));
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,7 @@ private:
|
||||
void LoadConfig();
|
||||
void OnSaveConfig();
|
||||
void OnEmulationStateChanged(Core::State state);
|
||||
void UpdateDescriptionsUsingHardcoreStatus();
|
||||
|
||||
// Widgets
|
||||
QVBoxLayout* m_main_layout;
|
||||
|
@@ -15,7 +15,11 @@ TASCheckBox::TASCheckBox(const QString& text, TASInputWindow* parent)
|
||||
{
|
||||
setTristate(true);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(this, &TASCheckBox::checkStateChanged, this, &TASCheckBox::OnUIValueChanged);
|
||||
#else
|
||||
connect(this, &TASCheckBox::stateChanged, this, &TASCheckBox::OnUIValueChanged);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TASCheckBox::GetValue() const
|
||||
@@ -58,7 +62,11 @@ void TASCheckBox::mousePressEvent(QMouseEvent* event)
|
||||
setCheckState(Qt::PartiallyChecked);
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
void TASCheckBox::OnUIValueChanged(Qt::CheckState new_value)
|
||||
#else
|
||||
void TASCheckBox::OnUIValueChanged(int new_value)
|
||||
#endif
|
||||
{
|
||||
m_state.OnUIValueChanged(new_value);
|
||||
}
|
||||
|
@@ -25,7 +25,11 @@ protected:
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
private slots:
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
void OnUIValueChanged(Qt::CheckState new_value);
|
||||
#else
|
||||
void OnUIValueChanged(int new_value);
|
||||
#endif
|
||||
void ApplyControllerValueChange();
|
||||
|
||||
private:
|
||||
|
@@ -66,13 +66,7 @@ static void AddGCAdapter(libusb_device* device);
|
||||
static void ResetRumbleLockNeeded();
|
||||
#endif
|
||||
|
||||
enum class CalledFromReadThread
|
||||
{
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
static void Reset(CalledFromReadThread called_from_read_thread);
|
||||
static void Reset();
|
||||
static void Setup();
|
||||
static void ProcessInputPayload(const u8* data, std::size_t size);
|
||||
static void ReadThreadFunc();
|
||||
@@ -129,24 +123,22 @@ static std::atomic<int> s_controller_write_payload_size{0};
|
||||
|
||||
static std::thread s_read_adapter_thread;
|
||||
static Common::Flag s_read_adapter_thread_running;
|
||||
static Common::Flag s_read_adapter_thread_needs_joining;
|
||||
static std::thread s_write_adapter_thread;
|
||||
static Common::Flag s_write_adapter_thread_running;
|
||||
static Common::Event s_write_happened;
|
||||
|
||||
static std::mutex s_read_mutex;
|
||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||
static std::mutex s_init_mutex;
|
||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
static std::mutex s_read_mutex;
|
||||
#if GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
static std::mutex s_write_mutex;
|
||||
#endif
|
||||
|
||||
static std::thread s_adapter_detect_thread;
|
||||
static Common::Flag s_adapter_detect_thread_running;
|
||||
|
||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||
static Common::Event s_hotplug_event;
|
||||
|
||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||
static std::function<void(void)> s_detect_callback;
|
||||
|
||||
#if defined(__FreeBSD__) && __FreeBSD__ >= 11
|
||||
@@ -180,14 +172,14 @@ static void ReadThreadFunc()
|
||||
bool first_read = true;
|
||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
||||
|
||||
const jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controller_payload", "[B");
|
||||
const jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controllerPayload", "[B");
|
||||
jobject payload_object = env->GetStaticObjectField(s_adapter_class, payload_field);
|
||||
auto* const java_controller_payload = reinterpret_cast<jbyteArray*>(&payload_object);
|
||||
|
||||
// Get function pointers
|
||||
const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "GetFD", "()I");
|
||||
const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "Input", "()I");
|
||||
const jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z");
|
||||
const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "getFd", "()I");
|
||||
const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "input", "()I");
|
||||
const jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z");
|
||||
|
||||
const bool connected = env->CallStaticBooleanMethod(s_adapter_class, openadapter_func);
|
||||
|
||||
@@ -279,7 +271,7 @@ static void WriteThreadFunc()
|
||||
int size = 0;
|
||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
||||
const jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "Output", "([B)I");
|
||||
const jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "output", "([B)I");
|
||||
#endif
|
||||
|
||||
while (s_write_adapter_thread_running.IsSet())
|
||||
@@ -331,7 +323,7 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
|
||||
else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
|
||||
{
|
||||
if (s_handle != nullptr && libusb_get_device(s_handle) == dev)
|
||||
Reset(CalledFromReadThread::No);
|
||||
Reset();
|
||||
|
||||
// Reset a potential error status now that the adapter is unplugged
|
||||
if (s_status == AdapterStatus::Error)
|
||||
@@ -344,6 +336,25 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterConnected(JNIEnv* env, jclass)
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter connected");
|
||||
if (!s_detected)
|
||||
s_hotplug_event.Set();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterDisconnected(JNIEnv* env, jclass)
|
||||
{
|
||||
INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter disconnected");
|
||||
if (s_detected)
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ScanThreadFunc()
|
||||
@@ -393,15 +404,23 @@ static void ScanThreadFunc()
|
||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
||||
|
||||
const jmethodID queryadapter_func =
|
||||
env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
|
||||
const jmethodID enable_hotplug_callback_func =
|
||||
env->GetStaticMethodID(s_adapter_class, "enableHotplugCallback", "()V");
|
||||
env->CallStaticVoidMethod(s_adapter_class, enable_hotplug_callback_func);
|
||||
|
||||
const jmethodID is_usb_device_available_func =
|
||||
env->GetStaticMethodID(s_adapter_class, "isUsbDeviceAvailable", "()Z");
|
||||
|
||||
while (s_adapter_detect_thread_running.IsSet())
|
||||
{
|
||||
if (!s_detected && UseAdapter() &&
|
||||
env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func))
|
||||
env->CallStaticBooleanMethod(s_adapter_class, is_usb_device_available_func))
|
||||
{
|
||||
std::lock_guard lk(s_init_mutex);
|
||||
Setup();
|
||||
Common::SleepCurrentThread(1000);
|
||||
}
|
||||
|
||||
s_hotplug_event.Wait();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -456,7 +475,7 @@ void Init()
|
||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
||||
|
||||
const jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_GCAdapter");
|
||||
const jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/GCAdapter");
|
||||
s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
|
||||
#endif
|
||||
|
||||
@@ -484,9 +503,7 @@ void StopScanThread()
|
||||
{
|
||||
if (s_adapter_detect_thread_running.TestAndClear())
|
||||
{
|
||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||
s_hotplug_event.Set();
|
||||
#endif
|
||||
s_adapter_detect_thread.join();
|
||||
}
|
||||
}
|
||||
@@ -523,11 +540,8 @@ static void Setup()
|
||||
s_detected = true;
|
||||
|
||||
// Make sure the thread isn't in the middle of shutting down while starting a new one
|
||||
if (s_read_adapter_thread_needs_joining.TestAndClear() ||
|
||||
s_read_adapter_thread_running.TestAndClear())
|
||||
{
|
||||
if (s_read_adapter_thread_running.TestAndClear())
|
||||
s_read_adapter_thread.join();
|
||||
}
|
||||
|
||||
s_read_adapter_thread_running.Set(true);
|
||||
s_read_adapter_thread = std::thread(ReadThreadFunc);
|
||||
@@ -691,8 +705,13 @@ void Shutdown()
|
||||
if (s_libusb_context && s_libusb_context->IsValid() && s_libusb_hotplug_enabled)
|
||||
libusb_hotplug_deregister_callback(*s_libusb_context, s_hotplug_handle);
|
||||
#endif
|
||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
||||
const jmethodID disable_hotplug_callback_func =
|
||||
env->GetStaticMethodID(s_adapter_class, "disableHotplugCallback", "()V");
|
||||
env->CallStaticVoidMethod(s_adapter_class, disable_hotplug_callback_func);
|
||||
#endif
|
||||
Reset(CalledFromReadThread::No);
|
||||
Reset();
|
||||
|
||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||
s_libusb_context.reset();
|
||||
@@ -706,12 +725,12 @@ void Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
static void Reset(CalledFromReadThread called_from_read_thread)
|
||||
static void Reset()
|
||||
{
|
||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||
std::unique_lock lock(s_init_mutex, std::defer_lock);
|
||||
if (!lock.try_lock())
|
||||
return;
|
||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||
if (s_status != AdapterStatus::Detected)
|
||||
return;
|
||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
@@ -719,16 +738,8 @@ static void Reset(CalledFromReadThread called_from_read_thread)
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (called_from_read_thread == CalledFromReadThread::No)
|
||||
{
|
||||
if (s_read_adapter_thread_running.TestAndClear())
|
||||
s_read_adapter_thread.join();
|
||||
}
|
||||
else
|
||||
{
|
||||
s_read_adapter_thread_needs_joining.Set();
|
||||
s_read_adapter_thread_running.Clear();
|
||||
}
|
||||
if (s_read_adapter_thread_running.TestAndClear())
|
||||
s_read_adapter_thread.join();
|
||||
// The read thread will close the write thread
|
||||
|
||||
s_port_states.fill({});
|
||||
@@ -807,9 +818,6 @@ void ProcessInputPayload(const u8* data, std::size_t size)
|
||||
// This can occur for a few frames on initialization.
|
||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "error reading payload (size: {}, type: {:02x})", size,
|
||||
data[0]);
|
||||
#if GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||
Reset(CalledFromReadThread::Yes);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
|
Reference in New Issue
Block a user