This commit is contained in:
Nayla Hanegan 2024-09-28 20:23:25 -04:00
commit 1b8bdf7de7
899 changed files with 294977 additions and 411008 deletions

View file

@ -8,10 +8,11 @@ plugins {
@Suppress("UnstableApiUsage")
android {
compileSdkVersion = "android-34"
ndkVersion = "26.1.10909125"
ndkVersion = "27.0.12077973"
buildFeatures {
viewBinding = true
buildConfig = true
}
compileOptions {
@ -116,12 +117,11 @@ android {
}
dependencies {
"baselineProfile"(project(":benchmark"))
baselineProfile(project(":benchmark"))
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
implementation("androidx.core:core-ktx:1.13.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.exifinterface:exifinterface:1.3.7")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
@ -135,7 +135,6 @@ dependencies {
// Kotlin extensions for lifecycle components
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
// Android TV UI libraries.
implementation("androidx.leanback:leanback:1.0.0")
@ -171,7 +170,7 @@ fun getGitVersion(): String {
fun getBuildVersionCode(): Int {
try {
return Integer.valueOf(
val commitCount = Integer.valueOf(
ProcessBuilder("git", "rev-list", "--first-parent", "--count", "HEAD")
.directory(project.rootDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
@ -179,6 +178,15 @@ fun getBuildVersionCode(): Int {
.start().inputStream.bufferedReader().use { it.readText() }
.trim()
)
val isRelease = ProcessBuilder("git", "describe", "--exact-match", "HEAD")
.directory(project.rootDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
.waitFor() == 0
return commitCount * 2 + (if (isRelease) 0 else 1)
} catch (e: Exception) {
logger.error("Cannot find git, defaulting to dummy version code")
}

View file

@ -2,46 +2,6 @@
# than the space savings obfuscation could give us
-dontobfuscate
#
# Kotlin Serialization
#
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
# Don't print notes about potential mistakes or omissions in the configuration for kotlinx-serialization classes
# See also https://github.com/Kotlin/kotlinx.serialization/issues/1900
-dontnote kotlinx.serialization.**
# Serialization core uses `java.lang.ClassValue` for caching inside these specified classes.
# If there is no `java.lang.ClassValue` (for example, in Android), then R8/ProGuard will print a warning.
# However, since in this case they will not be used, we can disable these warnings
-dontwarn kotlinx.serialization.internal.ClassValueReferences
# Required for R8 full mode
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket

View file

@ -6,6 +6,7 @@ import android.app.Application;
import android.content.Context;
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;
@ -20,6 +21,7 @@ public class DolphinApplication extends Application
{
super.onCreate();
application = this;
registerActivityLifecycleCallbacks(new ActivityTracker());
VolleyUtil.init(getApplicationContext());
System.loadLibrary("main");

View file

@ -80,7 +80,6 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
private var infinityFigureData = Figure(-1, "Position")
private var skylanderSlot = -1
private var infinityPosition = -1
private var infinityListPosition = -1
private lateinit var skylandersBinding: DialogNfcFiguresManagerBinding
private lateinit var infinityBinding: DialogNfcFiguresManagerBinding
@ -140,12 +139,14 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
if (infinityFigures.isEmpty()) {
infinityFigures.apply {
add(FigureSlot(getString(R.string.infinity_hexagon_label), 0))
add(FigureSlot(getString(R.string.infinity_p1_label), 1))
add(FigureSlot(getString(R.string.infinity_p1a1_label), 3))
add(FigureSlot(getString(R.string.infinity_power_hex_two_label), 1))
add(FigureSlot(getString(R.string.infinity_power_hex_three_label), 2))
add(FigureSlot(getString(R.string.infinity_p1_label), 3))
add(FigureSlot(getString(R.string.infinity_p1a1_label), 4))
add(FigureSlot(getString(R.string.infinity_p1a2_label), 5))
add(FigureSlot(getString(R.string.infinity_p2_label), 2))
add(FigureSlot(getString(R.string.infinity_p2a1_label), 4))
add(FigureSlot(getString(R.string.infinity_p2a2_label), 6))
add(FigureSlot(getString(R.string.infinity_p2_label), 6))
add(FigureSlot(getString(R.string.infinity_p2a1_label), 7))
add(FigureSlot(getString(R.string.infinity_p2a2_label), 8))
}
}
}
@ -164,7 +165,6 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
putInt(EXTRA_SKYLANDER_VAR, skylanderData.variant)
putString(EXTRA_SKYLANDER_NAME, skylanderData.name)
putInt(EXTRA_INFINITY_POSITION, infinityPosition)
putInt(EXTRA_INFINITY_LIST_POSITION, infinityListPosition)
putLong(EXTRA_INFINITY_NUM, infinityFigureData.number)
putString(EXTRA_INFINITY_NAME, infinityFigureData.name)
}
@ -183,7 +183,6 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
savedInstanceState.getString(EXTRA_SKYLANDER_NAME)!!
)
infinityPosition = savedInstanceState.getInt(EXTRA_INFINITY_POSITION)
infinityListPosition = savedInstanceState.getInt(EXTRA_INFINITY_LIST_POSITION)
infinityFigureData = Figure(
savedInstanceState.getLong(EXTRA_INFINITY_NUM),
savedInstanceState.getString(EXTRA_INFINITY_NAME)!!
@ -297,11 +296,10 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
} else if (requestCode == REQUEST_INFINITY_FIGURE_FILE) {
val label = InfinityConfig.loadFigure(infinityPosition, result!!.data.toString())
if (label != null && label != "Unknown Figure") {
clearInfinityFigure(infinityListPosition)
infinityFigures[infinityListPosition].label = label
infinityBinding.figureManager.adapter?.notifyItemChanged(infinityListPosition)
clearInfinityFigure(infinityPosition)
infinityFigures[infinityPosition].label = label
infinityBinding.figureManager.adapter?.notifyItemChanged(infinityPosition)
infinityPosition = -1
infinityListPosition = -1
infinityFigureData = Figure.BLANK_FIGURE
} else {
MaterialAlertDialogBuilder(this)
@ -317,11 +315,10 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
result!!.data.toString(),
infinityPosition
)
clearInfinityFigure(infinityListPosition)
infinityFigures[infinityListPosition].label = label!!
infinityBinding.figureManager.adapter?.notifyItemChanged(infinityListPosition)
clearInfinityFigure(infinityPosition)
infinityFigures[infinityPosition].label = label!!
infinityBinding.figureManager.adapter?.notifyItemChanged(infinityPosition)
infinityPosition = -1
infinityListPosition = -1
infinityFigureData = Figure.BLANK_FIGURE
}
}
@ -906,18 +903,19 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
fun setInfinityFigureData(num: Long, name: String, position: Int, listPosition: Int) {
infinityFigureData = Figure(num, name)
infinityPosition = position
infinityListPosition = listPosition
}
fun clearInfinityFigure(position: Int) {
when (position) {
0 -> infinityFigures[position].label = getString(R.string.infinity_hexagon_label)
1 -> infinityFigures[position].label = getString(R.string.infinity_p1_label)
2 -> infinityFigures[position].label = getString(R.string.infinity_p1a1_label)
3 -> infinityFigures[position].label = getString(R.string.infinity_p1a2_label)
4 -> infinityFigures[position].label = getString(R.string.infinity_p2_label)
5 -> infinityFigures[position].label = getString(R.string.infinity_p2a1_label)
6 -> infinityFigures[position].label = getString(R.string.infinity_p2a2_label)
1 -> infinityFigures[position].label = getString(R.string.infinity_power_hex_two_label)
2 -> infinityFigures[position].label = getString(R.string.infinity_power_hex_three_label)
3 -> infinityFigures[position].label = getString(R.string.infinity_p1_label)
4 -> infinityFigures[position].label = getString(R.string.infinity_p1a1_label)
5 -> infinityFigures[position].label = getString(R.string.infinity_p1a2_label)
6 -> infinityFigures[position].label = getString(R.string.infinity_p2_label)
7 -> infinityFigures[position].label = getString(R.string.infinity_p2a1_label)
8 -> infinityFigures[position].label = getString(R.string.infinity_p2a2_label)
}
infinityBinding.figureManager.adapter?.notifyItemChanged(position)
}

View file

@ -75,6 +75,11 @@ class FigureSlotAdapter(
}
1, 2 -> {
// Hexagon Power Discs
validFigures.filter { (_, value) -> value in 4000000..4999999 }
}
3, 6 -> {
// Characters
validFigures.filter { (_, value) -> value in 1000000..1999999 }
}

View file

@ -19,19 +19,31 @@ class DolphinSensorEventListener : SensorEventListener {
private class AxisSetDetails(val firstAxisOfSet: Int, val axisSetType: Int)
private class SensorDetails(
val sensor: Sensor,
val sensorType: Int,
val axisNames: Array<String>,
val axisSetDetails: Array<AxisSetDetails>
) {
var isSuspended = true
var hasRegisteredListener = false
}
private val sensorManager: SensorManager?
private val sensorDetails = HashMap<Sensor, SensorDetails>()
private val sensorDetails = ArrayList<SensorDetails>()
private val rotateCoordinatesForScreenOrientation: Boolean
/**
* AOSP has a bug in InputDeviceSensorManager where
* InputSensorEventListenerDelegate.removeSensor attempts to modify an ArrayList it's iterating
* through in a way that throws a ConcurrentModificationException. Because of this, we can't
* suspend individual sensors for InputDevices, but we can suspend all sensors at once.
*/
private val canSuspendSensorsIndividually: Boolean
private var unsuspendedSensors = 0
private var deviceQualifier = ""
@Keep
@ -39,25 +51,22 @@ class DolphinSensorEventListener : SensorEventListener {
sensorManager = DolphinApplication.getAppContext()
.getSystemService(Context.SENSOR_SERVICE) as SensorManager?
rotateCoordinatesForScreenOrientation = true
canSuspendSensorsIndividually = true
addSensors()
sortSensorDetails()
}
@Keep
constructor(inputDevice: InputDevice) {
rotateCoordinatesForScreenOrientation = false
sensorManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
inputDevice.sensorManager
// TODO: There is a bug where after suspending sensors, onSensorChanged can get called for
// a sensor that we never registered as a listener for. The way our code is currently written,
// this causes a NullPointerException, but if we checked for null we would instead have the
// problem of being spammed with onSensorChanged calls even though the sensor shouldn't be
// enabled. For now, let's comment out the ability to use InputDevice sensors.
//addSensors();
canSuspendSensorsIndividually = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
sensorManager = inputDevice.sensorManager
addSensors()
} else {
null
sensorManager = null
}
sortSensorDetails()
}
private fun addSensors() {
@ -254,15 +263,22 @@ class DolphinSensorEventListener : SensorEventListener {
) {
val sensor = sensorManager!!.getDefaultSensor(sensorType)
if (sensor != null) {
sensorDetails[sensor] = SensorDetails(sensorType, axisNames, axisSetDetails)
sensorDetails.add(SensorDetails(sensor, sensorType, axisNames, axisSetDetails))
}
}
private fun sortSensorDetails() {
Collections.sort(
sensorDetails,
Comparator.comparingInt { s: SensorDetails -> s.sensorType }
)
}
override fun onSensorChanged(sensorEvent: SensorEvent) {
val sensorDetails = sensorDetails[sensorEvent.sensor]
val sensorDetails = sensorDetails.first{s -> sensorsAreEqual(s.sensor, sensorEvent.sensor)}
val values = sensorEvent.values
val axisNames = sensorDetails!!.axisNames
val axisNames = sensorDetails.axisNames
val axisSetDetails = sensorDetails.axisSetDetails
var eventAxisIndex = 0
@ -356,7 +372,7 @@ class DolphinSensorEventListener : SensorEventListener {
}
}
if (!keepSensorAlive) {
setSensorSuspended(sensorEvent.sensor, sensorDetails, true)
setSensorSuspended(sensorDetails, true)
}
}
@ -381,18 +397,14 @@ class DolphinSensorEventListener : SensorEventListener {
*/
@Keep
fun requestUnsuspendSensor(axisName: String) {
for ((key, value) in sensorDetails) {
if (listOf(*value.axisNames).contains(axisName)) {
setSensorSuspended(key, value, false)
for (sd in sensorDetails) {
if (listOf(*sd.axisNames).contains(axisName)) {
setSensorSuspended(sd, false)
}
}
}
private fun setSensorSuspended(
sensor: Sensor,
sensorDetails: SensorDetails,
suspend: Boolean
) {
private fun setSensorSuspended(sensorDetails: SensorDetails, suspend: Boolean) {
var changeOccurred = false
synchronized(sensorDetails) {
@ -403,10 +415,46 @@ class DolphinSensorEventListener : SensorEventListener {
suspend
)
if (suspend)
sensorManager!!.unregisterListener(this, sensor)
else
sensorManager!!.registerListener(this, sensor, SAMPLING_PERIOD_US)
if (suspend) {
unsuspendedSensors -= 1
} else {
unsuspendedSensors += 1
}
if (canSuspendSensorsIndividually) {
if (suspend) {
sensorManager!!.unregisterListener(this, sensorDetails.sensor)
} else {
sensorManager!!.registerListener(
this,
sensorDetails.sensor,
SAMPLING_PERIOD_US
)
}
sensorDetails.hasRegisteredListener = !suspend
} else {
if (suspend) {
// If there are no unsuspended sensors left, unregister them all.
// Otherwise, leave unregistering for later. A possible alternative could be
// to unregister everything and then re-register the sensors we still want,
// but I fear this could lead to dropped inputs.
if (unsuspendedSensors == 0) {
sensorManager!!.unregisterListener(this)
for (sd in this.sensorDetails) {
sd.hasRegisteredListener = false
}
}
} else {
if (!sensorDetails.hasRegisteredListener) {
sensorManager!!.registerListener(
this,
sensorDetails.sensor,
SAMPLING_PERIOD_US
)
sensorDetails.hasRegisteredListener = true
}
}
}
sensorDetails.isSuspended = suspend
@ -415,14 +463,14 @@ class DolphinSensorEventListener : SensorEventListener {
}
if (changeOccurred) {
Log.info((if (suspend) "Suspended sensor " else "Unsuspended sensor ") + sensor.name)
Log.info((if (suspend) "Suspended sensor " else "Unsuspended sensor ") + sensorDetails.sensor.name)
}
}
@Keep
fun getAxisNames(): Array<String> {
val axisNames = ArrayList<String>()
for (sensorDetails in sensorDetailsSorted) {
for (sensorDetails in sensorDetails) {
sensorDetails.axisNames.forEach { axisNames.add(it) }
}
return axisNames.toArray(arrayOf())
@ -432,7 +480,7 @@ class DolphinSensorEventListener : SensorEventListener {
fun getNegativeAxes(): BooleanArray {
val negativeAxes = ArrayList<Boolean>()
for (sensorDetails in sensorDetailsSorted) {
for (sensorDetails in sensorDetails) {
var eventAxisIndex = 0
var detailsAxisIndex = 0
var detailsAxisSetIndex = 0
@ -467,22 +515,13 @@ class DolphinSensorEventListener : SensorEventListener {
return result
}
private val sensorDetailsSorted: List<SensorDetails>
get() {
val sensorDetails = ArrayList(sensorDetails.values)
Collections.sort(
sensorDetails,
Comparator.comparingInt { s: SensorDetails -> s.sensorType }
)
return sensorDetails
}
companion object {
// Set of three axes. Creates a negative companion to each axis, and corrects for device rotation.
private const val AXIS_SET_TYPE_DEVICE_COORDINATES = 0
// Set of three axes. Creates a negative companion to each axis.
private const val AXIS_SET_TYPE_OTHER_COORDINATES = 1
private var deviceRotation = Surface.ROTATION_0
// The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS
@ -500,5 +539,13 @@ class DolphinSensorEventListener : SensorEventListener {
fun setDeviceRotation(deviceRotation: Int) {
this.deviceRotation = deviceRotation
}
private fun sensorsAreEqual(s1: Sensor, s2: Sensor): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && s1.id > 0 && s2.id > 0) {
s1.type == s2.type && s1.id == s2.id
} else {
s1.type == s2.type && s1.name == s2.name
}
}
}
}

View file

@ -101,8 +101,8 @@ class AdvancedMappingDialog(
return
} else if (!isInput) {
// Find the first device that has an output. (Most built-in devices don't have any)
val deviceWithOutputs = devices.first { deviceHasOutputs(it) }
if (deviceWithOutputs.isNotEmpty()) {
val deviceWithOutputs = devices.firstOrNull { deviceHasOutputs(it) }
if (deviceWithOutputs != null) {
setSelectedDevice(deviceWithOutputs)
binding.dropdownDevice.setText(deviceWithOutputs, false)
return

View file

@ -0,0 +1,41 @@
package org.dolphinemu.dolphinemu.utils
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
class ActivityTracker : ActivityLifecycleCallbacks {
val resumedActivities = HashSet<Activity>()
var backgroundExecutionAllowed = false
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {
resumedActivities.add(activity)
if (!backgroundExecutionAllowed && !resumedActivities.isEmpty()) {
backgroundExecutionAllowed = true
setBackgroundExecutionAllowedNative(true)
}
}
override fun onActivityPaused(activity: Activity) {
resumedActivities.remove(activity)
if (backgroundExecutionAllowed && resumedActivities.isEmpty()) {
backgroundExecutionAllowed = false
setBackgroundExecutionAllowedNative(false)
}
}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
companion object {
@JvmStatic
external fun setBackgroundExecutionAllowedNative(allowed: Boolean)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

@ -1,27 +1,33 @@
<vector
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportHeight="2048"
android:viewportWidth="2048">
<path
android:fillColor="#0057ab"
android:pathData="m2046.8,1538.7c-6.8,-22.8 -49.3,-160.5 -120.8,-292.6 -94.3,-173.9 -175.3,-298.3 -310.4,-424.6 -43.4,-40.6 -84.6,-76.9 -127.4,-109.5l0,0c0,0 -0.3,-0.2 -0.9,-0.7 -0.8,-0.6 -1.6,-1.2 -2.4,-1.8 -16,-12.4 -83.3,-69.1 -59.4,-131.8 8,-21 27.4,-38.5 50.5,-52.6l124.1,-1v-46l-0,0c0,0 0,-0 0,-0 0,0 -72.7,-24.7 -199.8,-16.5 -119.3,7.7 -226.5,77.4 -246.6,87.2 -64.3,-18.4 -137.6,-34.6 -223.3,-49.2 -296.6,-50.3 -547.6,29.9 -673.6,117.3 -165.1,114.6 -160.5,221.4 -174.1,274.8 -8.4,33.1 -83.4,94.3 -82.5,137.2v45.2l15.5,16 58.4,-19.8 7,-24.4c24,-8.6 50.3,-20.5 74.2,-30.4 59.6,-24.5 64.2,-37.9 227.1,-62.2 75,-11.2 153.8,-14.4 212.6,-14.9 22.9,48.3 79.7,147.5 178.1,195 64,30.9 135.7,46.8 192.9,54.9l-7.5,37.7 113.7,16.8v-46l-0.1,-0c0,0 0,0 0,0 0,0 -95.4,-39.6 -154.1,-98.4 -40,-40 -49.5,-100.6 -51.6,-135.3l0.4,0c96.2,18.3 180.2,31.5 381.2,108.4 37.2,14.2 71.8,29.8 103.4,45.6l-5.9,3.3 90.6,73 108.2,89.5v-46l-0.4,-0.3c-1.6,-2 -124.8,-154.6 -331.8,-256.7 -171.1,-84.4 -311.6,-126.1 -506.2,-135.5 -212.8,-10.3 -369.5,17 -369.5,17 0,0 4.4,-94.5 165,-169.9 139.7,-65.5 359.4,-76.5 611.6,-12.1 356.3,90.9 477.8,245.6 631,405.6 97.2,101.5 186.6,244.2 243,343l-9.5,-4.3 29.2,75.4 41.8,89.1v-46.3l-1.2,-3.6z" />
<path android:pathData="m1926,1292c-94.3,-173.9 -175.3,-298.3 -310.4,-424.6 -43.4,-40.6 -84.6,-76.9 -127.4,-109.5l0,0c0,0 -0.3,-0.2 -1,-0.7 -0.8,-0.6 -1.5,-1.2 -2.3,-1.8 -15.9,-12.3 -83.4,-69.1 -59.4,-131.8 26.3,-68.8 174.6,-99.6 174.6,-99.6 0,0 -72.7,-24.7 -199.8,-16.5 -119.3,7.7 -226.5,77.4 -246.6,87.2 -64.3,-18.4 -137.6,-34.6 -223.3,-49.2 -296.6,-50.3 -547.6,29.9 -673.6,117.3 -165.1,114.6 -160.5,221.4 -174.1,274.8 -9.8,38.4 -109.4,114.8 -75.5,156.4 21.1,26 91.4,-9.3 148.1,-32.6 59.6,-24.5 64.2,-37.9 227.1,-62.2 75,-11.2 153.8,-14.4 212.6,-14.9 22.9,48.3 79.7,147.5 178.1,195 132.9,64.2 299,63.4 299,63.4 0,0 -95.4,-39.6 -154.1,-98.4 -40,-40 -49.5,-100.6 -51.6,-135.3l0.4,0c96.2,18.3 180.2,31.5 381.2,108.4 175.8,67.3 295.9,165.3 295.9,165.3 0,0 -123.5,-154 -331.9,-256.8 -171.1,-84.4 -311.6,-126.1 -506.2,-135.5 -212.8,-10.3 -369.5,17 -369.5,17 0,0 4.4,-94.5 165,-169.9 139.7,-65.5 359.4,-76.5 611.6,-12.1 356.3,90.9 477.8,245.6 631,405.6 163.1,170.2 304.1,456.7 304.1,456.7 0,0 -43.5,-151.4 -121.8,-295.9z">
<vector android:height="107dp" android:viewportHeight="571.3"
android:viewportWidth="1024" android:width="192dp"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillType="evenOdd"
android:pathData="M1022.6,571.2c-1.3,0.2 -38.5,-170 -134.5,-264.6 -78.3,-77.1 -255.3,-227.8 -485.3,-211.4 -180,12.8 -155.3,99.2 -145.3,95.1 28.2,-11.8 160.4,-49.3 301.5,29.6 124.7,69.7 172.5,186.6 165,195.7 -1.6,1.9 -45.2,-108.8 -244.5,-151 -154.5,-32.7 -316.6,-5.9 -385.6,21.5 -28.1,11.1 -73.9,41.6 -82.8,11.1 -8.3,-28.5 33.5,-51.5 38.4,-70.4 6.4,-24.7 2,-147.1 179.8,-199.5 69.1,-20.4 206.7,-40 356.8,6.1 7.4,1.6 16.3,0.9 22.1,-2.6 12.9,-8 39,-24.9 91.2,-29.6 100.3,-8.9 129,34.5 124.2,42.9 -2.4,4.2 -80.1,-20.5 -96.6,28.1 -6.1,18.1 6.8,28.3 15.9,34.6 32.7,21.3 63.4,45.4 91.8,72.1 66.5,62.2 112.8,129 150.4,219.7 29.9,72.2 43.6,171.9 37.4,172.8Z" android:strokeWidth="0">
<aapt:attr name="android:fillColor">
<gradient
android:endX="0"
android:endY="1588"
android:startX="0"
android:startY="506"
android:type="linear">
<item
android:color="#FF46D4FF"
android:offset="0" />
<item
android:color="#FF1792FF"
android:offset="1" />
<gradient android:endX="225.6" android:endY="-23.7"
android:startX="921.6" android:startY="672.3" android:type="linear">
<item android:color="#FF3217FF" android:offset="0"/>
<item android:color="#FF2B38FF" android:offset="0.3"/>
<item android:color="#FF2455FF" android:offset="0.5"/>
<item android:color="#FF1D74FF" android:offset="0.8"/>
<item android:color="#FF1792FF" android:offset="1"/>
</gradient>
</aapt:attr>
</path>
<path android:fillType="evenOdd"
android:pathData="M1022.7,571.3c-2.2,0.3 -73.6,-154.8 -169.6,-249.4 -78.3,-77.1 -220.9,-203.5 -451.3,-194.6 -102.8,4 -151.1,51.4 -144.1,63 6.8,11.3 140.6,-31 283.6,44.4 130.1,68.6 185.5,179.3 182.8,181.3 -2.2,1.7 -101.1,-86.7 -250.5,-125.8 -0.6,-0.1 -1.1,-0.2 -1.7,-0.3 -26.8,-6.6 -29,29.3 -1.5,63.5 31.9,39.7 46.9,43.2 45.3,48.3 -2,6.4 -63.1,8.8 -122.2,-32.5 -52,-36.4 -75.4,-85.8 -82.3,-92.1 -5.8,-5.3 -12.5,-4.1 -20.2,-3.9 -0.3,0 -0.6,0 -0.9,0 -83,3.2 -132.4,12.4 -196.1,37.7 -28.1,11.1 -66.8,13.6 -88.8,-9.2 -22.9,-23.6 39,-57 44,-75.9 6.4,-24.7 2.5,-121.3 180.4,-173.7 69.2,-20.4 207.1,-40.1 357.5,6.3 7.6,1.9 14.4,2.1 21.4,-2.8 18.6,-13.1 49.7,-30.8 101.9,-35.4 87.7,-7.8 115.7,19.1 113.4,24.1 -2,4.4 -80.1,4.2 -96.6,52.7 -6.1,18.1 6.8,28.3 15.9,34.6 32.7,21.3 63.4,45.4 91.8,72.1 66.6,62.2 112.8,129 150.4,219.7 29.9,72.2 40,147.8 37.5,148.2Z" android:strokeWidth="0">
<aapt:attr name="android:fillColor">
<gradient android:endX="438.4" android:endY="12.1"
android:startX="551.7" android:startY="654.3" android:type="linear">
<item android:color="#FF0E80FF" android:offset="0"/>
<item android:color="#FF0789FF" android:offset="0.1"/>
<item android:color="#FF0090FF" android:offset="0.3"/>
<item android:color="#FF009DFF" android:offset="0.4"/>
<item android:color="#FF00AAFF" android:offset="0.5"/>
<item android:color="#FF00B7FF" android:offset="0.6"/>
<item android:color="#FF00C4FF" android:offset="0.8"/>
<item android:color="#FF21CCFF" android:offset="0.9"/>
<item android:color="#FF46D4FF" android:offset="1"/>
</gradient>
</aapt:attr>
</path>

View file

@ -1,19 +1,48 @@
<vector android:height="192dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="192dp"
xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#0057AB" android:fillType="nonZero"
android:pathData="M344.28,301.46C343.6,299.18 339.35,285.41 332.2,272.21C322.77,254.81 314.66,242.38 301.16,229.75C296.81,225.69 292.7,222.06 288.41,218.8L288.42,218.8C288.42,218.8 288.38,218.78 288.32,218.73C288.24,218.67 288.16,218.61 288.08,218.55C286.48,217.32 279.75,211.64 282.15,205.38C282.95,203.28 284.89,201.53 287.19,200.12L299.6,200.02L299.6,195.42L299.6,195.42C299.6,195.42 299.6,195.42 299.6,195.42C299.6,195.42 292.33,192.95 279.62,193.77C267.69,194.53 256.97,201.51 254.96,202.49C248.54,200.65 241.2,199.03 232.63,197.57C202.97,192.54 177.86,200.56 165.27,209.3C148.76,220.76 149.21,231.44 147.85,236.78C147.01,240.09 139.51,246.21 139.6,250.5L139.6,255.02L141.15,256.61L146.99,254.63L147.69,252.19C150.09,251.34 152.72,250.14 155.11,249.16C161.07,246.71 161.53,245.37 177.82,242.94C185.32,241.82 193.21,241.5 199.08,241.45C201.37,246.28 207.05,256.2 216.89,260.96C223.3,264.05 230.47,265.64 236.18,266.45L235.43,270.22L246.8,271.9L246.8,267.3L246.79,267.3C246.79,267.3 246.79,267.3 246.79,267.3C246.79,267.3 237.25,263.34 231.38,257.47C227.38,253.47 226.43,247.4 226.23,243.93L226.26,243.93C235.88,245.76 244.28,247.08 264.38,254.78C268.09,256.2 271.56,257.76 274.72,259.34L274.12,259.67L283.18,266.98L294,275.93L294,271.33L293.96,271.3C293.8,271.1 281.48,255.84 260.78,245.63C243.67,237.19 229.62,233.02 210.16,232.07C188.88,231.04 173.21,233.77 173.21,233.77C173.21,233.77 173.64,224.32 189.71,216.79C203.67,210.23 225.65,209.14 250.86,215.57C286.49,224.66 298.64,240.14 313.96,256.13C323.69,266.27 332.62,280.55 338.26,290.43L337.31,290L340.23,297.54L344.4,306.45L344.4,301.82L344.28,301.46z"
android:strokeColor="#00000000" android:strokeLineCap="butt"
android:strokeLineJoin="miter" android:strokeWidth="0.1"/>
<path android:fillType="nonZero"
android:pathData="M332.2,276.8C322.77,259.41 314.67,246.97 301.16,234.34C296.82,230.28 292.7,226.65 288.41,223.39L288.42,223.39C288.42,223.39 288.39,223.37 288.32,223.32C288.25,223.27 288.17,223.21 288.09,223.15C286.5,221.92 279.75,216.24 282.15,209.97C284.78,203.09 299.6,200.01 299.6,200.01C299.6,200.01 292.34,197.54 279.62,198.36C267.69,199.12 256.97,206.1 254.96,207.08C248.54,205.24 241.21,203.62 232.63,202.17C202.97,197.13 177.87,205.15 165.27,213.9C148.76,225.36 149.22,236.03 147.86,241.38C146.88,245.22 136.92,252.85 140.3,257.01C142.41,259.61 149.44,256.08 155.11,253.75C161.07,251.3 161.53,249.96 177.83,247.53C185.32,246.42 193.21,246.09 199.08,246.05C201.37,250.87 207.05,260.79 216.9,265.55C230.19,271.97 246.8,271.89 246.8,271.89C246.8,271.89 237.25,267.93 231.38,262.06C227.39,258.06 226.43,251.99 226.23,248.52L226.26,248.53C235.88,250.35 244.29,251.67 264.38,259.37C281.96,266.1 293.97,275.9 293.97,275.9C293.97,275.9 281.62,260.5 260.78,250.22C243.67,241.78 229.63,237.61 210.16,236.67C188.89,235.63 173.21,238.37 173.21,238.37C173.21,238.37 173.65,228.91 189.71,221.38C203.68,214.83 225.65,213.73 250.87,220.16C286.49,229.25 298.64,244.73 313.97,260.72C330.28,277.74 344.38,306.39 344.38,306.39C344.38,306.39 340.03,291.25 332.2,276.8z"
android:strokeColor="#00000000" android:strokeLineCap="butt"
android:strokeLineJoin="miter" android:strokeWidth="0.1">
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M74.85,66.87c-0.05,0 -1.66,-7.31 -5.79,-11.38 -3.37,-3.32 -10.98,-9.8 -20.88,-9.09 -7.74,0.55 -6.68,4.27 -6.25,4.09 1.21,-0.51 6.9,-2.12 12.97,1.27 5.37,3 7.42,8.03 7.1,8.42 -0.07,0.08 -1.94,-4.68 -10.52,-6.5 -6.65,-1.41 -13.62,-0.26 -16.59,0.92 -1.21,0.48 -3.18,1.79 -3.56,0.48 -0.36,-1.23 1.44,-2.22 1.65,-3.03 0.28,-1.06 0.09,-6.33 7.74,-8.59 2.97,-0.88 8.89,-1.72 15.35,0.26 0.32,0.07 0.7,0.04 0.95,-0.11 0.55,-0.34 1.68,-1.07 3.92,-1.27 4.31,-0.38 5.55,1.48 5.34,1.84 -0.1,0.18 -3.45,-0.88 -4.15,1.21 -0.26,0.78 0.29,1.22 0.69,1.49 1.41,0.92 2.73,1.95 3.95,3.1 2.86,2.68 4.85,5.55 6.47,9.45 1.29,3.11 1.87,7.39 1.61,7.43Z"
android:strokeWidth="0"
android:fillType="evenOdd">
<aapt:attr name="android:fillColor">
<gradient android:endX="139.6" android:endY="306.4"
android:startX="139.6" android:startY="198.2" android:type="linear">
<item android:color="#FF46D4FF" android:offset="0"/>
<item android:color="#FF1792FF" android:offset="1"/>
<gradient
android:startX="70.5"
android:startY="71.22"
android:endX="40.56"
android:endY="41.28"
android:type="linear">
<item android:offset="0" android:color="#FF3217FF"/>
<item android:offset="0.26" android:color="#FF2B38FF"/>
<item android:offset="0.5" android:color="#FF2455FF"/>
<item android:offset="0.75" android:color="#FF1D74FF"/>
<item android:offset="1" android:color="#FF1792FF"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M74.85,66.88c-0.09,0.01 -3.17,-6.66 -7.3,-10.73 -3.37,-3.32 -9.5,-8.76 -19.42,-8.37 -4.43,0.17 -6.5,2.21 -6.2,2.71 0.29,0.49 6.05,-1.33 12.2,1.91 5.6,2.95 7.98,7.71 7.86,7.8 -0.1,0.07 -4.35,-3.73 -10.78,-5.41 -0.02,0 -0.05,0 -0.07,-0.01 -1.15,-0.28 -1.25,1.26 -0.06,2.73 1.37,1.71 2.02,1.86 1.95,2.08 -0.08,0.28 -2.72,0.38 -5.26,-1.4 -2.24,-1.57 -3.24,-3.69 -3.54,-3.96 -0.25,-0.23 -0.54,-0.17 -0.87,-0.17 -0.01,0 -0.03,0 -0.04,0 -3.57,0.14 -5.7,0.53 -8.44,1.62 -1.21,0.48 -2.87,0.58 -3.82,-0.4 -0.98,-1.02 1.68,-2.45 1.89,-3.26 0.28,-1.06 0.11,-5.22 7.76,-7.48 2.98,-0.88 8.91,-1.72 15.38,0.27 0.33,0.08 0.62,0.09 0.92,-0.12 0.8,-0.56 2.14,-1.32 4.38,-1.52 3.77,-0.34 4.98,0.82 4.88,1.04 -0.09,0.19 -3.45,0.18 -4.15,2.27 -0.26,0.78 0.29,1.22 0.69,1.49 1.41,0.92 2.73,1.95 3.95,3.1 2.86,2.68 4.85,5.55 6.47,9.45 1.29,3.11 1.72,6.36 1.61,6.38Z"
android:strokeWidth="0"
android:fillType="evenOdd">
<aapt:attr name="android:fillColor">
<gradient
android:startX="54.59"
android:startY="70.45"
android:endX="49.71"
android:endY="42.82"
android:type="linear">
<item android:offset="0" android:color="#FF0E80FF"/>
<item android:offset="0.13" android:color="#FF0789FF"/>
<item android:offset="0.25" android:color="#FF0090FF"/>
<item android:offset="0.38" android:color="#FF009DFF"/>
<item android:offset="0.5" android:color="#FF00AAFF"/>
<item android:offset="0.62" android:color="#FF00B7FF"/>
<item android:offset="0.75" android:color="#FF00C4FF"/>
<item android:offset="0.87" android:color="#FF21CCFF"/>
<item android:offset="1" android:color="#FF46D4FF"/>
</gradient>
</aapt:attr>
</path>

View file

@ -1,11 +1,11 @@
<vector android:height="192dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="192dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000000" android:fillType="evenOdd"
android:pathData="M226.26,250.63C235.88,252.46 244.28,253.78 264.38,261.48C281.96,268.21 293.97,278 293.97,278C293.97,278 281.62,262.6 260.78,252.32C243.67,243.89 229.62,239.72 210.16,238.77C188.88,237.74 173.2,240.47 173.2,240.47C173.2,240.47 173.64,231.02 189.7,223.48C203.67,216.93 225.64,215.84 250.86,222.27C286.49,231.36 298.64,246.83 313.96,262.83C330.27,279.85 344.38,308.49 344.38,308.49C344.38,308.49 340.03,293.36 332.2,278.91C322.77,261.51 314.66,249.08 301.15,236.45C283.48,219.92 269.59,210.54 232.63,204.27C202.96,199.24 177.86,207.26 165.27,216C148.76,227.46 149.21,238.14 147.85,243.48C146.87,247.32 136.91,254.96 140.3,259.12C142.41,261.71 149.44,258.19 155.11,255.86C161.07,253.4 161.53,252.07 177.82,249.64C185.49,248.5 193.56,248.19 199.47,248.15"
android:strokeColor="#00000000" android:strokeLineCap="butt"
android:strokeLineJoin="miter" android:strokeWidth="0.1"/>
<path android:fillColor="#000000" android:fillType="evenOdd"
android:pathData="M197.9,245.4C197.9,245.4 203.6,261.23 216.89,267.66C230.19,274.08 246.79,274 246.79,274C246.79,274 237.25,270.04 231.38,264.16C225.51,258.29 226.2,247.94 226.2,247.94ZM250.39,208.96C250.39,208.96 253.37,209.82 254.78,209.27C256.19,208.72 267.25,201.26 279.62,200.46C292.33,199.65 299.6,202.12 299.6,202.12C299.6,202.12 284.78,205.19 282.14,212.08C279.43,219.17 288.42,225.5 288.42,225.5L262.14,222.75Z"
android:strokeColor="#00000000" android:strokeLineCap="butt"
android:strokeLineJoin="miter" android:strokeWidth="0.1"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M74.85,66.88c-0.09,0.01 -3.17,-6.66 -7.3,-10.73 -3.37,-3.32 -9.5,-8.76 -19.42,-8.37 -4.43,0.17 -6.5,2.21 -6.2,2.71 0.29,0.49 6.05,-1.33 12.2,1.91 5.6,2.95 7.98,7.71 7.86,7.8 -0.1,0.07 -4.35,-3.73 -10.78,-5.41 -0.02,0 -0.05,0 -0.07,-0.01 -1.15,-0.28 -1.25,1.26 -0.06,2.73 1.37,1.71 2.02,1.86 1.95,2.08 -0.08,0.28 -2.72,0.38 -5.26,-1.4 -2.24,-1.57 -3.24,-3.69 -3.54,-3.96 -0.25,-0.23 -0.54,-0.17 -0.87,-0.17 -0.01,0 -0.03,0 -0.04,0 -3.57,0.14 -5.7,0.53 -8.44,1.62 -1.21,0.48 -2.87,0.58 -3.82,-0.4 -0.98,-1.02 1.68,-2.45 1.89,-3.26 0.28,-1.06 0.11,-5.22 7.76,-7.48 2.98,-0.88 8.91,-1.72 15.38,0.27 0.33,0.08 0.62,0.09 0.92,-0.12 0.8,-0.56 2.14,-1.32 4.38,-1.52 3.77,-0.34 4.98,0.82 4.88,1.04 -0.09,0.19 -3.45,0.18 -4.15,2.27 -0.26,0.78 0.29,1.22 0.69,1.49 1.41,0.92 2.73,1.95 3.95,3.1 2.86,2.68 4.85,5.55 6.47,9.45 1.29,3.11 1.72,6.36 1.61,6.38Z"
android:strokeWidth="0"
android:fillColor="#000"
android:fillType="evenOdd"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 7 KiB

Before After
Before After

View file

@ -69,7 +69,7 @@
<!-- General Preference Fragment -->
<string name="general_submenu">General</string>
<string name="dual_core">Dual Core</string>
<string name="dual_core">Dual Core (speedhack)</string>
<string name="dual_core_description">Split workload to two CPU cores instead of one. Increases speed.</string>
<string name="enable_cheats">Enable Cheats</string>
<string name="speed_limit">Speed Limit (0% = Unlimited)</string>
@ -874,7 +874,7 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="about_website"><a href="https://dolphin-emu.org/">Website</a></string>
<string name="about_github"><a href="https://github.com/dolphin-emu/dolphin">GitHub</a></string>
<string name="about_support"><a href="https://forums.dolphin-emu.org/">Support</a></string>
<string name="about_copyright_warning">\u00A9 20032015+ Dolphin Team. \u201cGameCube\u201d and \u201cWii\u201d are trademarks of Nintendo. Dolphin is not affiliated with Nintendo in any way.</string>
<string name="about_copyright_warning">\u00A9 20032024+ Dolphin Team. \u201cGameCube\u201d and \u201cWii\u201d are trademarks of Nintendo. Dolphin is not affiliated with Nintendo in any way.</string>
<string name="system_driver">System driver</string>
<string name="system_driver_desc">The GPU driver that is part of the OS.</string>
@ -914,6 +914,8 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="infinity_number">Figure Number</string>
<string name="invalid_infinity_figure">Invalid Figure Selection</string>
<string name="infinity_hexagon_label">Power Disc/Play Set</string>
<string name="infinity_power_hex_two_label">Power Disc Two</string>
<string name="infinity_power_hex_three_label">Power Disc Three</string>
<string name="infinity_p1_label">Player One</string>
<string name="infinity_p2_label">Player Two</string>
<string name="infinity_p1a1_label">P1 Ability One</string>

View file

@ -14,6 +14,5 @@ android.enableJetifier=true
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View file

@ -0,0 +1,21 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <jni.h>
#include "Common/Logging/Log.h"
#include "Core/AchievementManager.h"
extern "C" {
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_utils_ActivityTracker_setBackgroundExecutionAllowedNative(
JNIEnv*, jclass, jboolean allowed)
{
// This is called with allowed == false when the app goes into the background.
// We use this to stop continuously running background threads so we don't waste battery.
INFO_LOG_FMT(CORE, "SetBackgroundExecutionAllowed {}", allowed);
AchievementManager::GetInstance().SetBackgroundExecutionAllowed(allowed);
}
}

View file

@ -1,4 +1,5 @@
add_library(main SHARED
ActivityTracker.cpp
Cheats/ARCheat.cpp
Cheats/Cheats.h
Cheats/GeckoCheat.cpp
@ -11,6 +12,7 @@ add_library(main SHARED
GameList/GameFile.cpp
GameList/GameFile.h
GameList/GameFileCache.cpp
GpuDriver.cpp
Host.cpp
Host.h
InfinityConfig.cpp
@ -32,7 +34,6 @@ add_library(main SHARED
RiivolutionPatches.cpp
SkylanderConfig.cpp
WiiUtils.cpp
GpuDriver.cpp
)
target_link_libraries(main

View file

@ -53,7 +53,7 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GraphicsModGroup_getMods(JN
// If no group matches the mod's features, or if the mod has no features, skip it
if (std::none_of(mod.m_features.begin(), mod.m_features.end(),
[&groups](const GraphicsModFeatureConfig& feature) {
return groups.count(feature.m_group) == 1;
return groups.contains(feature.m_group);
}))
{
continue;

View file

@ -10,6 +10,8 @@
#include "Core/IOS/USB/Emulated/Infinity.h"
#include "Core/System.h"
using FigureUIPosition = IOS::HLE::USB::FigureUIPosition;
extern "C" {
JNIEXPORT jobject JNICALL
@ -66,7 +68,7 @@ Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_removeFigure
jint position)
{
auto& system = Core::System::GetInstance();
system.GetInfinityBase().RemoveFigure(position);
system.GetInfinityBase().RemoveFigure(static_cast<FigureUIPosition>(position));
}
JNIEXPORT jstring JNICALL
@ -87,9 +89,10 @@ Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_loadFigure(J
}
auto& system = Core::System::GetInstance();
system.GetInfinityBase().RemoveFigure(position);
system.GetInfinityBase().RemoveFigure(static_cast<FigureUIPosition>(position));
return ToJString(env,
system.GetInfinityBase().LoadFigure(file_data, std::move(inf_file), position));
system.GetInfinityBase().LoadFigure(file_data, std::move(inf_file),
static_cast<FigureUIPosition>(position)));
}
JNIEXPORT jstring JNICALL
@ -102,7 +105,7 @@ Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_createFigure
auto& system = Core::System::GetInstance();
system.GetInfinityBase().CreateFigure(file_name, fig_num);
system.GetInfinityBase().RemoveFigure(position);
system.GetInfinityBase().RemoveFigure(static_cast<FigureUIPosition>(position));
File::IOFile inf_file(file_name, "r+b");
if (!inf_file)
@ -116,6 +119,7 @@ Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_createFigure
}
return ToJString(env,
system.GetInfinityBase().LoadFigure(file_data, std::move(inf_file), position));
system.GetInfinityBase().LoadFigure(file_data, std::move(inf_file),
static_cast<FigureUIPosition>(position)));
}
}

View file

@ -173,6 +173,11 @@ bool Host_RendererIsFullscreen()
return false;
}
bool Host_TASInputHasFocus()
{
return false;
}
void Host_YieldToUI()
{
}
@ -279,7 +284,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunnin
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndStarted(JNIEnv*,
jclass)
{
return static_cast<jboolean>(Core::IsRunningAndStarted());
return static_cast<jboolean>(Core::IsRunning(Core::System::GetInstance()));
}
JNIEXPORT jboolean JNICALL
@ -412,11 +417,11 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteJitBloc
HostThreadLock guard;
auto& system = Core::System::GetInstance();
auto& jit_interface = system.GetJitInterface();
const Core::CPUThreadGuard cpu_guard(system);
if (jit_interface.GetCore() == nullptr)
{
env->CallStaticVoidMethod(native_library_class, IDCache::GetDisplayToastMsg(),
ToJString(env, Common::GetStringT("JIT is not active")),
static_cast<jboolean>(false));
ToJString(env, Common::GetStringT("JIT is not active")), JNI_FALSE);
return;
}
const std::string filename = fmt::format("{}{}.txt", File::GetUserPath(D_DUMPDEBUG_JITBLOCKS_IDX),
@ -427,13 +432,13 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteJitBloc
env->CallStaticVoidMethod(
native_library_class, IDCache::GetDisplayToastMsg(),
ToJString(env, Common::FmtFormatT("Failed to open \"{0}\" for writing.", filename)),
static_cast<jboolean>(false));
JNI_FALSE);
return;
}
jit_interface.JitBlockLogDump(Core::CPUThreadGuard{system}, f.GetHandle());
jit_interface.JitBlockLogDump(cpu_guard, f.GetHandle());
env->CallStaticVoidMethod(native_library_class, IDCache::GetDisplayToastMsg(),
ToJString(env, Common::FmtFormatT("Wrote to \"{0}\".", filename)),
static_cast<jboolean>(false));
JNI_FALSE);
}
// Surface Handling
@ -696,7 +701,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ConvertD
blob_reader = DiscIO::CreateBlobReader(in_path);
if (!blob_reader)
return static_cast<jboolean>(false);
return JNI_FALSE;
jobject jCallbackGlobal = env->NewGlobalRef(jCallback);
Common::ScopeGuard scope_guard([jCallbackGlobal, env] { env->DeleteGlobalRef(jCallbackGlobal); });

View file

@ -20,7 +20,7 @@ AlsaSound::~AlsaSound()
m_thread_status.store(ALSAThreadStatus::STOPPING);
// Immediately lock and unlock mutex to prevent cv race.
std::unique_lock<std::mutex>{cv_m};
std::unique_lock<std::mutex>{cv_m}.unlock();
// Give the opportunity to the audio thread
// to realize we are stopping the emulation
@ -82,7 +82,7 @@ bool AlsaSound::SetRunning(bool running)
m_thread_status.store(running ? ALSAThreadStatus::RUNNING : ALSAThreadStatus::PAUSED);
// Immediately lock and unlock mutex to prevent cv race.
std::unique_lock<std::mutex>{cv_m};
std::unique_lock<std::mutex>{cv_m}.unlock();
// Notify thread that status has changed
cv.notify_one();

View file

@ -137,6 +137,12 @@ OpenSLESStream::~OpenSLESStream()
}
}
bool OpenSLESStream::SetRunning(bool running)
{
SLuint32 new_state = running ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_PAUSED;
return (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, new_state) == SL_RESULT_SUCCESS;
}
void OpenSLESStream::SetVolume(int volume)
{
const SLmillibel attenuation =

View file

@ -14,7 +14,7 @@ class OpenSLESStream final : public SoundStream
public:
~OpenSLESStream() override;
bool Init() override;
bool SetRunning(bool running) override { return true; }
bool SetRunning(bool running) override;
void SetVolume(int volume) override;
static bool IsValid() { return true; }

View file

@ -2,12 +2,12 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "AudioCommon/WaveFile.h"
#include "AudioCommon/Mixer.h"
#include <string>
#include <fmt/format.h>
#include "AudioCommon/Mixer.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"

View file

@ -30,3 +30,59 @@ endif()
if (WIN32 AND ENABLE_AUTOUPDATE)
add_subdirectory(WinUpdater)
endif()
if (APPLE AND ENABLE_QT)
set(DOLPHIN_MAC_BUNDLE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Dolphin.app")
add_custom_target(build_final_bundle ALL
COMMAND ${CMAKE_COMMAND} -E remove_directory
${DOLPHIN_MAC_BUNDLE}
COMMAND cp -R
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DolphinQt.app
${DOLPHIN_MAC_BUNDLE}
# HACK: The Updater does not support setting the executable bit on new files,
# so don't use the new executable name, and instead continue to use "Dolphin".
COMMAND ${CMAKE_COMMAND} -E rename
${DOLPHIN_MAC_BUNDLE}/Contents/MacOS/DolphinQt
${DOLPHIN_MAC_BUNDLE}/Contents/MacOS/Dolphin
COMMAND plutil
-replace CFBundleExecutable -string Dolphin
${DOLPHIN_MAC_BUNDLE}/Contents/Info.plist
DEPENDS dolphin-emu)
if (ENABLE_AUTOUPDATE)
add_dependencies(build_final_bundle MacUpdater)
add_custom_command(TARGET build_final_bundle
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
"${DOLPHIN_MAC_BUNDLE}/Contents/Helpers"
COMMAND cp -R
"$<TARGET_BUNDLE_DIR:MacUpdater>"
"${DOLPHIN_MAC_BUNDLE}/Contents/Helpers/Dolphin Updater.app")
if (MACOS_CODE_SIGNING)
add_custom_command(TARGET build_final_bundle
POST_BUILD
COMMAND "${CMAKE_SOURCE_DIR}/Tools/mac-codesign.sh"
"-t"
"${MACOS_CODE_SIGNING_IDENTITY}"
"${DOLPHIN_MAC_BUNDLE}/Contents/Helpers/Dolphin Updater.app")
endif()
endif()
if (MACOS_CODE_SIGNING)
add_custom_command(TARGET build_final_bundle
POST_BUILD
COMMAND "${CMAKE_SOURCE_DIR}/Tools/mac-codesign.sh"
"-t"
"-e" "${CMAKE_SOURCE_DIR}/Source/Core/DolphinQt/DolphinEmu$<$<CONFIG:Debug>:Debug>.entitlements"
"${MACOS_CODE_SIGNING_IDENTITY}"
"${DOLPHIN_MAC_BUNDLE}")
endif()
endif()

View file

@ -87,26 +87,6 @@ void ARM64XEmitter::SetCodePtr(u8* ptr, u8* end, bool write_failed)
m_lastCacheFlushEnd = ptr;
}
const u8* ARM64XEmitter::GetCodePtr() const
{
return m_code;
}
u8* ARM64XEmitter::GetWritableCodePtr()
{
return m_code;
}
const u8* ARM64XEmitter::GetCodeEnd() const
{
return m_code_end;
}
u8* ARM64XEmitter::GetWritableCodeEnd()
{
return m_code_end;
}
void ARM64XEmitter::ReserveCodeSpace(u32 bytes)
{
for (u32 i = 0; i < bytes / 4; i++)

View file

@ -680,10 +680,10 @@ public:
void SetCodePtr(u8* ptr, u8* end, bool write_failed = false);
void SetCodePtrUnsafe(u8* ptr, u8* end, bool write_failed = false);
const u8* GetCodePtr() const;
u8* GetWritableCodePtr();
const u8* GetCodeEnd() const;
u8* GetWritableCodeEnd();
const u8* GetCodePtr() const { return m_code; }
u8* GetWritableCodePtr() { return m_code; }
const u8* GetCodeEnd() const { return m_code_end; }
u8* GetWritableCodeEnd() { return m_code_end; }
void ReserveCodeSpace(u32 bytes);
u8* AlignCode16();
u8* AlignCodePage();

View file

@ -15,11 +15,16 @@
#include <Windows.h>
#include <arm64intr.h>
#include "Common/WindowsRegistry.h"
#else
#ifndef __FreeBSD__
#elif defined(__linux__)
#include <asm/hwcap.h>
#endif
#include <sys/auxv.h>
#elif defined(__FreeBSD__)
#include <sys/auxv.h>
#elif defined(__OpenBSD__)
#include <machine/armreg.h>
#include <machine/cpu.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#endif
#include <fmt/format.h>
@ -183,7 +188,7 @@ static bool Read_MIDR_EL1(u64* value)
#endif
#ifndef __APPLE__
#if defined(_WIN32) || defined(__linux__) || defined(__FreeBSD__)
static std::string MIDRToString(u64 midr)
{
@ -248,7 +253,7 @@ void CPUInfo::Detect()
{
cpu_id = MIDRToString(reg);
}
#else
#elif defined(__linux__) || defined(__FreeBSD__)
// Linux, Android, and FreeBSD
#if defined(__FreeBSD__)
@ -277,6 +282,33 @@ void CPUInfo::Detect()
{
cpu_id = MIDRToString(midr);
}
#elif defined(__OpenBSD__)
// OpenBSD
int mib[2];
size_t len;
char hwmodel[256];
uint64_t isar0;
mib[0] = CTL_HW;
mib[1] = HW_MODEL;
len = std::size(hwmodel);
if (sysctl(mib, 2, &hwmodel, &len, nullptr, 0) != -1)
model_name = std::string(hwmodel, len - 1);
mib[0] = CTL_MACHDEP;
mib[1] = CPU_ID_AA64ISAR0;
len = sizeof(isar0);
if (sysctl(mib, 2, &isar0, &len, nullptr, 0) != -1)
{
if (ID_AA64ISAR0_AES(isar0) >= ID_AA64ISAR0_AES_BASE)
bAES = true;
if (ID_AA64ISAR0_SHA1(isar0) >= ID_AA64ISAR0_SHA1_BASE)
bSHA1 = true;
if (ID_AA64ISAR0_SHA2(isar0) >= ID_AA64ISAR0_SHA2_BASE)
bSHA2 = true;
if (ID_AA64ISAR0_CRC32(isar0) >= ID_AA64ISAR0_CRC32_BASE)
bCRC32 = true;
}
#endif
model_name = ReplaceAll(model_name, ",", "_");

View file

@ -42,40 +42,40 @@ constexpr ExtendedMnemonicDesc INVALID_EXT_MNEMONIC = {0, nullptr};
// All operands as referenced by the Gekko/Broadway user manual
// See section 12.1.2 under Chapter 12
constexpr OperandDesc _A = OperandDesc{Mask(11, 15), {16, false}};
constexpr OperandDesc _B = OperandDesc{Mask(16, 20), {11, false}};
constexpr OperandDesc _BD = OperandDesc{Mask(16, 29), {0, true}};
constexpr OperandDesc _BI = OperandDesc{Mask(11, 15), {16, false}};
constexpr OperandDesc _BO = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc _C = OperandDesc{Mask(21, 25), {6, false}};
constexpr OperandDesc _Crba = OperandDesc{Mask(11, 15), {16, false}};
constexpr OperandDesc _Crbb = OperandDesc{Mask(16, 20), {11, false}};
constexpr OperandDesc _Crbd = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc _Crfd = OperandDesc{Mask(6, 8), {23, false}};
constexpr OperandDesc _Crfs = OperandDesc{Mask(11, 13), {18, false}};
constexpr OperandDesc _CRM = OperandDesc{Mask(12, 19), {12, false}};
constexpr OperandDesc _D = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc _FM = OperandDesc{Mask(7, 14), {17, false}};
constexpr OperandDesc _W1 = OperandDesc{Mask(16, 16), {15, false}};
constexpr OperandDesc _W2 = OperandDesc{Mask(21, 21), {10, false}};
constexpr OperandDesc _IMM = OperandDesc{Mask(16, 19), {12, false}};
constexpr OperandDesc _L = OperandDesc{Mask(10, 10), {21, false}};
constexpr OperandDesc _LI = OperandDesc{Mask(6, 29), {0, true}};
constexpr OperandDesc _MB = OperandDesc{Mask(21, 25), {6, false}};
constexpr OperandDesc _ME = OperandDesc{Mask(26, 30), {1, false}};
constexpr OperandDesc _NB = OperandDesc{Mask(16, 20), {11, false}};
constexpr OperandDesc _Offd = OperandDesc{Mask(16, 31), {0, true}};
constexpr OperandDesc _OffdPs = OperandDesc{Mask(20, 31), {0, true}};
constexpr OperandDesc _S = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc _SH = OperandDesc{Mask(16, 20), {11, false}};
constexpr OperandDesc _SIMM = OperandDesc{Mask(16, 31), {0, true}};
constexpr OperandDesc _SPR = OperandDesc{Mask(11, 20), {11, false}};
constexpr OperandDesc _SR = OperandDesc{Mask(12, 15), {16, false}};
constexpr OperandDesc _TO = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc _TPR = OperandDesc{Mask(11, 20), {11, false}};
constexpr OperandDesc _UIMM = OperandDesc{Mask(16, 31), {0, false}};
constexpr OperandDesc _I1 = OperandDesc{Mask(17, 19), {12, false}};
constexpr OperandDesc _I2 = OperandDesc{Mask(22, 24), {7, false}};
constexpr OperandDesc OpDesc_A = OperandDesc{Mask(11, 15), {16, false}};
constexpr OperandDesc OpDesc_B = OperandDesc{Mask(16, 20), {11, false}};
constexpr OperandDesc OpDesc_BD = OperandDesc{Mask(16, 29), {0, true}};
constexpr OperandDesc OpDesc_BI = OperandDesc{Mask(11, 15), {16, false}};
constexpr OperandDesc OpDesc_BO = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc OpDesc_C = OperandDesc{Mask(21, 25), {6, false}};
constexpr OperandDesc OpDesc_Crba = OperandDesc{Mask(11, 15), {16, false}};
constexpr OperandDesc OpDesc_Crbb = OperandDesc{Mask(16, 20), {11, false}};
constexpr OperandDesc OpDesc_Crbd = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc OpDesc_Crfd = OperandDesc{Mask(6, 8), {23, false}};
constexpr OperandDesc OpDesc_Crfs = OperandDesc{Mask(11, 13), {18, false}};
constexpr OperandDesc OpDesc_CRM = OperandDesc{Mask(12, 19), {12, false}};
constexpr OperandDesc OpDesc_D = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc OpDesc_FM = OperandDesc{Mask(7, 14), {17, false}};
constexpr OperandDesc OpDesc_W1 = OperandDesc{Mask(16, 16), {15, false}};
constexpr OperandDesc OpDesc_W2 = OperandDesc{Mask(21, 21), {10, false}};
constexpr OperandDesc OpDesc_IMM = OperandDesc{Mask(16, 19), {12, false}};
constexpr OperandDesc OpDesc_L = OperandDesc{Mask(10, 10), {21, false}};
constexpr OperandDesc OpDesc_LI = OperandDesc{Mask(6, 29), {0, true}};
constexpr OperandDesc OpDesc_MB = OperandDesc{Mask(21, 25), {6, false}};
constexpr OperandDesc OpDesc_ME = OperandDesc{Mask(26, 30), {1, false}};
constexpr OperandDesc OpDesc_NB = OperandDesc{Mask(16, 20), {11, false}};
constexpr OperandDesc OpDesc_Offd = OperandDesc{Mask(16, 31), {0, true}};
constexpr OperandDesc OpDesc_OffdPs = OperandDesc{Mask(20, 31), {0, true}};
constexpr OperandDesc OpDesc_S = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc OpDesc_SH = OperandDesc{Mask(16, 20), {11, false}};
constexpr OperandDesc OpDesc_SIMM = OperandDesc{Mask(16, 31), {0, true}};
constexpr OperandDesc OpDesc_SPR = OperandDesc{Mask(11, 20), {11, false}};
constexpr OperandDesc OpDesc_SR = OperandDesc{Mask(12, 15), {16, false}};
constexpr OperandDesc OpDesc_TO = OperandDesc{Mask(6, 10), {21, false}};
constexpr OperandDesc OpDesc_TPR = OperandDesc{Mask(11, 20), {11, false}};
constexpr OperandDesc OpDesc_UIMM = OperandDesc{Mask(16, 31), {0, false}};
constexpr OperandDesc OpDesc_I1 = OperandDesc{Mask(17, 19), {12, false}};
constexpr OperandDesc OpDesc_I2 = OperandDesc{Mask(22, 24), {7, false}};
} // namespace
void OperandList::Insert(size_t before, u32 val)
@ -675,288 +675,293 @@ extern const CaseInsensitiveDict<ParseInfo, '.', '_', '+', '-'> extended_mnemoni
// Defines all basic mnemonics that Broadway/Gekko supports
extern const std::array<MnemonicDesc, NUM_MNEMONICS* VARIANT_PERMUTATIONS> mnemonics = {
// A-2
OERC_MNEMONIC(31, InsertVal(266, 22, 30), _D, _A, _B), // add
OERC_MNEMONIC(31, InsertVal(10, 22, 30), _D, _A, _B), // addc
OERC_MNEMONIC(31, InsertVal(138, 22, 30), _D, _A, _B), // adde
BASIC_MNEMONIC(14, _D, _A, _SIMM), // addi
BASIC_MNEMONIC(12, _D, _A, _SIMM), // addic
BASIC_MNEMONIC(13, _D, _A, _SIMM), // addic.
BASIC_MNEMONIC(15, _D, _A, _SIMM), // addis
OERC_MNEMONIC(31, InsertVal(234, 22, 30), _D, _A), // addme
OERC_MNEMONIC(31, InsertVal(202, 22, 30), _D, _A), // addze
OERC_MNEMONIC(31, InsertVal(491, 22, 30), _D, _A, _B), // divw
OERC_MNEMONIC(31, InsertVal(459, 22, 30), _D, _A, _B), // divwu
RC_MNEMONIC(31, InsertVal(75, 22, 30), _D, _A, _B), // mulhw
RC_MNEMONIC(31, InsertVal(11, 22, 30), _D, _A, _B), // mulhwu
BASIC_MNEMONIC(7, _D, _A, _SIMM), // mulli
OERC_MNEMONIC(31, InsertVal(235, 22, 30), _D, _A, _B), // mullw
OERC_MNEMONIC(31, InsertVal(104, 22, 30), _D, _A), // neg
OERC_MNEMONIC(31, InsertVal(40, 22, 30), _D, _A, _B), // subf
OERC_MNEMONIC(31, InsertVal(8, 22, 30), _D, _A, _B), // subfc
OERC_MNEMONIC(31, InsertVal(136, 22, 30), _D, _A, _B), // subfe
BASIC_MNEMONIC(8, _D, _A, _SIMM), // subfic
OERC_MNEMONIC(31, InsertVal(232, 22, 30), _D, _A), // subfme
OERC_MNEMONIC(31, InsertVal(200, 22, 30), _D, _A), // subfze
OERC_MNEMONIC(31, InsertVal(266, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // add
OERC_MNEMONIC(31, InsertVal(10, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // addc
OERC_MNEMONIC(31, InsertVal(138, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // adde
BASIC_MNEMONIC(14, OpDesc_D, OpDesc_A, OpDesc_SIMM), // addi
BASIC_MNEMONIC(12, OpDesc_D, OpDesc_A, OpDesc_SIMM), // addic
BASIC_MNEMONIC(13, OpDesc_D, OpDesc_A, OpDesc_SIMM), // addic.
BASIC_MNEMONIC(15, OpDesc_D, OpDesc_A, OpDesc_SIMM), // addis
OERC_MNEMONIC(31, InsertVal(234, 22, 30), OpDesc_D, OpDesc_A), // addme
OERC_MNEMONIC(31, InsertVal(202, 22, 30), OpDesc_D, OpDesc_A), // addze
OERC_MNEMONIC(31, InsertVal(491, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // divw
OERC_MNEMONIC(31, InsertVal(459, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // divwu
RC_MNEMONIC(31, InsertVal(75, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // mulhw
RC_MNEMONIC(31, InsertVal(11, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // mulhwu
BASIC_MNEMONIC(7, OpDesc_D, OpDesc_A, OpDesc_SIMM), // mulli
OERC_MNEMONIC(31, InsertVal(235, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // mullw
OERC_MNEMONIC(31, InsertVal(104, 22, 30), OpDesc_D, OpDesc_A), // neg
OERC_MNEMONIC(31, InsertVal(40, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // subf
OERC_MNEMONIC(31, InsertVal(8, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // subfc
OERC_MNEMONIC(31, InsertVal(136, 22, 30), OpDesc_D, OpDesc_A, OpDesc_B), // subfe
BASIC_MNEMONIC(8, OpDesc_D, OpDesc_A, OpDesc_SIMM), // subfic
OERC_MNEMONIC(31, InsertVal(232, 22, 30), OpDesc_D, OpDesc_A), // subfme
OERC_MNEMONIC(31, InsertVal(200, 22, 30), OpDesc_D, OpDesc_A), // subfze
// A-3
MNEMONIC(31, InsertVal(0, 21, 30), _Crfd, _L, _A, _B), // cmp
BASIC_MNEMONIC(11, _Crfd, _L, _A, _SIMM), // cmpi
MNEMONIC(31, InsertVal(32, 21, 30), _Crfd, _L, _A, _B), // cmpl
BASIC_MNEMONIC(10, _Crfd, _L, _A, _UIMM), // cmpli
MNEMONIC(31, InsertVal(0, 21, 30), OpDesc_Crfd, OpDesc_L, OpDesc_A, OpDesc_B), // cmp
BASIC_MNEMONIC(11, OpDesc_Crfd, OpDesc_L, OpDesc_A, OpDesc_SIMM), // cmpi
MNEMONIC(31, InsertVal(32, 21, 30), OpDesc_Crfd, OpDesc_L, OpDesc_A, OpDesc_B), // cmpl
BASIC_MNEMONIC(10, OpDesc_Crfd, OpDesc_L, OpDesc_A, OpDesc_UIMM), // cmpli
// A-4
RC_MNEMONIC(31, InsertVal(28, 21, 30), _A, _S, _B), // and
RC_MNEMONIC(31, InsertVal(60, 21, 30), _A, _S, _B), // andc
BASIC_MNEMONIC(28, _A, _S, _UIMM), // andi.
BASIC_MNEMONIC(29, _A, _S, _UIMM), // andis.
RC_MNEMONIC(31, InsertVal(26, 21, 30), _A, _S), // cntlzw
RC_MNEMONIC(31, InsertVal(284, 21, 30), _A, _S, _B), // eqv
RC_MNEMONIC(31, InsertVal(954, 21, 30), _A, _S), // extsb
RC_MNEMONIC(31, InsertVal(922, 21, 30), _A, _S), // extsh
RC_MNEMONIC(31, InsertVal(476, 21, 30), _A, _S, _B), // nand
RC_MNEMONIC(31, InsertVal(124, 21, 30), _A, _S, _B), // nor
RC_MNEMONIC(31, InsertVal(444, 21, 30), _A, _S, _B), // or
RC_MNEMONIC(31, InsertVal(412, 21, 30), _A, _S, _B), // orc
BASIC_MNEMONIC(24, _A, _S, _UIMM), // ori
BASIC_MNEMONIC(25, _A, _S, _UIMM), // oris
RC_MNEMONIC(31, InsertVal(316, 21, 30), _A, _S, _B), // xor
BASIC_MNEMONIC(26, _A, _S, _UIMM), // xori
BASIC_MNEMONIC(27, _A, _S, _UIMM), // xoris
RC_MNEMONIC(31, InsertVal(28, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // and
RC_MNEMONIC(31, InsertVal(60, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // andc
BASIC_MNEMONIC(28, OpDesc_A, OpDesc_S, OpDesc_UIMM), // andi.
BASIC_MNEMONIC(29, OpDesc_A, OpDesc_S, OpDesc_UIMM), // andis.
RC_MNEMONIC(31, InsertVal(26, 21, 30), OpDesc_A, OpDesc_S), // cntlzw
RC_MNEMONIC(31, InsertVal(284, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // eqv
RC_MNEMONIC(31, InsertVal(954, 21, 30), OpDesc_A, OpDesc_S), // extsb
RC_MNEMONIC(31, InsertVal(922, 21, 30), OpDesc_A, OpDesc_S), // extsh
RC_MNEMONIC(31, InsertVal(476, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // nand
RC_MNEMONIC(31, InsertVal(124, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // nor
RC_MNEMONIC(31, InsertVal(444, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // or
RC_MNEMONIC(31, InsertVal(412, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // orc
BASIC_MNEMONIC(24, OpDesc_A, OpDesc_S, OpDesc_UIMM), // ori
BASIC_MNEMONIC(25, OpDesc_A, OpDesc_S, OpDesc_UIMM), // oris
RC_MNEMONIC(31, InsertVal(316, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // xor
BASIC_MNEMONIC(26, OpDesc_A, OpDesc_S, OpDesc_UIMM), // xori
BASIC_MNEMONIC(27, OpDesc_A, OpDesc_S, OpDesc_UIMM), // xoris
// A-5
RC_MNEMONIC(20, 0, _A, _S, _SH, _MB, _ME), // rlwimi
RC_MNEMONIC(21, 0, _A, _S, _SH, _MB, _ME), // rlwinm
RC_MNEMONIC(23, 0, _A, _S, _B, _MB, _ME), // rlwnm
RC_MNEMONIC(20, 0, OpDesc_A, OpDesc_S, OpDesc_SH, OpDesc_MB, OpDesc_ME), // rlwimi
RC_MNEMONIC(21, 0, OpDesc_A, OpDesc_S, OpDesc_SH, OpDesc_MB, OpDesc_ME), // rlwinm
RC_MNEMONIC(23, 0, OpDesc_A, OpDesc_S, OpDesc_B, OpDesc_MB, OpDesc_ME), // rlwnm
// A-6
RC_MNEMONIC(31, InsertVal(24, 21, 30), _A, _S, _B), // slw
RC_MNEMONIC(31, InsertVal(792, 21, 30), _A, _S, _B), // sraw
RC_MNEMONIC(31, InsertVal(824, 21, 30), _A, _S, _SH), // srawi
RC_MNEMONIC(31, InsertVal(536, 21, 30), _A, _S, _B), // srw
RC_MNEMONIC(31, InsertVal(24, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // slw
RC_MNEMONIC(31, InsertVal(792, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // sraw
RC_MNEMONIC(31, InsertVal(824, 21, 30), OpDesc_A, OpDesc_S, OpDesc_SH), // srawi
RC_MNEMONIC(31, InsertVal(536, 21, 30), OpDesc_A, OpDesc_S, OpDesc_B), // srw
// A-7
RC_MNEMONIC(63, InsertVal(21, 26, 30), _D, _A, _B), // fadd
RC_MNEMONIC(59, InsertVal(21, 26, 30), _D, _A, _B), // fadds
RC_MNEMONIC(63, InsertVal(18, 26, 30), _D, _A, _B), // fdiv
RC_MNEMONIC(59, InsertVal(18, 26, 30), _D, _A, _B), // fdivs
RC_MNEMONIC(63, InsertVal(25, 26, 30), _D, _A, _C), // fmul
RC_MNEMONIC(59, InsertVal(25, 26, 30), _D, _A, _C), // fmuls
RC_MNEMONIC(59, InsertVal(24, 26, 30), _D, _B), // fres
RC_MNEMONIC(63, InsertVal(26, 26, 30), _D, _B), // frsqrte
RC_MNEMONIC(63, InsertVal(20, 26, 30), _D, _A, _B), // fsub
RC_MNEMONIC(59, InsertVal(20, 26, 30), _D, _A, _B), // fsubs
RC_MNEMONIC(63, InsertVal(23, 26, 30), _D, _A, _C, _B), // fsel
RC_MNEMONIC(63, InsertVal(21, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // fadd
RC_MNEMONIC(59, InsertVal(21, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // fadds
RC_MNEMONIC(63, InsertVal(18, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // fdiv
RC_MNEMONIC(59, InsertVal(18, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // fdivs
RC_MNEMONIC(63, InsertVal(25, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C), // fmul
RC_MNEMONIC(59, InsertVal(25, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C), // fmuls
RC_MNEMONIC(59, InsertVal(24, 26, 30), OpDesc_D, OpDesc_B), // fres
RC_MNEMONIC(63, InsertVal(26, 26, 30), OpDesc_D, OpDesc_B), // frsqrte
RC_MNEMONIC(63, InsertVal(20, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // fsub
RC_MNEMONIC(59, InsertVal(20, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // fsubs
RC_MNEMONIC(63, InsertVal(23, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fsel
// A-8
RC_MNEMONIC(63, InsertVal(29, 26, 30), _D, _A, _C, _B), // fmadd
RC_MNEMONIC(59, InsertVal(29, 26, 30), _D, _A, _C, _B), // fmadds
RC_MNEMONIC(63, InsertVal(28, 26, 30), _D, _A, _C, _B), // fmsub
RC_MNEMONIC(59, InsertVal(28, 26, 30), _D, _A, _C, _B), // fmsubs
RC_MNEMONIC(63, InsertVal(31, 26, 30), _D, _A, _C, _B), // fnmadd
RC_MNEMONIC(59, InsertVal(31, 26, 30), _D, _A, _C, _B), // fnmadds
RC_MNEMONIC(63, InsertVal(30, 26, 30), _D, _A, _C, _B), // fnmsub
RC_MNEMONIC(59, InsertVal(30, 26, 30), _D, _A, _C, _B), // fnmsubs
RC_MNEMONIC(63, InsertVal(29, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fmadd
RC_MNEMONIC(59, InsertVal(29, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fmadds
RC_MNEMONIC(63, InsertVal(28, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fmsub
RC_MNEMONIC(59, InsertVal(28, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fmsubs
RC_MNEMONIC(63, InsertVal(31, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fnmadd
RC_MNEMONIC(59, InsertVal(31, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fnmadds
RC_MNEMONIC(63, InsertVal(30, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fnmsub
RC_MNEMONIC(59, InsertVal(30, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // fnmsubs
// A-9
RC_MNEMONIC(63, InsertVal(14, 21, 30), _D, _B), // fctiw
RC_MNEMONIC(63, InsertVal(15, 21, 30), _D, _B), // fctiwz
RC_MNEMONIC(63, InsertVal(12, 21, 30), _D, _B), // frsp
RC_MNEMONIC(63, InsertVal(14, 21, 30), OpDesc_D, OpDesc_B), // fctiw
RC_MNEMONIC(63, InsertVal(15, 21, 30), OpDesc_D, OpDesc_B), // fctiwz
RC_MNEMONIC(63, InsertVal(12, 21, 30), OpDesc_D, OpDesc_B), // frsp
// A-10
MNEMONIC(63, InsertVal(32, 21, 30), _Crfd, _A, _B), // fcmpo
MNEMONIC(63, InsertVal(0, 21, 30), _Crfd, _A, _B), // fcmpu
MNEMONIC(63, InsertVal(32, 21, 30), OpDesc_Crfd, OpDesc_A, OpDesc_B), // fcmpo
MNEMONIC(63, InsertVal(0, 21, 30), OpDesc_Crfd, OpDesc_A, OpDesc_B), // fcmpu
// A-11
MNEMONIC(63, InsertVal(64, 21, 30), _Crfd, _Crfs), // mcrfs
RC_MNEMONIC(63, InsertVal(583, 21, 30), _D), // mffs
RC_MNEMONIC(63, InsertVal(70, 21, 30), _Crbd), // mtfsb0
RC_MNEMONIC(63, InsertVal(38, 21, 30), _Crbd), // mtfsb1
RC_MNEMONIC(63, InsertVal(711, 21, 30), _FM, _B), // mtfsf
RC_MNEMONIC(63, InsertVal(134, 21, 30), _Crfd, _IMM), // mtfsfi
MNEMONIC(63, InsertVal(64, 21, 30), OpDesc_Crfd, OpDesc_Crfs), // mcrfs
RC_MNEMONIC(63, InsertVal(583, 21, 30), OpDesc_D), // mffs
RC_MNEMONIC(63, InsertVal(70, 21, 30), OpDesc_Crbd), // mtfsb0
RC_MNEMONIC(63, InsertVal(38, 21, 30), OpDesc_Crbd), // mtfsb1
RC_MNEMONIC(63, InsertVal(711, 21, 30), OpDesc_FM, OpDesc_B), // mtfsf
RC_MNEMONIC(63, InsertVal(134, 21, 30), OpDesc_Crfd, OpDesc_IMM), // mtfsfi
// A-12
BASIC_MNEMONIC(34, _D, _Offd, _A), // lbz
BASIC_MNEMONIC(35, _D, _Offd, _A), // lbzu
MNEMONIC(31, InsertVal(119, 21, 30), _D, _A, _B), // lbzux
MNEMONIC(31, InsertVal(87, 21, 30), _D, _A, _B), // lbzx
BASIC_MNEMONIC(42, _D, _Offd, _A), // lha
BASIC_MNEMONIC(43, _D, _Offd, _A), // lhau
MNEMONIC(31, InsertVal(375, 21, 30), _D, _A, _B), // lhaux
MNEMONIC(31, InsertVal(343, 21, 30), _D, _A, _B), // lhax
BASIC_MNEMONIC(40, _D, _Offd, _A), // lhz
BASIC_MNEMONIC(41, _D, _Offd, _A), // lhzu
MNEMONIC(31, InsertVal(311, 21, 30), _D, _A, _B), // lhzux
MNEMONIC(31, InsertVal(279, 21, 30), _D, _A, _B), // lhzx
BASIC_MNEMONIC(32, _D, _Offd, _A), // lwz
BASIC_MNEMONIC(33, _D, _Offd, _A), // lwzu
MNEMONIC(31, InsertVal(55, 21, 30), _D, _A, _B), // lwzux
MNEMONIC(31, InsertVal(23, 21, 30), _D, _A, _B), // lwzx
BASIC_MNEMONIC(34, OpDesc_D, OpDesc_Offd, OpDesc_A), // lbz
BASIC_MNEMONIC(35, OpDesc_D, OpDesc_Offd, OpDesc_A), // lbzu
MNEMONIC(31, InsertVal(119, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lbzux
MNEMONIC(31, InsertVal(87, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lbzx
BASIC_MNEMONIC(42, OpDesc_D, OpDesc_Offd, OpDesc_A), // lha
BASIC_MNEMONIC(43, OpDesc_D, OpDesc_Offd, OpDesc_A), // lhau
MNEMONIC(31, InsertVal(375, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lhaux
MNEMONIC(31, InsertVal(343, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lhax
BASIC_MNEMONIC(40, OpDesc_D, OpDesc_Offd, OpDesc_A), // lhz
BASIC_MNEMONIC(41, OpDesc_D, OpDesc_Offd, OpDesc_A), // lhzu
MNEMONIC(31, InsertVal(311, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lhzux
MNEMONIC(31, InsertVal(279, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lhzx
BASIC_MNEMONIC(32, OpDesc_D, OpDesc_Offd, OpDesc_A), // lwz
BASIC_MNEMONIC(33, OpDesc_D, OpDesc_Offd, OpDesc_A), // lwzu
MNEMONIC(31, InsertVal(55, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lwzux
MNEMONIC(31, InsertVal(23, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lwzx
// A-13
BASIC_MNEMONIC(38, _S, _Offd, _A), // stb
BASIC_MNEMONIC(39, _S, _Offd, _A), // stbu
MNEMONIC(31, InsertVal(247, 21, 30), _S, _A, _B), // stbux
MNEMONIC(31, InsertVal(215, 21, 30), _S, _A, _B), // stbx
BASIC_MNEMONIC(44, _S, _Offd, _A), // sth
BASIC_MNEMONIC(45, _S, _Offd, _A), // sthu
MNEMONIC(31, InsertVal(439, 21, 30), _S, _A, _B), // sthux
MNEMONIC(31, InsertVal(407, 21, 30), _S, _A, _B), // sthx
BASIC_MNEMONIC(36, _S, _Offd, _A), // stw
BASIC_MNEMONIC(37, _S, _Offd, _A), // stwu
MNEMONIC(31, InsertVal(183, 21, 30), _S, _A, _B), // stwux
MNEMONIC(31, InsertVal(151, 21, 30), _S, _A, _B), // stwx
BASIC_MNEMONIC(38, OpDesc_S, OpDesc_Offd, OpDesc_A), // stb
BASIC_MNEMONIC(39, OpDesc_S, OpDesc_Offd, OpDesc_A), // stbu
MNEMONIC(31, InsertVal(247, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stbux
MNEMONIC(31, InsertVal(215, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stbx
BASIC_MNEMONIC(44, OpDesc_S, OpDesc_Offd, OpDesc_A), // sth
BASIC_MNEMONIC(45, OpDesc_S, OpDesc_Offd, OpDesc_A), // sthu
MNEMONIC(31, InsertVal(439, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // sthux
MNEMONIC(31, InsertVal(407, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // sthx
BASIC_MNEMONIC(36, OpDesc_S, OpDesc_Offd, OpDesc_A), // stw
BASIC_MNEMONIC(37, OpDesc_S, OpDesc_Offd, OpDesc_A), // stwu
MNEMONIC(31, InsertVal(183, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stwux
MNEMONIC(31, InsertVal(151, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stwx
// A-14
MNEMONIC(31, InsertVal(790, 21, 30), _D, _A, _B), // lhbrx
MNEMONIC(31, InsertVal(534, 21, 30), _D, _A, _B), // lwbrx
MNEMONIC(31, InsertVal(918, 21, 30), _S, _A, _B), // sthbrx
MNEMONIC(31, InsertVal(662, 21, 30), _S, _A, _B), // stwbrx
MNEMONIC(31, InsertVal(790, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lhbrx
MNEMONIC(31, InsertVal(534, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lwbrx
MNEMONIC(31, InsertVal(918, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // sthbrx
MNEMONIC(31, InsertVal(662, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stwbrx
// A-15
BASIC_MNEMONIC(46, _D, _Offd, _A), // lmw
BASIC_MNEMONIC(47, _S, _Offd, _A), // stmw
BASIC_MNEMONIC(46, OpDesc_D, OpDesc_Offd, OpDesc_A), // lmw
BASIC_MNEMONIC(47, OpDesc_S, OpDesc_Offd, OpDesc_A), // stmw
// A-16
MNEMONIC(31, InsertVal(597, 21, 30), _D, _A, _NB), // lswi
MNEMONIC(31, InsertVal(533, 21, 30), _D, _A, _B), // lswx
MNEMONIC(31, InsertVal(725, 21, 30), _S, _A, _NB), // stswi
MNEMONIC(31, InsertVal(661, 21, 30), _S, _A, _B), // stswx
MNEMONIC(31, InsertVal(597, 21, 30), OpDesc_D, OpDesc_A, OpDesc_NB), // lswi
MNEMONIC(31, InsertVal(533, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lswx
MNEMONIC(31, InsertVal(725, 21, 30), OpDesc_S, OpDesc_A, OpDesc_NB), // stswi
MNEMONIC(31, InsertVal(661, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stswx
// A-17
MNEMONIC(31, InsertVal(854, 21, 30)), // eieio
MNEMONIC(19, InsertVal(150, 21, 30)), // isync
MNEMONIC(31, InsertVal(20, 21, 30), _D, _A, _B), // lwarx
MNEMONIC(31, InsertVal(150, 21, 30) | InsertVal(1, 31, 31), _S, _A, _B), // stwcx.
MNEMONIC(31, InsertVal(598, 21, 30)), // sync
MNEMONIC(31, InsertVal(854, 21, 30)), // eieio
MNEMONIC(19, InsertVal(150, 21, 30)), // isync
MNEMONIC(31, InsertVal(20, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lwarx
MNEMONIC(31, InsertVal(150, 21, 30) | InsertVal(1, 31, 31), OpDesc_S, OpDesc_A,
OpDesc_B), // stwcx.
MNEMONIC(31, InsertVal(598, 21, 30)), // sync
// A-18
BASIC_MNEMONIC(50, _D, _Offd, _A), // lfd
BASIC_MNEMONIC(51, _D, _Offd, _A), // lfdu
MNEMONIC(31, InsertVal(631, 21, 30), _D, _A, _B), // lfdux
MNEMONIC(31, InsertVal(599, 21, 30), _D, _A, _B), // lfdx
BASIC_MNEMONIC(48, _D, _Offd, _A), // lfs
BASIC_MNEMONIC(49, _D, _Offd, _A), // lfsu
MNEMONIC(31, InsertVal(567, 21, 30), _D, _A, _B), // lfsux
MNEMONIC(31, InsertVal(535, 21, 30), _D, _A, _B), // lfsx
BASIC_MNEMONIC(50, OpDesc_D, OpDesc_Offd, OpDesc_A), // lfd
BASIC_MNEMONIC(51, OpDesc_D, OpDesc_Offd, OpDesc_A), // lfdu
MNEMONIC(31, InsertVal(631, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lfdux
MNEMONIC(31, InsertVal(599, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lfdx
BASIC_MNEMONIC(48, OpDesc_D, OpDesc_Offd, OpDesc_A), // lfs
BASIC_MNEMONIC(49, OpDesc_D, OpDesc_Offd, OpDesc_A), // lfsu
MNEMONIC(31, InsertVal(567, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lfsux
MNEMONIC(31, InsertVal(535, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // lfsx
// A-19
BASIC_MNEMONIC(54, _S, _Offd, _A), // stfd
BASIC_MNEMONIC(55, _S, _Offd, _A), // stfdu
MNEMONIC(31, InsertVal(759, 21, 30), _S, _A, _B), // stfdux
MNEMONIC(31, InsertVal(727, 21, 30), _S, _A, _B), // stfdx
MNEMONIC(31, InsertVal(983, 21, 30), _S, _A, _B), // stfiwx
BASIC_MNEMONIC(52, _S, _Offd, _A), // stfs
BASIC_MNEMONIC(53, _S, _Offd, _A), // stfsu
MNEMONIC(31, InsertVal(695, 21, 30), _S, _A, _B), // stfsux
MNEMONIC(31, InsertVal(663, 21, 30), _S, _A, _B), // stfsx
BASIC_MNEMONIC(54, OpDesc_S, OpDesc_Offd, OpDesc_A), // stfd
BASIC_MNEMONIC(55, OpDesc_S, OpDesc_Offd, OpDesc_A), // stfdu
MNEMONIC(31, InsertVal(759, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stfdux
MNEMONIC(31, InsertVal(727, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stfdx
MNEMONIC(31, InsertVal(983, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stfiwx
BASIC_MNEMONIC(52, OpDesc_S, OpDesc_Offd, OpDesc_A), // stfs
BASIC_MNEMONIC(53, OpDesc_S, OpDesc_Offd, OpDesc_A), // stfsu
MNEMONIC(31, InsertVal(695, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stfsux
MNEMONIC(31, InsertVal(663, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // stfsx
// A-20
RC_MNEMONIC(63, InsertVal(264, 21, 30), _D, _B), // fabs
RC_MNEMONIC(63, InsertVal(72, 21, 30), _D, _B), // fmr
RC_MNEMONIC(63, InsertVal(136, 21, 30), _D, _B), // fnabs
RC_MNEMONIC(63, InsertVal(40, 21, 30), _D, _B), // fneg
RC_MNEMONIC(63, InsertVal(264, 21, 30), OpDesc_D, OpDesc_B), // fabs
RC_MNEMONIC(63, InsertVal(72, 21, 30), OpDesc_D, OpDesc_B), // fmr
RC_MNEMONIC(63, InsertVal(136, 21, 30), OpDesc_D, OpDesc_B), // fnabs
RC_MNEMONIC(63, InsertVal(40, 21, 30), OpDesc_D, OpDesc_B), // fneg
// A-21
AALK_MNEMONIC(18, 0, _LI), // b
AALK_MNEMONIC(16, 0, _BO, _BI, _BD), // bc
LK_MNEMONIC(19, InsertVal(528, 21, 30), _BO, _BI), // bcctr
LK_MNEMONIC(19, InsertVal(16, 21, 30), _BO, _BI), // bclr
AALK_MNEMONIC(18, 0, OpDesc_LI), // b
AALK_MNEMONIC(16, 0, OpDesc_BO, OpDesc_BI, OpDesc_BD), // bc
LK_MNEMONIC(19, InsertVal(528, 21, 30), OpDesc_BO, OpDesc_BI), // bcctr
LK_MNEMONIC(19, InsertVal(16, 21, 30), OpDesc_BO, OpDesc_BI), // bclr
// A-22
MNEMONIC(19, InsertVal(257, 21, 30), _Crbd, _Crba, _Crbb), // crand
MNEMONIC(19, InsertVal(129, 21, 30), _Crbd, _Crba, _Crbb), // crandc
MNEMONIC(19, InsertVal(289, 21, 30), _Crbd, _Crba, _Crbb), // creqv
MNEMONIC(19, InsertVal(225, 21, 30), _Crbd, _Crba, _Crbb), // crnand
MNEMONIC(19, InsertVal(33, 21, 30), _Crbd, _Crba, _Crbb), // crnor
MNEMONIC(19, InsertVal(449, 21, 30), _Crbd, _Crba, _Crbb), // cror
MNEMONIC(19, InsertVal(417, 21, 30), _Crbd, _Crba, _Crbb), // crorc
MNEMONIC(19, InsertVal(193, 21, 30), _Crbd, _Crba, _Crbb), // crxor
MNEMONIC(19, InsertVal(0, 21, 30), _Crfd, _Crfs), // mcrf
MNEMONIC(19, InsertVal(257, 21, 30), OpDesc_Crbd, OpDesc_Crba, OpDesc_Crbb), // crand
MNEMONIC(19, InsertVal(129, 21, 30), OpDesc_Crbd, OpDesc_Crba, OpDesc_Crbb), // crandc
MNEMONIC(19, InsertVal(289, 21, 30), OpDesc_Crbd, OpDesc_Crba, OpDesc_Crbb), // creqv
MNEMONIC(19, InsertVal(225, 21, 30), OpDesc_Crbd, OpDesc_Crba, OpDesc_Crbb), // crnand
MNEMONIC(19, InsertVal(33, 21, 30), OpDesc_Crbd, OpDesc_Crba, OpDesc_Crbb), // crnor
MNEMONIC(19, InsertVal(449, 21, 30), OpDesc_Crbd, OpDesc_Crba, OpDesc_Crbb), // cror
MNEMONIC(19, InsertVal(417, 21, 30), OpDesc_Crbd, OpDesc_Crba, OpDesc_Crbb), // crorc
MNEMONIC(19, InsertVal(193, 21, 30), OpDesc_Crbd, OpDesc_Crba, OpDesc_Crbb), // crxor
MNEMONIC(19, InsertVal(0, 21, 30), OpDesc_Crfd, OpDesc_Crfs), // mcrf
// A-23
MNEMONIC(19, InsertVal(50, 21, 30)), // rfi
MNEMONIC(17, InsertVal(1, 30, 30)), // sc
// A-24
MNEMONIC(31, InsertVal(4, 21, 30), _TO, _A, _B), // tw
BASIC_MNEMONIC(3, _TO, _A, _SIMM), // twi
MNEMONIC(31, InsertVal(4, 21, 30), OpDesc_TO, OpDesc_A, OpDesc_B), // tw
BASIC_MNEMONIC(3, OpDesc_TO, OpDesc_A, OpDesc_SIMM), // twi
// A-25
MNEMONIC(31, InsertVal(512, 21, 30), _Crfd), // mcrxr
MNEMONIC(31, InsertVal(19, 21, 30), _D), // mfcr
MNEMONIC(31, InsertVal(83, 21, 30), _D), // mfmsr
MNEMONIC(31, InsertVal(339, 21, 30), _D, _SPR), // mfspr
MNEMONIC(31, InsertVal(371, 21, 30), _D, _TPR), // mftb
MNEMONIC(31, InsertVal(144, 21, 30), _CRM, _S), // mtcrf
MNEMONIC(31, InsertVal(146, 21, 30), _S), // mtmsr
MNEMONIC(31, InsertVal(467, 21, 30), _SPR, _D), // mtspr
MNEMONIC(31, InsertVal(512, 21, 30), OpDesc_Crfd), // mcrxr
MNEMONIC(31, InsertVal(19, 21, 30), OpDesc_D), // mfcr
MNEMONIC(31, InsertVal(83, 21, 30), OpDesc_D), // mfmsr
MNEMONIC(31, InsertVal(339, 21, 30), OpDesc_D, OpDesc_SPR), // mfspr
MNEMONIC(31, InsertVal(371, 21, 30), OpDesc_D, OpDesc_TPR), // mftb
MNEMONIC(31, InsertVal(144, 21, 30), OpDesc_CRM, OpDesc_S), // mtcrf
MNEMONIC(31, InsertVal(146, 21, 30), OpDesc_S), // mtmsr
MNEMONIC(31, InsertVal(467, 21, 30), OpDesc_SPR, OpDesc_D), // mtspr
// A-26
MNEMONIC(31, InsertVal(86, 21, 30), _A, _B), // dcbf
MNEMONIC(31, InsertVal(470, 21, 30), _A, _B), // dcbi
MNEMONIC(31, InsertVal(54, 21, 30), _A, _B), // dcbst
MNEMONIC(31, InsertVal(278, 21, 30), _A, _B), // dcbt
MNEMONIC(31, InsertVal(246, 21, 30), _A, _B), // dcbtst
MNEMONIC(31, InsertVal(1014, 21, 30), _A, _B), // dcbz
MNEMONIC(31, InsertVal(982, 21, 30), _A, _B), // icbi
MNEMONIC(31, InsertVal(86, 21, 30), OpDesc_A, OpDesc_B), // dcbf
MNEMONIC(31, InsertVal(470, 21, 30), OpDesc_A, OpDesc_B), // dcbi
MNEMONIC(31, InsertVal(54, 21, 30), OpDesc_A, OpDesc_B), // dcbst
MNEMONIC(31, InsertVal(278, 21, 30), OpDesc_A, OpDesc_B), // dcbt
MNEMONIC(31, InsertVal(246, 21, 30), OpDesc_A, OpDesc_B), // dcbtst
MNEMONIC(31, InsertVal(1014, 21, 30), OpDesc_A, OpDesc_B), // dcbz
MNEMONIC(31, InsertVal(982, 21, 30), OpDesc_A, OpDesc_B), // icbi
// A-27
MNEMONIC(31, InsertVal(595, 21, 30), _D, _SR), // mfsr
MNEMONIC(31, InsertVal(659, 21, 30), _D, _B), // mfsrin
MNEMONIC(31, InsertVal(210, 21, 30), _SR, _S), // mtsr
MNEMONIC(31, InsertVal(242, 21, 30), _S, _B), // mtsrin
MNEMONIC(31, InsertVal(595, 21, 30), OpDesc_D, OpDesc_SR), // mfsr
MNEMONIC(31, InsertVal(659, 21, 30), OpDesc_D, OpDesc_B), // mfsrin
MNEMONIC(31, InsertVal(210, 21, 30), OpDesc_SR, OpDesc_S), // mtsr
MNEMONIC(31, InsertVal(242, 21, 30), OpDesc_S, OpDesc_B), // mtsrin
// A-28
MNEMONIC(31, InsertVal(306, 21, 30), _B), // tlbie
MNEMONIC(31, InsertVal(566, 21, 30)), // tlbsync
MNEMONIC(31, InsertVal(306, 21, 30), OpDesc_B), // tlbie
MNEMONIC(31, InsertVal(566, 21, 30)), // tlbsync
// A-29
MNEMONIC(31, InsertVal(310, 21, 30), _D, _A, _B), // eciwx
MNEMONIC(31, InsertVal(438, 21, 30), _S, _A, _B), // ecowx
MNEMONIC(31, InsertVal(310, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // eciwx
MNEMONIC(31, InsertVal(438, 21, 30), OpDesc_S, OpDesc_A, OpDesc_B), // ecowx
// A-30
MNEMONIC(4, InsertVal(6, 25, 30), _D, _A, _B, _W2, _I2), // psq_lx
MNEMONIC(4, InsertVal(7, 25, 30), _S, _A, _B, _W2, _I2), // psq_stx
MNEMONIC(4, InsertVal(38, 25, 30), _D, _A, _B, _W2, _I2), // psq_lux
MNEMONIC(4, InsertVal(39, 25, 30), _S, _A, _B, _W2, _I2), // psq_stux
BASIC_MNEMONIC(56, _D, _OffdPs, _A, _W1, _I1), // psq_l
BASIC_MNEMONIC(57, _D, _OffdPs, _A, _W1, _I1), // psq_lu
BASIC_MNEMONIC(60, _S, _OffdPs, _A, _W1, _I1), // psq_st
BASIC_MNEMONIC(61, _S, _OffdPs, _A, _W1, _I1), // psq_stu
MNEMONIC(4, InsertVal(6, 25, 30), OpDesc_D, OpDesc_A, OpDesc_B, OpDesc_W2,
OpDesc_I2), // psq_lx
MNEMONIC(4, InsertVal(7, 25, 30), OpDesc_S, OpDesc_A, OpDesc_B, OpDesc_W2,
OpDesc_I2), // psq_stx
MNEMONIC(4, InsertVal(38, 25, 30), OpDesc_D, OpDesc_A, OpDesc_B, OpDesc_W2,
OpDesc_I2), // psq_lux
MNEMONIC(4, InsertVal(39, 25, 30), OpDesc_S, OpDesc_A, OpDesc_B, OpDesc_W2,
OpDesc_I2), // psq_stux
BASIC_MNEMONIC(56, OpDesc_D, OpDesc_OffdPs, OpDesc_A, OpDesc_W1, OpDesc_I1), // psq_l
BASIC_MNEMONIC(57, OpDesc_D, OpDesc_OffdPs, OpDesc_A, OpDesc_W1, OpDesc_I1), // psq_lu
BASIC_MNEMONIC(60, OpDesc_S, OpDesc_OffdPs, OpDesc_A, OpDesc_W1, OpDesc_I1), // psq_st
BASIC_MNEMONIC(61, OpDesc_S, OpDesc_OffdPs, OpDesc_A, OpDesc_W1, OpDesc_I1), // psq_stu
// A-31
RC_MNEMONIC(4, InsertVal(18, 26, 30), _D, _A, _B), // ps_div
RC_MNEMONIC(4, InsertVal(20, 26, 30), _D, _A, _B), // ps_sub
RC_MNEMONIC(4, InsertVal(21, 26, 30), _D, _A, _B), // ps_add
RC_MNEMONIC(4, InsertVal(23, 26, 30), _D, _A, _C, _B), // ps_sel
RC_MNEMONIC(4, InsertVal(24, 26, 30), _D, _B), // ps_res
RC_MNEMONIC(4, InsertVal(25, 26, 30), _D, _A, _C), // ps_mul
RC_MNEMONIC(4, InsertVal(26, 26, 30), _D, _B), // ps_rsqrte
RC_MNEMONIC(4, InsertVal(28, 26, 30), _D, _A, _C, _B), // ps_msub
RC_MNEMONIC(4, InsertVal(29, 26, 30), _D, _A, _C, _B), // ps_madd
RC_MNEMONIC(4, InsertVal(30, 26, 30), _D, _A, _C, _B), // ps_nmsub
RC_MNEMONIC(4, InsertVal(31, 26, 30), _D, _A, _C, _B), // ps_nmadd
RC_MNEMONIC(4, InsertVal(40, 21, 30), _D, _B), // ps_neg
RC_MNEMONIC(4, InsertVal(72, 21, 30), _D, _B), // ps_mr
RC_MNEMONIC(4, InsertVal(136, 21, 30), _D, _B), // ps_nabs
RC_MNEMONIC(4, InsertVal(264, 21, 30), _D, _B), // ps_abs
RC_MNEMONIC(4, InsertVal(18, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // ps_div
RC_MNEMONIC(4, InsertVal(20, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // ps_sub
RC_MNEMONIC(4, InsertVal(21, 26, 30), OpDesc_D, OpDesc_A, OpDesc_B), // ps_add
RC_MNEMONIC(4, InsertVal(23, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_sel
RC_MNEMONIC(4, InsertVal(24, 26, 30), OpDesc_D, OpDesc_B), // ps_res
RC_MNEMONIC(4, InsertVal(25, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C), // ps_mul
RC_MNEMONIC(4, InsertVal(26, 26, 30), OpDesc_D, OpDesc_B), // ps_rsqrte
RC_MNEMONIC(4, InsertVal(28, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_msub
RC_MNEMONIC(4, InsertVal(29, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_madd
RC_MNEMONIC(4, InsertVal(30, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_nmsub
RC_MNEMONIC(4, InsertVal(31, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_nmadd
RC_MNEMONIC(4, InsertVal(40, 21, 30), OpDesc_D, OpDesc_B), // ps_neg
RC_MNEMONIC(4, InsertVal(72, 21, 30), OpDesc_D, OpDesc_B), // ps_mr
RC_MNEMONIC(4, InsertVal(136, 21, 30), OpDesc_D, OpDesc_B), // ps_nabs
RC_MNEMONIC(4, InsertVal(264, 21, 30), OpDesc_D, OpDesc_B), // ps_abs
// A-32
RC_MNEMONIC(4, InsertVal(10, 26, 30), _D, _A, _C, _B), // ps_sum0
RC_MNEMONIC(4, InsertVal(11, 26, 30), _D, _A, _C, _B), // ps_sum1
RC_MNEMONIC(4, InsertVal(12, 26, 30), _D, _A, _C), // ps_muls0
RC_MNEMONIC(4, InsertVal(13, 26, 30), _D, _A, _C), // ps_muls1
RC_MNEMONIC(4, InsertVal(14, 26, 30), _D, _A, _C, _B), // ps_madds0
RC_MNEMONIC(4, InsertVal(15, 26, 30), _D, _A, _C, _B), // ps_madds1
MNEMONIC(4, InsertVal(0, 21, 30), _Crfd, _A, _B), // ps_cmpu0
MNEMONIC(4, InsertVal(32, 21, 30), _Crfd, _A, _B), // ps_cmpo0
MNEMONIC(4, InsertVal(64, 21, 30), _Crfd, _A, _B), // ps_cmpu1
MNEMONIC(4, InsertVal(96, 21, 30), _Crfd, _A, _B), // ps_cmpo1
RC_MNEMONIC(4, InsertVal(528, 21, 30), _D, _A, _B), // ps_merge00
RC_MNEMONIC(4, InsertVal(560, 21, 30), _D, _A, _B), // ps_merge01
RC_MNEMONIC(4, InsertVal(592, 21, 30), _D, _A, _B), // ps_merge10
RC_MNEMONIC(4, InsertVal(624, 21, 30), _D, _A, _B), // ps_merge11
MNEMONIC(4, InsertVal(1014, 21, 30), _A, _B), // dcbz_l
RC_MNEMONIC(4, InsertVal(10, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_sum0
RC_MNEMONIC(4, InsertVal(11, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_sum1
RC_MNEMONIC(4, InsertVal(12, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C), // ps_muls0
RC_MNEMONIC(4, InsertVal(13, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C), // ps_muls1
RC_MNEMONIC(4, InsertVal(14, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_madds0
RC_MNEMONIC(4, InsertVal(15, 26, 30), OpDesc_D, OpDesc_A, OpDesc_C, OpDesc_B), // ps_madds1
MNEMONIC(4, InsertVal(0, 21, 30), OpDesc_Crfd, OpDesc_A, OpDesc_B), // ps_cmpu0
MNEMONIC(4, InsertVal(32, 21, 30), OpDesc_Crfd, OpDesc_A, OpDesc_B), // ps_cmpo0
MNEMONIC(4, InsertVal(64, 21, 30), OpDesc_Crfd, OpDesc_A, OpDesc_B), // ps_cmpu1
MNEMONIC(4, InsertVal(96, 21, 30), OpDesc_Crfd, OpDesc_A, OpDesc_B), // ps_cmpo1
RC_MNEMONIC(4, InsertVal(528, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // ps_merge00
RC_MNEMONIC(4, InsertVal(560, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // ps_merge01
RC_MNEMONIC(4, InsertVal(592, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // ps_merge10
RC_MNEMONIC(4, InsertVal(624, 21, 30), OpDesc_D, OpDesc_A, OpDesc_B), // ps_merge11
MNEMONIC(4, InsertVal(1014, 21, 30), OpDesc_A, OpDesc_B), // dcbz_l
};
namespace

View file

@ -4,6 +4,7 @@
#include "Common/Assembler/GekkoLexer.h"
#include "Common/Assert.h"
#include "Common/StringUtil.h"
#include <iterator>
#include <numeric>
@ -181,6 +182,11 @@ std::optional<T> EvalIntegral(TokenType tp, std::string_view val)
case TokenType::BinaryLit:
return std::accumulate(val.begin() + 2, val.end(), T{0}, bin_step);
case TokenType::GPR:
if (CaseInsensitiveEquals(val, "sp"))
return T{1};
if (CaseInsensitiveEquals(val, "rtoc"))
return T{2};
[[fallthrough]];
case TokenType::FPR:
return std::accumulate(val.begin() + 1, val.end(), T{0}, dec_step);
case TokenType::CRField:
@ -643,50 +649,43 @@ TokenType Lexer::ClassifyAlnum() const
if (rn[0] == '3')
{
return rn[1] <= '2';
return rn[1] < '2';
}
}
return false;
};
constexpr auto eq_nocase = [](std::string_view str, std::string_view lwr) {
auto it_l = str.cbegin(), it_r = lwr.cbegin();
for (; it_l != str.cend() && it_r != lwr.cend(); it_l++, it_r++)
{
if (std::tolower(*it_l) != *it_r)
{
return false;
}
}
return it_l == str.end() && it_r == lwr.end();
};
if (std::tolower(alnum[0]) == 'r' && valid_regnum(alnum.substr(1)))
{
return TokenType::GPR;
}
else if ((CaseInsensitiveEquals(alnum, "sp")) || (CaseInsensitiveEquals(alnum, "rtoc")))
{
return TokenType::GPR;
}
else if (std::tolower(alnum[0]) == 'f' && valid_regnum(alnum.substr(1)))
{
return TokenType::FPR;
}
else if (alnum.length() == 3 && eq_nocase(alnum.substr(0, 2), "cr") && alnum[2] >= '0' &&
alnum[2] <= '7')
else if (alnum.length() == 3 && CaseInsensitiveEquals(alnum.substr(0, 2), "cr") &&
alnum[2] >= '0' && alnum[2] <= '7')
{
return TokenType::CRField;
}
else if (eq_nocase(alnum, "lt"))
else if (CaseInsensitiveEquals(alnum, "lt"))
{
return TokenType::Lt;
}
else if (eq_nocase(alnum, "gt"))
else if (CaseInsensitiveEquals(alnum, "gt"))
{
return TokenType::Gt;
}
else if (eq_nocase(alnum, "eq"))
else if (CaseInsensitiveEquals(alnum, "eq"))
{
return TokenType::Eq;
}
else if (eq_nocase(alnum, "so"))
else if (CaseInsensitiveEquals(alnum, "so"))
{
return TokenType::So;
}

View file

@ -312,7 +312,7 @@ class BitFieldArrayConstRef
friend class BitFieldArrayConstIterator<position, bits, size, T, S>;
public:
constexpr T Value() const { return m_array->Value(m_index); };
constexpr T Value() const { return m_array->Value(m_index); }
constexpr operator T() const { return Value(); }
private:
@ -333,7 +333,7 @@ class BitFieldArrayRef
friend class BitFieldArrayIterator<position, bits, size, T, S>;
public:
constexpr T Value() const { return m_array->Value(m_index); };
constexpr T Value() const { return m_array->Value(m_index); }
constexpr operator T() const { return Value(); }
T operator=(const BitFieldArrayRef<position, bits, size, T, S>& value) const
{

View file

@ -4,6 +4,7 @@
#pragma once
#include <array>
#include <bit>
#include <climits>
#include <cstddef>
#include <cstdint>
@ -166,50 +167,10 @@ inline auto BitCastPtr(PtrType* ptr) noexcept -> BitCastPtrType<T, PtrType>
}
// Similar to BitCastPtr, but specifically for aliasing structs to arrays.
template <typename ArrayType, typename T,
typename Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
inline auto BitCastToArray(const T& obj) noexcept -> Container
template <typename ValueType, typename From>
[[nodiscard]] constexpr auto BitCastToArray(const From& obj) noexcept
{
static_assert(sizeof(T) % sizeof(ArrayType) == 0,
"Size of array type must be a factor of size of source type.");
static_assert(std::is_trivially_copyable<T>(),
"BitCastToArray source type must be trivially copyable.");
static_assert(std::is_trivially_copyable<Container>(),
"BitCastToArray array type must be trivially copyable.");
Container result;
std::memcpy(result.data(), &obj, sizeof(T));
return result;
}
template <typename ArrayType, typename T,
typename Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
inline void BitCastFromArray(const Container& array, T& obj) noexcept
{
static_assert(sizeof(T) % sizeof(ArrayType) == 0,
"Size of array type must be a factor of size of destination type.");
static_assert(std::is_trivially_copyable<Container>(),
"BitCastFromArray array type must be trivially copyable.");
static_assert(std::is_trivially_copyable<T>(),
"BitCastFromArray destination type must be trivially copyable.");
std::memcpy(&obj, array.data(), sizeof(T));
}
template <typename ArrayType, typename T,
typename Container = std::array<ArrayType, sizeof(T) / sizeof(ArrayType)>>
inline auto BitCastFromArray(const Container& array) noexcept -> T
{
static_assert(sizeof(T) % sizeof(ArrayType) == 0,
"Size of array type must be a factor of size of destination type.");
static_assert(std::is_trivially_copyable<Container>(),
"BitCastFromArray array type must be trivially copyable.");
static_assert(std::is_trivially_copyable<T>(),
"BitCastFromArray destination type must be trivially copyable.");
T obj;
std::memcpy(&obj, array.data(), sizeof(T));
return obj;
return std::bit_cast<std::array<ValueType, sizeof(From) / sizeof(ValueType)>>(obj);
}
template <typename T>

View file

@ -87,6 +87,7 @@ add_library(common
JitRegister.cpp
JitRegister.h
JsonUtil.h
JsonUtil.cpp
Lazy.h
LinearDiskCache.h
Logging/ConsoleListener.h
@ -143,6 +144,7 @@ add_library(common
TraversalClient.h
TraversalProto.h
TypeUtils.h
Unreachable.h
UPnP.cpp
UPnP.h
VariantUtil.h

View file

@ -17,7 +17,7 @@ namespace Common
// having to prefix them with gen-> or something similar.
// Example implementation:
// class JIT : public CodeBlock<ARMXEmitter> {}
template <class T>
template <class T, bool executable = true>
class CodeBlock : public T
{
private:
@ -53,7 +53,10 @@ public:
{
region_size = size;
total_region_size = size;
region = static_cast<u8*>(Common::AllocateExecutableMemory(total_region_size));
if constexpr (executable)
region = static_cast<u8*>(Common::AllocateExecutableMemory(total_region_size));
else
region = static_cast<u8*>(Common::AllocateMemoryPages(total_region_size));
T::SetCodePtr(region, region + size);
}

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/ColorUtil.h"
#include "Common/Swap.h"
namespace Common

View file

@ -37,7 +37,7 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
{ \
DebugBreak(); \
}
#endif // WIN32 ndef
#endif // _WIN32
namespace Common
{

View file

@ -11,7 +11,6 @@
#define ROOT_DIR "."
// The normal user directory
#ifndef STEAM
#ifdef _WIN32
#define NORMAL_USER_DIR "Dolphin Emulator"
#elif defined(__APPLE__)
@ -21,15 +20,6 @@
#else
#define NORMAL_USER_DIR "dolphin-emu"
#endif
#else // ifndef STEAM
#ifdef _WIN32
#define NORMAL_USER_DIR "Dolphin Emulator (Steam)"
#elif defined(__APPLE__)
#define NORMAL_USER_DIR "Library/Application Support/Dolphin (Steam)"
#else
#define NORMAL_USER_DIR "dolphin-emu-steam"
#endif
#endif
// The portable user directory
#ifdef _WIN32
@ -63,6 +53,7 @@
#define COVERCACHE_DIR "GameCovers"
#define REDUMPCACHE_DIR "Redump"
#define SHADERCACHE_DIR "Shaders"
#define RETROACHIEVEMENTSCACHE_DIR "RetroAchievements"
#define STATESAVES_DIR "StateSaves"
#define SCREENSHOTS_DIR "ScreenShots"
#define LOAD_DIR "Load"
@ -160,6 +151,10 @@
#define GECKO_CODE_HANDLER "codehandler.bin"
#define GECKO_CODE_HANDLER_MPN "codehandler-mpn.bin"
#define GECKO_CODE_HANDLER_MPN_SUPER "codehandler-mpn.bin"
// Subdirs in Sys
#define GC_SYS_DIR "GC"
#define WII_SYS_DIR "Wii"

View file

@ -261,4 +261,4 @@ int __cdecl EnableCompatPatches()
extern "C" {
__declspec(allocate(".CRT$XCZ")) decltype(&EnableCompatPatches)
enableCompatPatches = EnableCompatPatches;
};
}

View file

@ -1,6 +1,8 @@
// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/Crypto/AES.h"
#include <array>
#include <bit>
#include <memory>
@ -9,7 +11,6 @@
#include "Common/Assert.h"
#include "Common/CPUDetect.h"
#include "Common/Crypto/AES.h"
#ifdef _MSC_VER
#include <intrin.h>

View file

@ -385,4 +385,20 @@ Digest CalculateDigest(const u8* msg, size_t len)
ctx->Update(msg, len);
return ctx->Finish();
}
std::string DigestToString(const Digest& digest)
{
static constexpr std::array<char, 16> lookup = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
std::string hash;
hash.reserve(digest.size() * 2);
for (size_t i = 0; i < digest.size(); ++i)
{
const u8 upper = static_cast<u8>((digest[i] >> 4) & 0xf);
const u8 lower = static_cast<u8>(digest[i] & 0xf);
hash.push_back(lookup[upper]);
hash.push_back(lookup[lower]);
}
return hash;
}
} // namespace Common::SHA1

View file

@ -6,6 +6,7 @@
#include <array>
#include <limits>
#include <memory>
#include <span>
#include <string_view>
#include <type_traits>
#include <vector>
@ -23,7 +24,11 @@ class Context
public:
virtual ~Context() = default;
virtual void Update(const u8* msg, size_t len) = 0;
void Update(const std::vector<u8>& msg) { return Update(msg.data(), msg.size()); }
void Update(std::span<const u8> msg) { return Update(msg.data(), msg.size()); }
void Update(std::string_view msg)
{
return Update(reinterpret_cast<const u8*>(msg.data()), msg.size());
}
virtual Digest Finish() = 0;
virtual bool HwAccelerated() const = 0;
};
@ -51,4 +56,6 @@ inline Digest CalculateDigest(const std::array<T, Size>& msg)
static_assert(std::is_trivially_copyable_v<T>);
return CalculateDigest(reinterpret_cast<const u8*>(msg.data()), sizeof(msg));
}
std::string DigestToString(const Digest& digest);
} // namespace Common::SHA1

View file

@ -14,7 +14,7 @@
namespace Core
{
class CPUThreadGuard;
};
}
namespace Common::Debug
{

View file

@ -553,7 +553,7 @@ bool SyncSDFolderToSDImage(const std::function<bool()>& cancelled, bool determin
}
MKFS_PARM options = {};
options.fmt = FM_FAT32;
options.fmt = FM_FAT32 | FM_SFD;
options.n_fat = 0; // Number of FATs: automatic
options.align = 1; // Alignment of the data region (in sectors)
options.n_root = 0; // Number of root directory entries: automatic (and unused for FAT32)

View file

@ -60,6 +60,10 @@
#include "jni/AndroidCommon/AndroidCommon.h"
#endif
#if defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif
namespace fs = std::filesystem;
namespace File
@ -738,6 +742,15 @@ std::string GetExePath()
return PathToString(exe_path_absolute);
#elif defined(__APPLE__)
return GetBundleDirectory();
#elif defined(__FreeBSD__)
int name[4]{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
size_t length = 0;
if (sysctl(name, 4, nullptr, &length, nullptr, 0) != 0 || length == 0)
return {};
std::string dolphin_exe_path(length, '\0');
if (sysctl(name, 4, dolphin_exe_path.data(), &length, nullptr, 0) != 0)
return {};
return dolphin_exe_path;
#else
char dolphin_exe_path[PATH_MAX];
ssize_t len = ::readlink("/proc/self/exe", dolphin_exe_path, sizeof(dolphin_exe_path));
@ -843,6 +856,8 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[D_COVERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + COVERCACHE_DIR DIR_SEP;
s_user_paths[D_REDUMPCACHE_IDX] = s_user_paths[D_CACHE_IDX] + REDUMPCACHE_DIR DIR_SEP;
s_user_paths[D_SHADERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + SHADERCACHE_DIR DIR_SEP;
s_user_paths[D_RETROACHIEVEMENTSCACHE_IDX] =
s_user_paths[D_CACHE_IDX] + RETROACHIEVEMENTSCACHE_DIR DIR_SEP;
s_user_paths[D_SHADERS_IDX] = s_user_paths[D_USER_IDX] + SHADERS_DIR DIR_SEP;
s_user_paths[D_STATESAVES_IDX] = s_user_paths[D_USER_IDX] + STATESAVES_DIR DIR_SEP;
s_user_paths[D_SCREENSHOTS_IDX] = s_user_paths[D_USER_IDX] + SCREENSHOTS_DIR DIR_SEP;
@ -926,6 +941,8 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[D_COVERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + COVERCACHE_DIR DIR_SEP;
s_user_paths[D_REDUMPCACHE_IDX] = s_user_paths[D_CACHE_IDX] + REDUMPCACHE_DIR DIR_SEP;
s_user_paths[D_SHADERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + SHADERCACHE_DIR DIR_SEP;
s_user_paths[D_RETROACHIEVEMENTSCACHE_IDX] =
s_user_paths[D_CACHE_IDX] + RETROACHIEVEMENTSCACHE_DIR DIR_SEP;
break;
case D_GCUSER_IDX:

View file

@ -40,6 +40,7 @@ enum
D_COVERCACHE_IDX,
D_REDUMPCACHE_IDX,
D_SHADERCACHE_IDX,
D_RETROACHIEVEMENTSCACHE_IDX,
D_SHADERS_IDX,
D_STATESAVES_IDX,
D_SCREENSHOTS_IDX,

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/GL/GLInterface/AGL.h"
#include "Common/Logging/Log.h"
// UpdateCachedDimensions and AttachContextToView contain calls to UI APIs, so they must only be

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/GL/GLX11Window.h"
#include "Common/GL/GLContext.h"
GLX11Window::GLX11Window(Display* display, Window parent_window, Colormap color_map, Window window,

View file

@ -75,7 +75,7 @@ bool IniFile::Section::Get(std::string_view key, std::string* value,
bool IniFile::Section::Exists(std::string_view key) const
{
return values.find(key) != values.end();
return values.contains(key);
}
bool IniFile::Section::Delete(std::string_view key)

View file

@ -3,6 +3,10 @@
#include "Common/JsonUtil.h"
#include <fstream>
#include "Common/FileUtil.h"
picojson::object ToJsonObject(const Common::Vec3& vec)
{
picojson::object obj;
@ -38,3 +42,27 @@ std::optional<bool> ReadBoolFromJson(const picojson::object& obj, const std::str
return std::nullopt;
return it->second.get<bool>();
}
bool JsonToFile(const std::string& filename, const picojson::value& root, bool prettify)
{
std::ofstream json_stream;
File::OpenFStream(json_stream, filename, std::ios_base::out);
if (!json_stream.is_open())
{
return false;
}
json_stream << root.serialize(prettify);
return true;
}
bool JsonFromFile(const std::string& filename, picojson::value* root, std::string* error)
{
std::string json_data;
if (!File::ReadFileToString(filename, json_data))
{
return false;
}
*error = picojson::parse(*root, json_data);
return error->empty();
}

View file

@ -9,7 +9,6 @@
#include <picojson.h>
#include "Common/MathUtil.h"
#include "Common/Matrix.h"
// Ideally this would use a concept like, 'template <std::ranges::range Range>' to constrain it,
@ -47,7 +46,7 @@ std::optional<Type> ReadNumericFromJson(const picojson::object& obj, const std::
return std::nullopt;
if (!it->second.is<double>())
return std::nullopt;
return MathUtil::SaturatingCast<Type>(it->second.get<double>());
return static_cast<Type>(it->second.get<double>());
}
std::optional<std::string> ReadStringFromJson(const picojson::object& obj, const std::string& key);
@ -56,3 +55,6 @@ std::optional<bool> ReadBoolFromJson(const picojson::object& obj, const std::str
picojson::object ToJsonObject(const Common::Vec3& vec);
void FromJson(const picojson::object& obj, Common::Vec3& vec);
bool JsonToFile(const std::string& filename, const picojson::value& root, bool prettify = false);
bool JsonFromFile(const std::string& filename, picojson::value* root, std::string* error);

View file

@ -78,7 +78,7 @@ public:
{
static LdrDllNotifier notifier;
return notifier;
};
}
void Install(LdrObserver* observer);
void Uninstall(LdrObserver* observer);

View file

@ -54,7 +54,7 @@ public:
private:
Profiler* m_p;
};
}; // namespace Common
} // namespace Common
// Warning: This profiler isn't thread safe. Only profile functions which doesn't run simultaneously
#define PROFILE(name) \

View file

@ -3,6 +3,9 @@
#include "Common/SocketContext.h"
#include "Common/Logging/Log.h"
#include "Common/Network.h"
namespace Common
{
#ifdef _WIN32
@ -11,7 +14,23 @@ SocketContext::SocketContext()
std::lock_guard<std::mutex> g(s_lock);
if (s_num_objects == 0)
{
static_cast<void>(WSAStartup(MAKEWORD(2, 2), &s_data));
const int ret = WSAStartup(MAKEWORD(2, 2), &s_data);
if (ret == 0)
{
INFO_LOG_FMT(COMMON, "WSAStartup succeeded, wVersion={}.{}, wHighVersion={}.{}",
int(LOBYTE(s_data.wVersion)), int(HIBYTE(s_data.wVersion)),
int(LOBYTE(s_data.wHighVersion)), int(HIBYTE(s_data.wHighVersion)));
}
else
{
// The WSAStartup function directly returns the extended error code in the return value.
// A call to the WSAGetLastError function is not needed and should not be used.
//
// Source:
// https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsastartup
ERROR_LOG_FMT(COMMON, "WSAStartup failed with error {}: {}", ret,
Common::DecodeNetworkError(ret));
}
}
s_num_objects++;
}

View file

@ -195,7 +195,7 @@ std::from_chars_result FromChars(std::string_view sv, T& value,
const char* const last = first + sv.size();
return std::from_chars(first, last, value, fmt);
}
}; // namespace Common
} // namespace Common
std::string TabsToSpaces(int tab_size, std::string str);

View file

@ -82,17 +82,4 @@ static_assert(!IsNOf<int, 1, int, int>::value);
static_assert(IsNOf<int, 2, int, int>::value);
static_assert(IsNOf<int, 2, int, short>::value); // Type conversions ARE allowed
static_assert(!IsNOf<int, 2, int, char*>::value);
// TODO: This can be replaced with std::array's fill() once C++20 is fully supported.
// Prior to C++20, std::array's fill() function is, unfortunately, not constexpr.
// Ditto for <algorithm>'s std::fill. Although Dolphin targets C++20, Android doesn't
// seem to properly support constexpr fill(), so we need this for now.
template <typename T1, size_t N, typename T2>
constexpr void Fill(std::array<T1, N>& array, const T2& value)
{
for (auto& entry : array)
{
entry = value;
}
}
} // namespace Common

View file

@ -0,0 +1,21 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Common/CommonFuncs.h"
namespace Common
{
// TODO C++23: Replace with std::unreachable.
[[noreturn]] inline void Unreachable()
{
#ifdef _DEBUG
Crash();
#elif defined(_MSC_VER) && !defined(__clang__)
__assume(false);
#else
__builtin_unreachable();
#endif
}
} // namespace Common

View file

@ -9,6 +9,8 @@
namespace Common
{
#define EMULATOR_NAME "Dolphin"
#ifdef _DEBUG
#define BUILD_TYPE_STR "Debug "
#elif defined DEBUGFAST
@ -17,13 +19,6 @@ namespace Common
#define BUILD_TYPE_STR ""
#endif
const std::string& GetScmRevStr()
{
#define MPN_REV_STR "02/27/2022"
static const std::string scm_rev_str = "Dolphin MPN";
return scm_rev_str;
}
const std::string& GetScmRevGitStr()
{
static const std::string scm_rev_git_str = SCM_REV_STR;
@ -34,6 +29,12 @@ const std::string& GetScmDescStr()
{
static const std::string scm_desc_str = SCM_DESC_STR;
return scm_desc_str;
}
const std::string& GetScmRevStr()
{
static const std::string scm_rev_str = std::string("Dolphin MPN (") + SCM_DESC_STR + ")";
return scm_rev_str;
}
const std::string& GetScmBranchStr()
@ -42,6 +43,12 @@ const std::string& GetScmBranchStr()
return scm_branch_str;
}
const std::string& GetUserAgentStr()
{
static const std::string user_agent_str = EMULATOR_NAME "/" SCM_DESC_STR;
return user_agent_str;
}
const std::string& GetScmDistributorStr()
{
static const std::string scm_distributor_str = SCM_DISTRIBUTOR_STR;

View file

@ -11,6 +11,7 @@ const std::string& GetScmDescStr();
const std::string& GetScmBranchStr();
const std::string& GetScmRevStr();
const std::string& GetScmRevGitStr();
const std::string& GetUserAgentStr();
const std::string& GetScmDistributorStr();
const std::string& GetScmUpdateTrackStr();
const std::string& GetNetplayDolphinVer();

View file

@ -69,4 +69,4 @@ OSVERSIONINFOW GetOSVersion()
}
return info;
}
}; // namespace WindowsRegistry
} // namespace WindowsRegistry

View file

@ -15,4 +15,4 @@ template <>
bool ReadValue(std::string* value, const std::string& subkey, const std::string& name);
OSVERSIONINFOW GetOSVersion();
}; // namespace WindowsRegistry
} // namespace WindowsRegistry

View file

@ -3,9 +3,10 @@ var oFS = new ActiveXObject("Scripting.FileSystemObject");
var outfile = "./scmrev.h";
var cmd_revision = " rev-parse HEAD";
var cmd_describe = " describe --always --long --dirty";
var cmd_describe = " rev-parse --short HEAD";
var cmd_branch = " rev-parse --abbrev-ref HEAD";
var cmd_commits_ahead = " rev-list --count HEAD ^master";
var cmd_get_tag = " describe --exact-match HEAD";
function GetGitExe()
{
@ -59,6 +60,25 @@ function GetFirstStdOutLine(cmd)
}
}
function AttemptToExecuteCommand(cmd)
{
try
{
var exec = wshShell.Exec(cmd)
// wait until the command has finished
while (exec.Status == 0) {}
return exec.ExitCode;
}
catch (e)
{
// catch "the system cannot find the file specified" error
WScript.Echo("Failed to exec " + cmd + " this should never happen");
WScript.Quit(1);
}
}
function GetFileContents(f)
{
try
@ -88,6 +108,12 @@ if (default_update_track == "%DOLPHIN_DEFAULT_UPDATE_TRACK%") default_update_tra
// remove hash (and trailing "-0" if needed) from description
describe = describe.replace(/(-0)?-[^-]+(-dirty)?$/, '$2');
// set commits ahead to zero if on a tag
if (AttemptToExecuteCommand(gitexe + cmd_get_tag) == 0)
{
commits_ahead = "0";
}
var out_contents =
"#define SCM_REV_STR \"" + revision + "\"\n" +
"#define SCM_DESC_STR \"" + describe + "\"\n" +

View file

@ -50,7 +50,7 @@
// FIXME: avoid pushing all 16 XMM registers when possible? most functions we call probably
// don't actually clobber them.
#define ABI_ALL_CALLER_SAVED (BitSet32{RAX, RCX, RDX, RDI, RSI, R8, R9, R10, R11} | ABI_ALL_FPRS)
#endif // WIN32
#endif // _WIN32
#define ABI_ALL_CALLEE_SAVED (~ABI_ALL_CALLER_SAVED)

View file

@ -107,26 +107,6 @@ void XEmitter::SetCodePtr(u8* ptr, u8* end, bool write_failed)
m_write_failed = write_failed;
}
const u8* XEmitter::GetCodePtr() const
{
return code;
}
u8* XEmitter::GetWritableCodePtr()
{
return code;
}
const u8* XEmitter::GetCodeEnd() const
{
return m_code_end;
}
u8* XEmitter::GetWritableCodeEnd()
{
return m_code_end;
}
void XEmitter::Write8(u8 value)
{
if (code >= m_code_end)

View file

@ -394,10 +394,10 @@ public:
u8* AlignCode4();
u8* AlignCode16();
u8* AlignCodePage();
const u8* GetCodePtr() const;
u8* GetWritableCodePtr();
const u8* GetCodeEnd() const;
u8* GetWritableCodeEnd();
const u8* GetCodePtr() const { return code; }
u8* GetWritableCodePtr() { return code; }
const u8* GetCodeEnd() const { return m_code_end; }
u8* GetWritableCodeEnd() { return m_code_end; }
void LockFlags() { flags_locked = true; }
void UnlockFlags() { flags_locked = false; }
@ -1082,14 +1082,6 @@ public:
ABI_CallFunction(func);
}
template <typename FunctionPointer>
void ABI_CallFunctionPP(FunctionPointer func, const void* param1, const void* param2)
{
MOV(64, R(ABI_PARAM1), Imm64(reinterpret_cast<u64>(param1)));
MOV(64, R(ABI_PARAM2), Imm64(reinterpret_cast<u64>(param2)));
ABI_CallFunction(func);
}
template <typename FunctionPointer>
void ABI_CallFunctionPC(FunctionPointer func, const void* param1, u32 param2)
{

View file

@ -13,19 +13,31 @@
#include <rcheevos/include/rc_api_info.h>
#include <rcheevos/include/rc_hash.h>
#include "Common/Assert.h"
#include "Common/BitUtils.h"
#include "Common/CommonPaths.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Common/Image.h"
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Common/Version.h"
#include "Common/WorkQueueThread.h"
#include "Core/Config/AchievementSettings.h"
#include "Core/Core.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/VideoInterface.h"
#include "Core/PatchEngine.h"
#include "Core/PowerPC/MMU.h"
#include "Core/System.h"
#include "DiscIO/Blob.h"
#include "UICommon/DiscordPresence.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/VideoEvents.h"
static std::unique_ptr<OSD::Icon> DecodeBadgeToOSDIcon(const AchievementManager::Badge& badge);
static const Common::HttpRequest::Headers USER_AGENT_HEADER = {
{"User-Agent", Common::GetUserAgentStr()}};
AchievementManager& AchievementManager::GetInstance()
{
@ -35,9 +47,13 @@ AchievementManager& AchievementManager::GetInstance()
void AchievementManager::Init()
{
LoadDefaultBadges();
if (!m_client && Config::Get(Config::RA_ENABLED))
{
m_client = rc_client_create(MemoryPeeker, Request);
{
std::lock_guard lg{m_lock};
m_client = rc_client_create(MemoryVerifier, Request);
}
std::string host_url = Config::Get(Config::RA_HOST_URL);
if (!host_url.empty())
rc_client_set_host(m_client, host_url.c_str());
@ -56,6 +72,33 @@ void AchievementManager::Init()
}
}
picojson::value AchievementManager::LoadApprovedList()
{
picojson::value temp;
std::string error;
if (!JsonFromFile(fmt::format("{}{}{}", File::GetSysDirectory(), DIR_SEP, APPROVED_LIST_FILENAME),
&temp, &error))
{
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to load approved game settings list {}",
APPROVED_LIST_FILENAME);
WARN_LOG_FMT(ACHIEVEMENTS, "Error: {}", error);
return {};
}
auto context = Common::SHA1::CreateContext();
context->Update(temp.serialize());
auto digest = context->Finish();
if (digest != APPROVED_LIST_HASH)
{
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to verify approved game settings list {}",
APPROVED_LIST_FILENAME);
WARN_LOG_FMT(ACHIEVEMENTS, "Expected hash {}, found hash {}",
Common::SHA1::DigestToString(APPROVED_LIST_HASH),
Common::SHA1::DigestToString(digest));
return {};
}
return temp;
}
void AchievementManager::SetUpdateCallback(UpdateCallback callback)
{
m_update_callback = std::move(callback);
@ -137,6 +180,7 @@ void AchievementManager::LoadGame(const std::string& file_path, const DiscIO::Vo
}
else
{
rc_client_set_read_memory_function(m_client, MemoryVerifier);
rc_client_begin_identify_and_load_game(m_client, RC_CONSOLE_GAMECUBE, file_path.c_str(), NULL,
0, LoadGameCallback, NULL);
}
@ -148,6 +192,18 @@ bool AchievementManager::IsGameLoaded() const
return game_info && game_info->id != 0;
}
void AchievementManager::SetBackgroundExecutionAllowed(bool allowed)
{
m_background_execution_allowed = allowed;
Core::System* system = m_system.load(std::memory_order_acquire);
if (!system)
return;
if (allowed && Core::GetState(*system) == Core::State::Paused)
DoIdle();
}
void AchievementManager::FetchPlayerBadge()
{
FetchBadge(&m_player_badge, RC_IMAGE_TYPE_USER,
@ -219,7 +275,8 @@ void AchievementManager::DoFrame()
std::lock_guard lg{m_lock};
rc_client_do_frame(m_client);
}
if (!m_system)
Core::System* system = m_system.load(std::memory_order_acquire);
if (!system)
return;
auto current_time = std::chrono::steady_clock::now();
if (current_time - m_last_rp_time > std::chrono::seconds{10})
@ -227,9 +284,59 @@ void AchievementManager::DoFrame()
m_last_rp_time = current_time;
rc_client_get_rich_presence_message(m_client, m_rich_presence.data(), RP_SIZE);
m_update_callback(UpdatedItems{.rich_presence = true});
if (Config::Get(Config::RA_DISCORD_PRESENCE_ENABLED))
Discord::UpdateDiscordPresence();
}
}
bool AchievementManager::CanPause()
{
u32 frames_to_next_pause = 0;
bool can_pause = rc_client_can_pause(m_client, &frames_to_next_pause);
if (!can_pause)
{
OSD::AddMessage(
fmt::format("RetroAchievements Hardcore Mode:\n"
"Cannot pause until another {:.2f} seconds have passed.",
static_cast<float>(frames_to_next_pause) /
Core::System::GetInstance().GetVideoInterface().GetTargetRefreshRate()),
OSD::Duration::VERY_LONG, OSD::Color::RED);
}
return can_pause;
}
void AchievementManager::DoIdle()
{
std::thread([this]() {
while (true)
{
Common::SleepCurrentThread(1000);
{
std::lock_guard lg{m_lock};
Core::System* system = m_system.load(std::memory_order_acquire);
if (!system || Core::GetState(*system) != Core::State::Paused)
return;
if (!m_background_execution_allowed)
return;
if (!m_client || !IsGameLoaded())
return;
}
// rc_client_idle peeks at memory to recalculate rich presence and therefore
// needs to be on host or CPU thread to access memory.
Core::QueueHostJob([this](Core::System& system) {
std::lock_guard lg{m_lock};
if (Core::GetState(system) != Core::State::Paused)
return;
if (!m_background_execution_allowed)
return;
if (!m_client || !IsGameLoaded())
return;
rc_client_idle(m_client);
});
}
}).detach();
}
std::recursive_mutex& AchievementManager::GetLock()
{
return m_lock;
@ -250,6 +357,62 @@ bool AchievementManager::IsHardcoreModeActive() const
return rc_client_is_processing_required(m_client);
}
void AchievementManager::FilterApprovedPatches(std::vector<PatchEngine::Patch>& patches,
const std::string& game_ini_id) const
{
if (patches.empty())
{
// There's nothing to verify, so let's save ourselves some work
return;
}
std::lock_guard lg{m_lock};
if (!IsHardcoreModeActive())
return;
const bool known_id = m_ini_root->contains(game_ini_id);
auto patch_itr = patches.begin();
while (patch_itr != patches.end())
{
INFO_LOG_FMT(ACHIEVEMENTS, "Verifying patch {}", patch_itr->name);
bool verified = false;
if (known_id)
{
auto context = Common::SHA1::CreateContext();
context->Update(Common::BitCastToArray<u8>(static_cast<u64>(patch_itr->entries.size())));
for (const auto& entry : patch_itr->entries)
{
context->Update(Common::BitCastToArray<u8>(entry.type));
context->Update(Common::BitCastToArray<u8>(entry.address));
context->Update(Common::BitCastToArray<u8>(entry.value));
context->Update(Common::BitCastToArray<u8>(entry.comparand));
context->Update(Common::BitCastToArray<u8>(entry.conditional));
}
auto digest = context->Finish();
verified = m_ini_root->get(game_ini_id).contains(Common::SHA1::DigestToString(digest));
}
if (!verified)
{
patch_itr = patches.erase(patch_itr);
OSD::AddMessage(
fmt::format("Failed to verify patch {} from file {}.", patch_itr->name, game_ini_id),
OSD::Duration::VERY_LONG, OSD::Color::RED);
OSD::AddMessage("Disable hardcore mode to enable this patch.", OSD::Duration::VERY_LONG,
OSD::Color::RED);
}
else
{
patch_itr++;
}
}
}
void AchievementManager::SetSpectatorMode()
{
rc_client_set_spectator_mode_enabled(m_client, Config::Get(Config::RA_SPECTATOR_ENABLED));
@ -275,9 +438,9 @@ u32 AchievementManager::GetPlayerScore() const
return user->score;
}
const AchievementManager::BadgeStatus& AchievementManager::GetPlayerBadge() const
const AchievementManager::Badge& AchievementManager::GetPlayerBadge() const
{
return m_player_badge;
return m_player_badge.data.empty() ? m_default_player_badge : m_player_badge;
}
std::string_view AchievementManager::GetGameDisplayName() const
@ -295,17 +458,19 @@ rc_api_fetch_game_data_response_t* AchievementManager::GetGameData()
return &m_game_data;
}
const AchievementManager::BadgeStatus& AchievementManager::GetGameBadge() const
const AchievementManager::Badge& AchievementManager::GetGameBadge() const
{
return m_game_badge;
return m_game_badge.data.empty() ? m_default_game_badge : m_game_badge;
}
const AchievementManager::BadgeStatus& AchievementManager::GetAchievementBadge(AchievementId id,
bool locked) const
const AchievementManager::Badge& AchievementManager::GetAchievementBadge(AchievementId id,
bool locked) const
{
auto& badge_list = locked ? m_locked_badges : m_locked_badges;
auto& badge_list = locked ? m_locked_badges : m_unlocked_badges;
auto itr = badge_list.find(id);
return (itr == badge_list.end()) ? m_default_badge : itr->second;
return (itr != badge_list.end() && itr->second.data.size() > 0) ?
itr->second :
(locked ? m_default_locked_badge : m_default_unlocked_badge);
}
const AchievementManager::LeaderboardStatus*
@ -314,8 +479,6 @@ AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderb
if (const auto leaderboard_iter = m_leaderboard_map.find(leaderboard_id);
leaderboard_iter != m_leaderboard_map.end())
{
if (leaderboard_iter->second.entries.size() == 0)
FetchBoardInfo(leaderboard_id);
return &leaderboard_iter->second;
}
@ -327,7 +490,18 @@ AchievementManager::RichPresence AchievementManager::GetRichPresence() const
return m_rich_presence;
}
const AchievementManager::NamedIconMap& AchievementManager::GetChallengeIcons() const
bool AchievementManager::AreChallengesUpdated() const
{
return m_challenges_updated;
}
void AchievementManager::ResetChallengesUpdated()
{
m_challenges_updated = false;
}
const std::unordered_set<AchievementManager::AchievementId>&
AchievementManager::GetActiveChallenges() const
{
return m_active_challenges;
}
@ -390,29 +564,36 @@ void AchievementManager::CloseGame()
{
m_active_challenges.clear();
m_active_leaderboards.clear();
m_game_badge.name.clear();
m_game_badge.width = 0;
m_game_badge.height = 0;
m_game_badge.data.clear();
m_unlocked_badges.clear();
m_locked_badges.clear();
m_leaderboard_map.clear();
m_rich_presence.fill('\0');
rc_api_destroy_fetch_game_data_response(&m_game_data);
m_game_data = {};
m_queue.Cancel();
m_image_queue.Cancel();
rc_client_unload_game(m_client);
m_system = nullptr;
m_system.store(nullptr, std::memory_order_release);
if (Config::Get(Config::RA_DISCORD_PRESENCE_ENABLED))
Discord::UpdateDiscordPresence();
INFO_LOG_FMT(ACHIEVEMENTS, "Game closed.");
}
}
m_update_callback(UpdatedItems{.all = true});
INFO_LOG_FMT(ACHIEVEMENTS, "Game closed.");
}
void AchievementManager::Logout()
{
{
std::lock_guard lg{m_lock};
CloseGame();
m_player_badge.name.clear();
std::lock_guard lg{m_lock};
m_player_badge.width = 0;
m_player_badge.height = 0;
m_player_badge.data.clear();
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, "");
}
@ -426,6 +607,7 @@ void AchievementManager::Shutdown()
{
CloseGame();
m_queue.Shutdown();
std::lock_guard lg{m_lock};
// DON'T log out - keep those credentials for next run.
rc_client_destroy(m_client);
m_client = nullptr;
@ -497,6 +679,53 @@ void AchievementManager::FilereaderClose(void* file_handle)
delete static_cast<FilereaderState*>(file_handle);
}
void AchievementManager::LoadDefaultBadges()
{
std::lock_guard lg{m_lock};
std::string directory = File::GetSysDirectory() + DIR_SEP + RESOURCES_DIR + DIR_SEP;
if (m_default_player_badge.data.empty())
{
if (!LoadPNGTexture(&m_default_player_badge,
fmt::format("{}{}", directory, DEFAULT_PLAYER_BADGE_FILENAME)))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Default player badge '{}' failed to load",
DEFAULT_PLAYER_BADGE_FILENAME);
}
}
if (m_default_game_badge.data.empty())
{
if (!LoadPNGTexture(&m_default_game_badge,
fmt::format("{}{}", directory, DEFAULT_GAME_BADGE_FILENAME)))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Default game badge '{}' failed to load",
DEFAULT_GAME_BADGE_FILENAME);
}
}
if (m_default_unlocked_badge.data.empty())
{
if (!LoadPNGTexture(&m_default_unlocked_badge,
fmt::format("{}{}", directory, DEFAULT_UNLOCKED_BADGE_FILENAME)))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Default unlocked achievement badge '{}' failed to load",
DEFAULT_UNLOCKED_BADGE_FILENAME);
}
}
if (m_default_locked_badge.data.empty())
{
if (!LoadPNGTexture(&m_default_locked_badge,
fmt::format("{}{}", directory, DEFAULT_LOCKED_BADGE_FILENAME)))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Default locked achievement badge '{}' failed to load",
DEFAULT_LOCKED_BADGE_FILENAME);
}
}
}
void AchievementManager::LoginCallback(int result, const char* error_message, rc_client_t* client,
void* userdata)
{
@ -575,6 +804,8 @@ void AchievementManager::LeaderboardEntriesCallback(int result, const char* erro
map_entry.username.assign(response_entry.user);
memcpy(map_entry.score.data(), response_entry.display, FORMAT_SIZE);
map_entry.rank = response_entry.rank;
if (ix == list->user_index)
leaderboard.player_index = response_entry.rank;
}
AchievementManager::GetInstance().m_update_callback({.leaderboards = {*leaderboard_id}});
}
@ -582,6 +813,7 @@ void AchievementManager::LeaderboardEntriesCallback(int result, const char* erro
void AchievementManager::LoadGameCallback(int result, const char* error_message,
rc_client_t* client, void* userdata)
{
AchievementManager::GetInstance().m_loading_volume.reset(nullptr);
if (result != RC_OK)
{
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to load data for current game.");
@ -600,20 +832,40 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message,
}
INFO_LOG_FMT(ACHIEVEMENTS, "Loaded data for game ID {}.", game->id);
AchievementManager::GetInstance().m_display_welcome_message = true;
AchievementManager::GetInstance().FetchGameBadges();
AchievementManager::GetInstance().m_system = &Core::System::GetInstance();
AchievementManager::GetInstance().m_update_callback({.all = true});
auto& instance = AchievementManager::GetInstance();
rc_client_set_read_memory_function(instance.m_client, MemoryPeeker);
instance.m_display_welcome_message = true;
instance.FetchGameBadges();
instance.m_system.store(&Core::System::GetInstance(), std::memory_order_release);
instance.m_update_callback({.all = true});
// Set this to a value that will immediately trigger RP
AchievementManager::GetInstance().m_last_rp_time =
std::chrono::steady_clock::now() - std::chrono::minutes{2};
instance.m_last_rp_time = std::chrono::steady_clock::now() - std::chrono::minutes{2};
std::lock_guard lg{instance.GetLock()};
auto* leaderboard_list =
rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
for (u32 bucket = 0; bucket < leaderboard_list->num_buckets; bucket++)
{
const auto& leaderboard_bucket = leaderboard_list->buckets[bucket];
for (u32 board = 0; board < leaderboard_bucket.num_leaderboards; board++)
{
const auto& leaderboard = leaderboard_bucket.leaderboards[board];
instance.m_leaderboard_map.insert(
std::pair(leaderboard->id, LeaderboardStatus{.name = leaderboard->title,
.description = leaderboard->description}));
}
}
rc_client_destroy_leaderboard_list(leaderboard_list);
}
void AchievementManager::ChangeMediaCallback(int result, const char* error_message,
rc_client_t* client, void* userdata)
{
AchievementManager::GetInstance().m_loading_volume.reset(nullptr);
if (result == RC_OK)
{
return;
}
if (result == RC_HARDCORE_DISABLED)
{
@ -634,11 +886,8 @@ void AchievementManager::DisplayWelcomeMessage()
m_display_welcome_message = false;
const u32 color =
rc_client_get_hardcore_enabled(m_client) ? OSD::Color::YELLOW : OSD::Color::CYAN;
if (Config::Get(Config::RA_BADGES_ENABLED) && !m_game_badge.name.empty())
{
OSD::AddMessage("", OSD::Duration::VERY_LONG, OSD::Color::GREEN,
DecodeBadgeToOSDIcon(m_game_badge.badge));
}
OSD::AddMessage("", OSD::Duration::VERY_LONG, OSD::Color::GREEN, &GetGameBadge());
auto info = rc_client_get_game_info(m_client);
if (!info)
{
@ -668,17 +917,15 @@ void AchievementManager::DisplayWelcomeMessage()
void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t* client_event)
{
const auto& instance = AchievementManager::GetInstance();
OSD::AddMessage(fmt::format("Unlocked: {} ({})", client_event->achievement->title,
client_event->achievement->points),
OSD::Duration::VERY_LONG,
(rc_client_get_hardcore_enabled(AchievementManager::GetInstance().m_client)) ?
OSD::Color::YELLOW :
OSD::Color::CYAN,
(Config::Get(Config::RA_BADGES_ENABLED)) ?
DecodeBadgeToOSDIcon(AchievementManager::GetInstance()
.m_unlocked_badges[client_event->achievement->id]
.badge) :
nullptr);
(rc_client_get_hardcore_enabled(instance.m_client)) ? OSD::Color::YELLOW :
OSD::Color::CYAN,
&instance.GetAchievementBadge(client_event->achievement->id, false));
AchievementManager::GetInstance().m_update_callback(
UpdatedItems{.achievements = {client_event->achievement->id}});
}
void AchievementManager::HandleLeaderboardStartedEvent(const rc_client_event_t* client_event)
@ -703,6 +950,8 @@ void AchievementManager::HandleLeaderboardSubmittedEvent(const rc_client_event_t
client_event->leaderboard->title),
OSD::Duration::VERY_LONG, OSD::Color::YELLOW);
AchievementManager::GetInstance().FetchBoardInfo(client_event->leaderboard->id);
AchievementManager::GetInstance().m_update_callback(
UpdatedItems{.leaderboards = {client_event->leaderboard->id}});
}
void AchievementManager::HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* client_event)
@ -735,36 +984,38 @@ void AchievementManager::HandleLeaderboardTrackerHideEvent(const rc_client_event
void AchievementManager::HandleAchievementChallengeIndicatorShowEvent(
const rc_client_event_t* client_event)
{
if (Config::Get(Config::RA_BADGES_ENABLED))
{
auto& unlocked_badges = AchievementManager::GetInstance().m_unlocked_badges;
if (const auto unlocked_iter = unlocked_badges.find(client_event->achievement->id);
unlocked_iter != unlocked_badges.end())
{
AchievementManager::GetInstance().m_active_challenges[client_event->achievement->badge_name] =
DecodeBadgeToOSDIcon(unlocked_iter->second.badge);
}
}
auto& instance = AchievementManager::GetInstance();
const auto [iter, inserted] = instance.m_active_challenges.insert(client_event->achievement->id);
if (inserted)
instance.m_challenges_updated = true;
AchievementManager::GetInstance().m_update_callback(UpdatedItems{.rich_presence = true});
}
void AchievementManager::HandleAchievementChallengeIndicatorHideEvent(
const rc_client_event_t* client_event)
{
AchievementManager::GetInstance().m_active_challenges.erase(
client_event->achievement->badge_name);
auto& instance = AchievementManager::GetInstance();
const auto removed = instance.m_active_challenges.erase(client_event->achievement->id);
if (removed > 0)
instance.m_challenges_updated = true;
AchievementManager::GetInstance().m_update_callback(UpdatedItems{.rich_presence = true});
}
void AchievementManager::HandleAchievementProgressIndicatorShowEvent(
const rc_client_event_t* client_event)
{
auto& instance = AchievementManager::GetInstance();
auto current_time = std::chrono::steady_clock::now();
const auto message_wait_time = std::chrono::milliseconds{OSD::Duration::SHORT};
if (current_time - instance.m_last_progress_message < message_wait_time)
return;
OSD::AddMessage(fmt::format("{} {}", client_event->achievement->title,
client_event->achievement->measured_progress),
OSD::Duration::SHORT, OSD::Color::GREEN,
(Config::Get(Config::RA_BADGES_ENABLED)) ?
DecodeBadgeToOSDIcon(AchievementManager::GetInstance()
.m_unlocked_badges[client_event->achievement->id]
.badge) :
nullptr);
&instance.GetAchievementBadge(client_event->achievement->id, false));
instance.m_last_progress_message = current_time;
AchievementManager::GetInstance().m_update_callback(
UpdatedItems{.achievements = {client_event->achievement->id}});
}
void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* client_event,
@ -781,9 +1032,7 @@ void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* clien
OSD::AddMessage(fmt::format("Congratulations! {} has {} {}", user_info->display_name,
hardcore ? "mastered" : "completed", game_info->title),
OSD::Duration::VERY_LONG, hardcore ? OSD::Color::YELLOW : OSD::Color::CYAN,
(Config::Get(Config::RA_BADGES_ENABLED)) ?
DecodeBadgeToOSDIcon(AchievementManager::GetInstance().m_game_badge.badge) :
nullptr);
&AchievementManager::GetInstance().GetGameBadge());
}
void AchievementManager::HandleResetEvent(const rc_client_event_t* client_event)
@ -798,62 +1047,65 @@ void AchievementManager::HandleServerErrorEvent(const rc_client_event_t* client_
client_event->server_error->api, client_event->server_error->error_message);
}
static std::unique_ptr<OSD::Icon> DecodeBadgeToOSDIcon(const AchievementManager::Badge& badge)
{
if (badge.empty())
return nullptr;
auto icon = std::make_unique<OSD::Icon>();
if (!Common::LoadPNG(badge, &icon->rgba_data, &icon->width, &icon->height))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Error decoding badge.");
return nullptr;
}
return icon;
}
void AchievementManager::Request(const rc_api_request_t* request,
rc_client_server_callback_t callback, void* callback_data,
rc_client_t* client)
{
std::string url = request->url;
std::string post_data = request->post_data;
AchievementManager::GetInstance().m_queue.EmplaceItem([url = std::move(url),
post_data = std::move(post_data),
callback = std::move(callback),
callback_data = std::move(callback_data)] {
const Common::HttpRequest::Headers USER_AGENT_HEADER = {{"User-Agent", "Dolphin/Placeholder"}};
AchievementManager::GetInstance().m_queue.EmplaceItem(
[url = std::move(url), post_data = std::move(post_data), callback = std::move(callback),
callback_data = std::move(callback_data)] {
Common::HttpRequest http_request;
Common::HttpRequest::Response http_response;
if (!post_data.empty())
{
http_response = http_request.Post(url, post_data, USER_AGENT_HEADER,
Common::HttpRequest::AllowedReturnCodes::All);
}
else
{
http_response = http_request.Get(url, USER_AGENT_HEADER,
Common::HttpRequest::AllowedReturnCodes::All);
}
Common::HttpRequest http_request;
Common::HttpRequest::Response http_response;
if (!post_data.empty())
{
http_response = http_request.Post(url, post_data, USER_AGENT_HEADER,
Common::HttpRequest::AllowedReturnCodes::All);
}
else
{
http_response =
http_request.Get(url, USER_AGENT_HEADER, Common::HttpRequest::AllowedReturnCodes::All);
}
rc_api_server_response_t server_response;
if (http_response.has_value() && http_response->size() > 0)
{
server_response.body = reinterpret_cast<const char*>(http_response->data());
server_response.body_length = http_response->size();
server_response.http_status_code = http_request.GetLastResponseCode();
}
else
{
static constexpr char error_message[] = "Failed HTTP request.";
server_response.body = error_message;
server_response.body_length = sizeof(error_message);
server_response.http_status_code = RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR;
}
rc_api_server_response_t server_response;
if (http_response.has_value() && http_response->size() > 0)
{
server_response.body = reinterpret_cast<const char*>(http_response->data());
server_response.body_length = http_response->size();
server_response.http_status_code = http_request.GetLastResponseCode();
}
else
{
constexpr char error_message[] = "Failed HTTP request.";
server_response.body = error_message;
server_response.body_length = sizeof(error_message);
server_response.http_status_code = RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR;
}
callback(&server_response, callback_data);
});
}
callback(&server_response, callback_data);
});
// Currently, when rc_client calls the memory peek method provided in its constructor (or in
// rc_client_set_read_memory_function) it will do so on the thread that calls DoFrame, which is
// currently the host thread, with one exception: an asynchronous callback in the load game process.
// This is done to validate/invalidate each memory reference in the downloaded assets, mark assets
// as unsupported, and notify the player upon startup that there are unsupported assets and how
// many. As such, all that call needs to do is return the number of bytes that can be read with this
// call. As only the CPU and host threads are allowed to read from memory, I provide a separate
// method for this verification. In lieu of a more convenient set of steps, I provide MemoryVerifier
// to rc_client at construction, and in the Load Game callback, after the verification has been
// complete, I call rc_client_set_read_memory_function to switch to the usual MemoryPeeker for all
// future synchronous calls.
u32 AchievementManager::MemoryVerifier(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client)
{
auto& system = Core::System::GetInstance();
u32 ram_size = system.GetMemory().GetRamSizeReal();
if (address >= ram_size)
return 0;
return std::min(ram_size - address, num_bytes);
}
u32 AchievementManager::MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client)
@ -861,6 +1113,11 @@ u32 AchievementManager::MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_
if (buffer == nullptr)
return 0u;
auto& system = Core::System::GetInstance();
if (!(Core::IsHostThread() || Core::IsCPUThread()))
{
ASSERT_MSG(ACHIEVEMENTS, false, "MemoryPeeker called from wrong thread");
return 0;
}
Core::CPUThreadGuard threadguard(system);
for (u32 num_read = 0; num_read < num_bytes; num_read++)
{
@ -873,11 +1130,11 @@ u32 AchievementManager::MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_
return num_bytes;
}
void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32 badge_type,
void AchievementManager::FetchBadge(AchievementManager::Badge* badge, u32 badge_type,
const AchievementManager::BadgeNameFunction function,
const UpdatedItems callback_data)
{
if (!m_client || !HasAPIToken() || !Config::Get(Config::RA_BADGES_ENABLED))
if (!m_client || !HasAPIToken())
{
m_update_callback(callback_data);
if (m_display_welcome_message && badge_type == RC_IMAGE_TYPE_GAME)
@ -899,40 +1156,71 @@ void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32
if (name_to_fetch.empty())
return;
}
rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(),
.image_type = badge_type};
Badge fetched_badge;
rc_api_request_t api_request;
Common::HttpRequest http_request;
if (rc_api_init_fetch_image_request(&api_request, &icon_request) != RC_OK)
const std::string cache_path = fmt::format(
"{}/badge-{}-{}.png", File::GetUserPath(D_RETROACHIEVEMENTSCACHE_IDX), badge_type,
Common::SHA1::DigestToString(Common::SHA1::CalculateDigest(name_to_fetch)));
AchievementManager::Badge tmp_badge;
if (!LoadPNGTexture(&tmp_badge, cache_path))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid request for image {}.", name_to_fetch);
return;
}
auto http_response = http_request.Get(api_request.url);
if (http_response.has_value() && http_response->size() <= 0)
{
WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed on image request.\n URL: {}",
api_request.url);
rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(),
.image_type = badge_type};
Badge fetched_badge;
rc_api_request_t api_request;
Common::HttpRequest http_request;
if (rc_api_init_fetch_image_request(&api_request, &icon_request) != RC_OK)
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid request for image {}.", name_to_fetch);
return;
}
auto http_response = http_request.Get(api_request.url, USER_AGENT_HEADER,
Common::HttpRequest::AllowedReturnCodes::All);
if (!http_response.has_value() || http_response->empty())
{
WARN_LOG_FMT(ACHIEVEMENTS,
"RetroAchievements connection failed on image request.\n URL: {}",
api_request.url);
rc_api_destroy_request(&api_request);
m_update_callback(callback_data);
return;
}
rc_api_destroy_request(&api_request);
m_update_callback(callback_data);
return;
INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded badge id {}.", name_to_fetch);
if (!LoadPNGTexture(&tmp_badge, *http_response))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Badge '{}' failed to load", name_to_fetch);
return;
}
std::string temp_path = fmt::format("{}.tmp", cache_path);
File::IOFile temp_file(temp_path, "wb");
if (!temp_file.IsOpen() ||
!temp_file.WriteBytes(http_response->data(), http_response->size()) ||
!temp_file.Close() || !File::Rename(temp_path, cache_path))
{
File::Delete(temp_path);
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to store badge '{}' to cache", name_to_fetch);
}
}
rc_api_destroy_request(&api_request);
fetched_badge = std::move(*http_response);
INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded badge id {}.", name_to_fetch);
std::lock_guard lg{m_lock};
if (function(*this).empty() || name_to_fetch != function(*this))
{
INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {}.", name_to_fetch);
return;
}
badge->badge = std::move(fetched_badge);
badge->name = std::move(name_to_fetch);
*badge = std::move(tmp_badge);
m_update_callback(callback_data);
if (badge_type == RC_IMAGE_TYPE_ACHIEVEMENT &&
m_active_challenges.contains(*callback_data.achievements.begin()))
{
m_challenges_updated = true;
}
});
}

View file

@ -5,23 +5,34 @@
#ifdef USE_RETRO_ACHIEVEMENTS
#include <array>
#include <atomic>
#include <chrono>
#include <ctime>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <rcheevos/include/rc_api_runtime.h>
#include <rcheevos/include/rc_api_user.h>
#include <rcheevos/include/rc_client.h>
#include <rcheevos/include/rc_runtime.h>
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/HttpRequest.h"
#include "Common/JsonUtil.h"
#include "Common/Lazy.h"
#include "Common/WorkQueueThread.h"
#include "DiscIO/Volume.h"
#include "VideoCommon/Assets/CustomTextureData.h"
namespace Core
{
@ -29,10 +40,10 @@ class CPUThreadGuard;
class System;
} // namespace Core
namespace OSD
namespace PatchEngine
{
struct Icon;
}
struct Patch;
} // namespace PatchEngine
class AchievementManager
{
@ -47,19 +58,20 @@ public:
using LeaderboardRank = u32;
static constexpr size_t RP_SIZE = 256;
using RichPresence = std::array<char, RP_SIZE>;
using Badge = std::vector<u8>;
using NamedIconMap = std::map<std::string, std::unique_ptr<OSD::Icon>, std::less<>>;
using Badge = VideoCommon::CustomTextureData::ArraySlice::Level;
static constexpr size_t MAX_DISPLAYED_LBOARDS = 4;
struct BadgeStatus
{
std::string name = "";
Badge badge{};
};
static constexpr std::string_view DEFAULT_PLAYER_BADGE_FILENAME = "achievements_player.png";
static constexpr std::string_view DEFAULT_GAME_BADGE_FILENAME = "achievements_game.png";
static constexpr std::string_view DEFAULT_LOCKED_BADGE_FILENAME = "achievements_locked.png";
static constexpr std::string_view DEFAULT_UNLOCKED_BADGE_FILENAME = "achievements_unlocked.png";
static constexpr std::string_view GRAY = "transparent";
static constexpr std::string_view GOLD = "#FFD700";
static constexpr std::string_view BLUE = "#0B71C1";
static constexpr std::string_view APPROVED_LIST_FILENAME = "ApprovedInis.json";
static const inline Common::SHA1::Digest APPROVED_LIST_HASH = {
0x50, 0x2F, 0x58, 0x02, 0x94, 0x60, 0x1B, 0x9F, 0x92, 0xC7,
0x04, 0x17, 0x50, 0x2E, 0xF3, 0x09, 0x8C, 0x8C, 0xD6, 0xC0};
struct LeaderboardEntry
{
@ -96,27 +108,36 @@ public:
bool HasAPIToken() const;
void LoadGame(const std::string& file_path, const DiscIO::Volume* volume);
bool IsGameLoaded() const;
void SetBackgroundExecutionAllowed(bool allowed);
void FetchPlayerBadge();
void FetchGameBadges();
void DoFrame();
bool CanPause();
void DoIdle();
std::recursive_mutex& GetLock();
void SetHardcoreMode();
bool IsHardcoreModeActive() const;
void SetGameIniId(const std::string& game_ini_id) { m_game_ini_id = game_ini_id; }
void FilterApprovedPatches(std::vector<PatchEngine::Patch>& patches,
const std::string& game_ini_id) const;
void SetSpectatorMode();
std::string_view GetPlayerDisplayName() const;
u32 GetPlayerScore() const;
const BadgeStatus& GetPlayerBadge() const;
const Badge& GetPlayerBadge() const;
std::string_view GetGameDisplayName() const;
rc_client_t* GetClient();
rc_api_fetch_game_data_response_t* GetGameData();
const BadgeStatus& GetGameBadge() const;
const BadgeStatus& GetAchievementBadge(AchievementId id, bool locked) const;
const Badge& GetGameBadge() const;
const Badge& GetAchievementBadge(AchievementId id, bool locked) const;
const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id);
RichPresence GetRichPresence() const;
const NamedIconMap& GetChallengeIcons() const;
bool AreChallengesUpdated() const;
void ResetChallengesUpdated();
const std::unordered_set<AchievementId>& GetActiveChallenges() const;
std::vector<std::string> GetActiveLeaderboards() const;
void DoState(PointerWrap& p);
@ -134,7 +155,7 @@ private:
std::unique_ptr<DiscIO::Volume> volume;
};
const BadgeStatus m_default_badge;
static picojson::value LoadApprovedList();
static void* FilereaderOpenByFilepath(const char* path_utf8);
static void* FilereaderOpenByVolume(const char* path_utf8);
@ -143,12 +164,13 @@ private:
static size_t FilereaderRead(void* file_handle, void* buffer, size_t requested_bytes);
static void FilereaderClose(void* file_handle);
void LoadDefaultBadges();
static void LoginCallback(int result, const char* error_message, rc_client_t* client,
void* userdata);
void FetchBoardInfo(AchievementId leaderboard_id);
std::unique_ptr<DiscIO::Volume>& GetLoadingVolume() { return m_loading_volume; };
std::unique_ptr<DiscIO::Volume>& GetLoadingVolume() { return m_loading_volume; }
static void LoadGameCallback(int result, const char* error_message, rc_client_t* client,
void* userdata);
@ -176,31 +198,42 @@ private:
static void Request(const rc_api_request_t* request, rc_client_server_callback_t callback,
void* callback_data, rc_client_t* client);
static u32 MemoryVerifier(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client);
static u32 MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client);
void FetchBadge(BadgeStatus* badge, u32 badge_type, const BadgeNameFunction function,
void FetchBadge(Badge* badge, u32 badge_type, const BadgeNameFunction function,
const UpdatedItems callback_data);
static void EventHandler(const rc_client_event_t* event, rc_client_t* client);
rc_runtime_t m_runtime{};
rc_client_t* m_client{};
Core::System* m_system{};
std::atomic<Core::System*> m_system{};
bool m_is_runtime_initialized = false;
UpdateCallback m_update_callback = [](const UpdatedItems&) {};
std::unique_ptr<DiscIO::Volume> m_loading_volume;
BadgeStatus m_player_badge;
Badge m_default_player_badge;
Badge m_default_game_badge;
Badge m_default_unlocked_badge;
Badge m_default_locked_badge;
std::atomic_bool m_background_execution_allowed = true;
Badge m_player_badge;
Hash m_game_hash{};
u32 m_game_id = 0;
rc_api_fetch_game_data_response_t m_game_data{};
bool m_is_game_loaded = false;
BadgeStatus m_game_badge;
Badge m_game_badge;
bool m_display_welcome_message = false;
std::unordered_map<AchievementId, BadgeStatus> m_unlocked_badges;
std::unordered_map<AchievementId, BadgeStatus> m_locked_badges;
std::unordered_map<AchievementId, Badge> m_unlocked_badges;
std::unordered_map<AchievementId, Badge> m_locked_badges;
RichPresence m_rich_presence;
std::chrono::steady_clock::time_point m_last_rp_time = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point m_last_progress_message = std::chrono::steady_clock::now();
Common::Lazy<picojson::value> m_ini_root{LoadApprovedList};
std::string m_game_ini_id;
std::unordered_map<AchievementId, LeaderboardStatus> m_leaderboard_map;
NamedIconMap m_active_challenges;
bool m_challenges_updated = false;
std::unordered_set<AchievementId> m_active_challenges;
std::vector<rc_client_leaderboard_tracker_t> m_active_leaderboards;
Common::WorkQueueThread<std::function<void()>> m_queue;
@ -209,4 +242,33 @@ private:
std::recursive_mutex m_filereader_lock;
}; // class AchievementManager
#else // USE_RETRO_ACHIEVEMENTS
#include <string>
namespace DiscIO
{
class Volume;
}
class AchievementManager
{
public:
static AchievementManager& GetInstance()
{
static AchievementManager s_instance;
return s_instance;
}
constexpr bool IsHardcoreModeActive() { return false; }
constexpr void LoadGame(const std::string&, const DiscIO::Volume*) {}
constexpr void SetBackgroundExecutionAllowed(bool allowed) {}
constexpr void DoFrame() {}
constexpr void CloseGame() {}
};
#endif // USE_RETRO_ACHIEVEMENTS

View file

@ -18,7 +18,7 @@ class IniFile;
namespace Core
{
class CPUThreadGuard;
};
}
namespace ActionReplay
{

View file

@ -235,7 +235,7 @@ std::unique_ptr<BootParameters> BootParameters::GenerateFromFile(std::vector<std
static const std::unordered_set<std::string> disc_image_extensions = {
{".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".wia", ".rvz", ".nfs", ".dol", ".elf"}};
if (disc_image_extensions.find(extension) != disc_image_extensions.end())
if (disc_image_extensions.contains(extension))
{
std::unique_ptr<DiscIO::VolumeDisc> disc = DiscIO::CreateDisc(path);
if (disc)
@ -575,9 +575,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
SetupGCMemory(system, guard);
}
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance().LoadGame(executable.path, nullptr);
#endif // USE_RETRO_ACHIEVEMENTS
if (!executable.reader->LoadIntoMemory(system))
{

View file

@ -24,7 +24,7 @@ public:
bool IsValid() const override { return m_is_valid; }
bool IsWii() const override { return m_is_wii; }
bool IsAncast() const { return m_is_ancast; };
bool IsAncast() const { return m_is_ancast; }
u32 GetEntryPoint() const override { return m_dolheader.entryPoint; }
bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const override;
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db) const override

View file

@ -166,9 +166,7 @@ bool BootCore(Core::System& system, std::unique_ptr<BootParameters> boot,
}
}
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance().CloseGame();
#endif // USE_RETRO_ACHIEVEMENTS
const bool load_ipl = !system.IsWii() && !Config::Get(Config::MAIN_SKIP_IPL) &&
std::holds_alternative<BootParameters::Disc>(boot->parameters);
@ -211,8 +209,7 @@ static void RestoreSYSCONF()
},
setting.config_info);
}
// Save the SYSCONF.
Config::GetLayer(Config::LayerType::Base)->Save();
ConfigLoaders::SaveToSYSCONF(Config::LayerType::Base);
}
void RestoreConfig()

View file

@ -485,8 +485,10 @@ add_library(core
PowerPC/BreakPoints.h
PowerPC/CachedInterpreter/CachedInterpreter.cpp
PowerPC/CachedInterpreter/CachedInterpreter.h
PowerPC/CachedInterpreter/InterpreterBlockCache.cpp
PowerPC/CachedInterpreter/InterpreterBlockCache.h
PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp
PowerPC/CachedInterpreter/CachedInterpreterBlockCache.h
PowerPC/CachedInterpreter/CachedInterpreterEmitter.cpp
PowerPC/CachedInterpreter/CachedInterpreterEmitter.h
PowerPC/ConditionRegister.cpp
PowerPC/ConditionRegister.h
PowerPC/Expression.cpp
@ -783,7 +785,7 @@ if(MSVC)
endif()
if(USE_RETRO_ACHIEVEMENTS)
target_link_libraries(core PRIVATE rcheevos)
target_compile_definitions(core PRIVATE -DUSE_RETRO_ACHIEVEMENTS)
target_compile_definitions(core PRIVATE -DRC_CLIENT_SUPPORTS_HASH)
target_link_libraries(core PUBLIC rcheevos)
target_compile_definitions(core PUBLIC -DUSE_RETRO_ACHIEVEMENTS)
target_compile_definitions(core PUBLIC -DRC_CLIENT_SUPPORTS_HASH)
endif()

View file

@ -38,7 +38,7 @@ void OnConfigChanged()
}
}
}; // namespace
} // namespace
namespace CPUThreadConfigCallback
{
@ -73,4 +73,4 @@ void CheckForConfigChanges()
RunCallbacks();
}
}; // namespace CPUThreadConfigCallback
} // namespace CPUThreadConfigCallback

View file

@ -27,4 +27,4 @@ void RemoveConfigChangedCallback(ConfigChangedCallbackID callback_id);
// Should be called regularly from the CPU thread
void CheckForConfigChanges();
}; // namespace CPUThreadConfigCallback
} // namespace CPUThreadConfigCallback

View file

@ -207,10 +207,8 @@ Cheats::NewSearch(const Core::CPUThreadGuard& guard,
PowerPC::RequestedAddressSpace address_space, bool aligned,
const std::function<bool(const T& value)>& validator)
{
#ifdef USE_RETRO_ACHIEVEMENTS
if (AchievementManager::GetInstance().IsHardcoreModeActive())
return Cheats::SearchErrorCode::DisabledInHardcoreMode;
#endif // USE_RETRO_ACHIEVEMENTS
auto& system = guard.GetSystem();
std::vector<Cheats::SearchResult<T>> results;
const Core::State core_state = Core::GetState(system);
@ -262,10 +260,8 @@ Cheats::NextSearch(const Core::CPUThreadGuard& guard,
PowerPC::RequestedAddressSpace address_space,
const std::function<bool(const T& new_value, const T& old_value)>& validator)
{
#ifdef USE_RETRO_ACHIEVEMENTS
if (AchievementManager::GetInstance().IsHardcoreModeActive())
return Cheats::SearchErrorCode::DisabledInHardcoreMode;
#endif // USE_RETRO_ACHIEVEMENTS
auto& system = guard.GetSystem();
std::vector<Cheats::SearchResult<T>> results;
const Core::State core_state = Core::GetState(system);
@ -429,10 +425,8 @@ MakeCompareFunctionForLastValue(Cheats::CompareType op)
template <typename T>
Cheats::SearchErrorCode Cheats::CheatSearchSession<T>::RunSearch(const Core::CPUThreadGuard& guard)
{
#ifdef USE_RETRO_ACHIEVEMENTS
if (AchievementManager::GetInstance().IsHardcoreModeActive())
return Cheats::SearchErrorCode::DisabledInHardcoreMode;
#endif // USE_RETRO_ACHIEVEMENTS
Common::Result<SearchErrorCode, std::vector<SearchResult<T>>> result =
Cheats::SearchErrorCode::InvalidParameters;
if (m_filter_type == FilterType::CompareAgainstSpecificValue)

View file

@ -17,7 +17,7 @@
namespace Core
{
class CPUThreadGuard;
};
}
namespace Cheats
{
@ -100,10 +100,8 @@ enum class SearchErrorCode
// currently off in the emulated game.
VirtualAddressesCurrentlyNotAccessible,
#ifdef USE_RETRO_ACHIEVEMENTS
// Cheats and memory reading are disabled in RetroAchievements hardcore mode.
DisabledInHardcoreMode,
#endif // USE_RETRO_ACHIEVEMENTS
};
// Returns the corresponding DataType enum for the value currently held by the given SearchValue.

View file

@ -22,9 +22,10 @@ const Info<bool> RA_UNOFFICIAL_ENABLED{{System::Achievements, "Achievements", "U
const Info<bool> RA_ENCORE_ENABLED{{System::Achievements, "Achievements", "EncoreEnabled"}, false};
const Info<bool> RA_SPECTATOR_ENABLED{{System::Achievements, "Achievements", "SpectatorEnabled"},
false};
const Info<bool> RA_DISCORD_PRESENCE_ENABLED{
{System::Achievements, "Achievements", "DiscordPresenceEnabled"}, false};
const Info<bool> RA_PROGRESS_ENABLED{{System::Achievements, "Achievements", "ProgressEnabled"},
false};
const Info<bool> RA_BADGES_ENABLED{{System::Achievements, "Achievements", "BadgesEnabled"}, false};
} // namespace Config
#endif // USE_RETRO_ACHIEVEMENTS

View file

@ -18,8 +18,8 @@ extern const Info<bool> RA_HARDCORE_ENABLED;
extern const Info<bool> RA_UNOFFICIAL_ENABLED;
extern const Info<bool> RA_ENCORE_ENABLED;
extern const Info<bool> RA_SPECTATOR_ENABLED;
extern const Info<bool> RA_DISCORD_PRESENCE_ENABLED;
extern const Info<bool> RA_PROGRESS_ENABLED;
extern const Info<bool> RA_BADGES_ENABLED;
} // namespace Config
#endif // USE_RETRO_ACHIEVEMENTS

View file

@ -749,22 +749,14 @@ bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot)
bool AreCheatsEnabled()
{
#ifdef USE_RETRO_ACHIEVEMENTS
return Config::Get(::Config::MAIN_ENABLE_CHEATS) &&
!AchievementManager::GetInstance().IsHardcoreModeActive();
#else // USE_RETRO_ACHIEVEMENTS
return Config::Get(::Config::MAIN_ENABLE_CHEATS);
#endif // USE_RETRO_ACHIEVEMENTS
}
bool IsDebuggingEnabled()
{
#ifdef USE_RETRO_ACHIEVEMENTS
return Config::Get(::Config::MAIN_ENABLE_DEBUGGING) &&
!AchievementManager::GetInstance().IsHardcoreModeActive();
#else // USE_RETRO_ACHIEVEMENTS
return Config::Get(::Config::MAIN_ENABLE_DEBUGGING);
#endif // USE_RETRO_ACHIEVEMENTS
}
} // namespace Config

View file

@ -15,7 +15,7 @@ const Info<u32> SYSCONF_LANGUAGE{{System::SYSCONF, "IPL", "LNG"},
const Info<u32> SYSCONF_COUNTRY{{System::SYSCONF, "IPL", "SADR"}, GetDefaultCountry()};
const Info<bool> SYSCONF_WIDESCREEN{{System::SYSCONF, "IPL", "AR"}, true};
const Info<bool> SYSCONF_PROGRESSIVE_SCAN{{System::SYSCONF, "IPL", "PGS"}, true};
const Info<bool> SYSCONF_PAL60{{System::SYSCONF, "IPL", "E60"}, 0x01};
const Info<bool> SYSCONF_PAL60{{System::SYSCONF, "IPL", "E60"}, true};
const Info<u32> SYSCONF_SOUND_MODE{{System::SYSCONF, "IPL", "SND"}, 0x01};
// SYSCONF.BT

View file

@ -29,6 +29,7 @@
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Common/Version.h"
#include "Common/Config/Config.h"
#include "Core/AchievementManager.h"
#include "Core/Boot/Boot.h"
@ -86,6 +87,11 @@ SConfig::~SConfig()
SaveSettings();
}
namespace Config
{
const Info<int> MAIN_CODE_HANDLER{{System::Main, "CodeHandler", "CodeHandlerValue"}, 0};
}
void SConfig::SaveSettings()
{
NOTICE_LOG_FMT(BOOT, "Saving settings to {}", File::GetUserPath(F_DOLPHINCONFIG_IDX));
@ -169,11 +175,6 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri
if (!was_changed)
return;
#ifdef USE_RETRO_ACHIEVEMENTS
if (game_id != "00000000")
AchievementManager::GetInstance().CloseGame();
#endif // USE_RETRO_ACHIEVEMENTS
if (game_id == "00000000")
{
m_title_name.clear();
@ -181,6 +182,8 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri
return;
}
AchievementManager::GetInstance().CloseGame();
const Core::TitleDatabase title_database;
auto& system = Core::System::GetInstance();
const DiscIO::Language language = GetLanguageAdjustedForRegion(system.IsWii(), region);
@ -188,22 +191,22 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri
m_title_description = title_database.Describe(m_gametdb_id, language);
NOTICE_LOG_FMT(CORE, "Active title: {}", m_title_description);
Host_TitleChanged();
if (Core::IsRunning(system))
{
const bool is_running_or_starting = Core::IsRunningOrStarting(system);
if (is_running_or_starting)
Core::UpdateTitle(system);
}
Config::AddLayer(ConfigLoaders::GenerateGlobalGameConfigLoader(game_id, revision));
Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision));
if (Core::IsRunning(system))
if (is_running_or_starting)
DolphinAnalytics::Instance().ReportGameStart();
}
void SConfig::OnNewTitleLoad(const Core::CPUThreadGuard& guard)
{
auto& system = guard.GetSystem();
if (!Core::IsRunning(system))
if (!Core::IsRunningOrStarting(system))
return;
auto& ppc_symbol_db = system.GetPPCSymbolDB();
@ -454,7 +457,14 @@ std::string SConfig::GetGameTDBImageRegionCode(bool wii, DiscIO::Region region)
switch (region)
{
case DiscIO::Region::NTSC_J:
{
// Taiwanese games share the Japanese region code however their title ID ends with 'W'.
// GameTDB differentiates the covers using the code "ZH".
if (m_game_id.size() >= 4 && m_game_id.at(3) == 'W')
return "ZH";
return "JA";
}
case DiscIO::Region::NTSC_U:
return "US";
case DiscIO::Region::NTSC_K:

View file

@ -13,6 +13,7 @@
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Common/Config/Config.h"
namespace Common
{
@ -33,7 +34,11 @@ enum class Region;
struct Partition;
class Volume;
} // namespace DiscIO
namespace Config
{
// Define the configuration option
extern const Info<int> MAIN_CODE_HANDLER;
} // namespace Config
namespace IOS::ES
{
class TMDReader;
@ -45,7 +50,7 @@ struct SConfig
{
// Settings
bool bBootToPause = false;
bool bJITNoBlockCache = false;
bool bJITNoBlockLinking = false;

View file

@ -101,10 +101,6 @@ namespace Core
static bool s_wants_determinism;
// Declarations and definitions
static bool s_is_stopping = false;
static bool s_hardware_initialized = false;
static bool s_is_started = false;
static Common::Flag s_is_booting;
static std::thread s_emu_thread;
static std::vector<StateChangedCallbackFunc> s_on_state_changed_callbacks;
@ -114,6 +110,10 @@ static std::atomic<double> s_last_actual_emulation_speed{1.0};
static bool s_frame_step = false;
static std::atomic<bool> s_stop_frame_step;
// The value Paused is never stored in this variable. The core is considered to be in
// the Paused state if this variable is Running and the CPU reports that it's stepping.
static std::atomic<State> s_state = State::Uninitialized;
#ifdef USE_MEMORYWATCHER
static std::unique_ptr<MemoryWatcher> s_memory_watcher;
#endif
@ -190,7 +190,7 @@ std::string StopMessage(bool main_thread, std::string_view message)
void DisplayMessage(std::string message, int time_in_ms)
{
if (!IsRunning(Core::System::GetInstance()))
if (!IsRunningOrStarting(Core::System::GetInstance()))
return;
// Actually displaying non-ASCII could cause things to go pear-shaped
@ -202,12 +202,13 @@ void DisplayMessage(std::string message, int time_in_ms)
bool IsRunning(Core::System& system)
{
return (GetState(system) != State::Uninitialized || s_hardware_initialized) && !s_is_stopping;
return s_state.load() == State::Running;
}
bool IsRunningAndStarted()
bool IsRunningOrStarting(Core::System& system)
{
return s_is_started && !s_is_stopping;
const State state = s_state.load();
return state == State::Running || state == State::Starting;
}
bool IsCPUThread()
@ -262,7 +263,7 @@ bool Init(Core::System& system, std::unique_ptr<BootParameters> boot, const Wind
g_video_backend->PrepareWindow(prepared_wsi);
// Start the emu thread
s_is_booting.Set();
s_state.store(State::Starting);
s_emu_thread = std::thread(EmuThread, std::ref(system), std::move(boot), prepared_wsi);
return true;
}
@ -281,17 +282,13 @@ static void ResetRumble()
// Called from GUI thread
void Stop(Core::System& system) // - Hammertime!
{
if (const State state = GetState(system);
state == State::Stopping || state == State::Uninitialized)
{
const State state = s_state.load();
if (state == State::Stopping || state == State::Uninitialized)
return;
}
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance().CloseGame();
#endif // USE_RETRO_ACHIEVEMENTS
s_is_stopping = true;
s_state.store(State::Stopping);
CallOnStateChangedCallbacks(State::Stopping);
@ -356,7 +353,7 @@ static void CPUSetInitialExecutionState(bool force_paused = false)
// SetState must be called on the host thread, so we defer it for later.
QueueHostJob([force_paused](Core::System& system) {
bool paused = SConfig::GetInstance().bBootToPause || force_paused;
SetState(system, paused ? State::Paused : State::Running);
SetState(system, paused ? State::Paused : State::Running, true, true);
Host_UpdateDisasmDialog();
Host_UpdateMainFrame();
Host_Message(HostMessageID::WMUserCreate);
@ -396,11 +393,15 @@ static void CpuThread(Core::System& system, const std::optional<std::string>& sa
File::Delete(*savestate_path);
}
s_is_started = true;
// If s_state is Starting, change it to Running. But if it's already been set to Stopping
// by the host thread, don't change it.
State expected = State::Starting;
s_state.compare_exchange_strong(expected, State::Running);
{
#ifndef _WIN32
std::string gdb_socket = Config::Get(Config::MAIN_GDB_SOCKET);
if (!gdb_socket.empty())
if (!gdb_socket.empty() && !AchievementManager::GetInstance().IsHardcoreModeActive())
{
GDBStub::InitLocal(gdb_socket.data());
CPUSetInitialExecutionState(true);
@ -409,7 +410,7 @@ static void CpuThread(Core::System& system, const std::optional<std::string>& sa
#endif
{
int gdb_port = Config::Get(Config::MAIN_GDB_PORT);
if (gdb_port > 0)
if (gdb_port > 0 && !AchievementManager::GetInstance().IsHardcoreModeActive())
{
GDBStub::Init(gdb_port);
CPUSetInitialExecutionState(true);
@ -428,8 +429,6 @@ static void CpuThread(Core::System& system, const std::optional<std::string>& sa
s_memory_watcher.reset();
#endif
s_is_started = false;
if (exception_handler)
EMM::UninstallExceptionHandler();
@ -455,12 +454,15 @@ static void FifoPlayerThread(Core::System& system, const std::optional<std::stri
if (auto cpu_core = system.GetFifoPlayer().GetCPUCore())
{
system.GetPowerPC().InjectExternalCPUCore(cpu_core.get());
s_is_started = true;
// If s_state is Starting, change it to Running. But if it's already been set to Stopping
// by the host thread, don't change it.
State expected = State::Starting;
s_state.compare_exchange_strong(expected, State::Running);
CPUSetInitialExecutionState();
system.GetCPU().Run();
s_is_started = false;
system.GetPowerPC().InjectExternalCPUCore(nullptr);
system.GetFifoPlayer().Close();
}
@ -481,10 +483,7 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
{
CallOnStateChangedCallbacks(State::Starting);
Common::ScopeGuard flag_guard{[] {
s_is_booting.Clear();
s_is_started = false;
s_is_stopping = false;
s_wants_determinism = false;
s_state.store(State::Uninitialized);
CallOnStateChangedCallbacks(State::Uninitialized);
@ -562,8 +561,6 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
NetPlay::NetPlay_RegisterEvents();
Common::ScopeGuard hw_guard{[&system] {
// We must set up this flag before executing HW::Shutdown()
s_hardware_initialized = false;
INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "Shutting down HW"));
HW::Shutdown(system);
INFO_LOG_FMT(CONSOLE, "{}", StopMessage(false, "HW shutdown"));
@ -607,10 +604,6 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
AudioCommon::PostInitSoundStream(system);
// The hardware is initialized.
s_hardware_initialized = true;
s_is_booting.Clear();
// Set execution state to known values (CPU/FIFO/Audio Paused)
system.GetCPU().Break();
@ -703,24 +696,32 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
// Set or get the running state
void SetState(Core::System& system, State state, bool report_state_change)
void SetState(Core::System& system, State state, bool report_state_change,
bool initial_execution_state)
{
// State cannot be controlled until the CPU Thread is operational
if (!IsRunningAndStarted())
if (s_state.load() != State::Running)
return;
switch (state)
{
case State::Paused:
#ifdef USE_RETRO_ACHIEVEMENTS
if (!initial_execution_state && !AchievementManager::GetInstance().CanPause())
return;
#endif // USE_RETRO_ACHIEVEMENTS
// NOTE: GetState() will return State::Paused immediately, even before anything has
// stopped (including the CPU).
system.GetCPU().EnableStepping(true); // Break
system.GetCPU().SetStepping(true); // Break
Wiimote::Pause();
ResetRumble();
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance().DoIdle();
#endif // USE_RETRO_ACHIEVEMENTS
break;
case State::Running:
{
system.GetCPU().EnableStepping(false);
system.GetCPU().SetStepping(false);
Wiimote::Resume();
break;
}
@ -737,21 +738,11 @@ void SetState(Core::System& system, State state, bool report_state_change)
State GetState(Core::System& system)
{
if (s_is_stopping)
return State::Stopping;
if (s_hardware_initialized)
{
if (system.GetCPU().IsStepping())
return State::Paused;
return State::Running;
}
if (s_is_booting.IsSet())
return State::Starting;
return State::Uninitialized;
const State state = s_state.load();
if (state == State::Running && system.GetCPU().IsStepping())
return State::Paused;
else
return state;
}
static std::string GenerateScreenshotFolderPath()
@ -805,7 +796,7 @@ static bool PauseAndLock(Core::System& system, bool do_lock, bool unpause_on_unl
{
// WARNING: PauseAndLock is not fully threadsafe so is only valid on the Host Thread
if (!IsRunningAndStarted())
if (!IsRunning(system))
return true;
bool was_unpaused = true;
@ -833,7 +824,7 @@ static bool PauseAndLock(Core::System& system, bool do_lock, bool unpause_on_unl
// The CPU is responsible for managing the Audio and FIFO state so we use its
// mechanism to unpause them. If we unpaused the systems above when releasing
// the locks then they could call CPU::Break which would require detecting it
// and re-pausing with CPU::EnableStepping.
// and re-pausing with CPU::SetStepping.
was_unpaused = system.GetCPU().PauseAndLock(false, unpause_on_unlock, true);
}
@ -911,9 +902,7 @@ void Callback_NewField(Core::System& system)
}
}
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance().DoFrame();
#endif // USE_RETRO_ACHIEVEMENTS
}
void UpdateTitle(Core::System& system)
@ -1034,13 +1023,12 @@ void HostDispatchJobs(Core::System& system)
HostJob job = std::move(s_host_jobs_queue.front());
s_host_jobs_queue.pop();
// NOTE: Memory ordering is important. The booting flag needs to be
// checked first because the state transition is:
// Core::State::Uninitialized: s_is_booting -> s_hardware_initialized
// We need to check variables in the same order as the state
// transition, otherwise we race and get transient failures.
if (!job.run_after_stop && !s_is_booting.IsSet() && !IsRunning(system))
continue;
if (!job.run_after_stop)
{
const State state = s_state.load();
if (state == State::Stopping || state == State::Uninitialized)
continue;
}
guard.unlock();
job.job(system);
@ -1051,13 +1039,11 @@ void HostDispatchJobs(Core::System& system)
// NOTE: Host Thread
void DoFrameStep(Core::System& system)
{
#ifdef USE_RETRO_ACHIEVEMENTS
if (AchievementManager::GetInstance().IsHardcoreModeActive())
{
OSD::AddMessage("Frame stepping is disabled in RetroAchievements hardcore mode");
return;
}
#endif // USE_RETRO_ACHIEVEMENTS
if (GetState(system) == State::Paused)
{
// if already paused, frame advance for 1 frame
@ -1076,7 +1062,8 @@ void UpdateInputGate(bool require_focus, bool require_full_focus)
{
// If the user accepts background input, controls should pass even if an on screen interface is on
const bool focus_passes =
!require_focus || (Host_RendererHasFocus() && !Host_UIBlocksControllerState());
!require_focus ||
((Host_RendererHasFocus() || Host_TASInputHasFocus()) && !Host_UIBlocksControllerState());
// Ignore full focus if we don't require basic focus
const bool full_focus_passes =
!require_focus || !require_full_focus || (focus_passes && Host_RendererHasFullFocus());
@ -1096,4 +1083,31 @@ CPUThreadGuard::~CPUThreadGuard()
PauseAndLock(m_system, false, m_was_unpaused);
}
static GameName mGameBeingPlayed = GameName::UnknownGame;
const std::map<std::string, GameName> mGameMap = {{"GMPE01", GameName::MarioParty4},
{"GP5E01", GameName::MarioParty5},
{"GP6E01", GameName::MarioParty6},
{"GP7E01", GameName::MarioParty7},
{"RM8E01", GameName::MarioParty8}};
std::optional<std::pair<u32,u32>> getGameFreeMemory()
{
switch (mGameBeingPlayed) {
case GameName::MarioParty4:
return std::nullopt;
case GameName::MarioParty5:
return std::make_pair(0x801A811C, 0x801A9B5C);
case GameName::MarioParty6:
return std::make_pair(0x80213974, 0x80216014);
case GameName::MarioParty7:
return std::make_pair(0x8023CF6C, 0x8023F9D0);
case GameName::MarioParty8:
return std::make_pair(0x802D5100, 0x802D9500);
case GameName::UnknownGame:
return std::nullopt;
default:
return std::nullopt;
}
}
} // namespace Core

View file

@ -135,15 +135,16 @@ void UndeclareAsHostThread();
std::string StopMessage(bool main_thread, std::string_view message);
bool IsRunning(Core::System& system);
bool IsRunningAndStarted(); // is running and the CPU loop has been entered
bool IsCPUThread(); // this tells us whether we are the CPU thread.
bool IsRunningOrStarting(Core::System& system);
bool IsCPUThread(); // this tells us whether we are the CPU thread.
bool IsGPUThread();
bool IsHostThread();
bool WantsDeterminism();
// [NOT THREADSAFE] For use by Host only
void SetState(Core::System& system, State state, bool report_state_change = true);
void SetState(Core::System& system, State state, bool report_state_change = true,
bool initial_execution_state = false);
State GetState(Core::System& system);
void SaveScreenShot();
@ -191,4 +192,14 @@ void UpdateInputGate(bool require_focus, bool require_full_focus = false);
void UpdateTitle(Core::System& system);
enum class GameName : u8 {
UnknownGame = 0,
MarioParty4 = 1,
MarioParty5 = 2,
MarioParty6 = 3,
MarioParty7 = 4,
MarioParty8 = 5
};
std::optional<std::pair<u32,u32>> getGameFreeMemory();
} // namespace Core

View file

@ -73,7 +73,7 @@ EventType* CoreTimingManager::RegisterEvent(const std::string& name, TimedCallba
{
// check for existing type with same name.
// we want event type names to remain unique so that we can use them for serialization.
ASSERT_MSG(POWERPC, m_event_types.find(name) == m_event_types.end(),
ASSERT_MSG(POWERPC, !m_event_types.contains(name),
"CoreTiming Event \"{}\" is already registered. Events should only be registered "
"during Init to avoid breaking save states.",
name);
@ -138,7 +138,6 @@ void CoreTimingManager::RefreshConfig()
m_max_variance = std::chrono::duration_cast<DT>(DT_ms(Config::Get(Config::MAIN_TIMING_VARIANCE)));
#ifdef USE_RETRO_ACHIEVEMENTS
if (AchievementManager::GetInstance().IsHardcoreModeActive() &&
Config::Get(Config::MAIN_EMULATION_SPEED) < 1.0f &&
Config::Get(Config::MAIN_EMULATION_SPEED) > 0.0f)
@ -147,7 +146,6 @@ void CoreTimingManager::RefreshConfig()
m_emulation_speed = 1.0f;
OSD::AddMessage("Minimum speed is 100% in Hardcore Mode");
}
#endif // USE_RETRO_ACHIEVEMENTS
m_emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED);
}
@ -305,13 +303,12 @@ void CoreTimingManager::ScheduleExternalEvent(u64 timepoint, EventType* event_ty
void CoreTimingManager::RemoveEvent(EventType* event_type)
{
auto itr = std::remove_if(m_event_queue.begin(), m_event_queue.end(),
[&](const Event& e) { return e.type == event_type; });
const size_t erased =
std::erase_if(m_event_queue, [&](const Event& e) { return e.type == event_type; });
// Removing random items breaks the invariant so we have to re-establish it.
if (itr != m_event_queue.end())
if (erased != 0)
{
m_event_queue.erase(itr, m_event_queue.end());
std::make_heap(m_event_queue.begin(), m_event_queue.end(), std::greater<Event>());
}
}

View file

@ -142,7 +142,7 @@ public:
m_collection_pf.size();
}
std::size_t GetBlacklistSize() const { return m_blacklist_size; }
Phase GetRecordingPhase() const { return m_recording_phase; };
Phase GetRecordingPhase() const { return m_recording_phase; }
// An empty selection in reduction mode can't be reconstructed when loading from a file.
bool CanSave() const { return !(m_recording_phase == Phase::Reduction && m_selection.empty()); }

View file

@ -189,7 +189,6 @@ AutoStepResults CodeTrace::AutoStepping(const Core::CPUThreadGuard& guard, bool
stop_condition = HitType::ACTIVE;
auto& power_pc = guard.GetSystem().GetPowerPC();
power_pc.GetBreakPoints().ClearAllTemporary();
using clock = std::chrono::steady_clock;
clock::time_point timeout = clock::now() + std::chrono::seconds(4);

View file

@ -73,8 +73,8 @@ public:
}
virtual bool IsAlive() const { return true; }
virtual bool IsBreakpoint(u32 /*address*/) const { return false; }
virtual void SetBreakpoint(u32 /*address*/) {}
virtual void ClearBreakpoint(u32 /*address*/) {}
virtual void AddBreakpoint(u32 /*address*/) {}
virtual void RemoveBreakpoint(u32 /*address*/) {}
virtual void ClearAllBreakpoints() {}
virtual void ToggleBreakpoint(u32 /*address*/) {}
virtual void ClearAllMemChecks() {}
@ -99,7 +99,7 @@ public:
virtual u32 GetPC() const { return 0; }
virtual void SetPC(u32 /*address*/) {}
virtual void Step() {}
virtual void RunToBreakpoint() {}
virtual void RunTo(u32 /*address*/) {}
virtual u32 GetColor(const CPUThreadGuard* /*guard*/, u32 /*address*/) const
{
return 0xFFFFFFFF;

View file

@ -13,7 +13,7 @@
namespace Core
{
class CPUThreadGuard;
};
}
namespace Core::Debug
{

View file

@ -20,6 +20,7 @@
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "Core/Debugger/OSThread.h"
#include "Core/HW/CPU.h"
#include "Core/HW/DSP.h"
#include "Core/PatchEngine.h"
#include "Core/PowerPC/MMU.h"
@ -30,10 +31,9 @@
void ApplyMemoryPatch(const Core::CPUThreadGuard& guard, Common::Debug::MemoryPatch& patch,
bool store_existing_value)
{
#ifdef USE_RETRO_ACHIEVEMENTS
if (AchievementManager::GetInstance().IsHardcoreModeActive())
return;
#endif // USE_RETRO_ACHIEVEMENTS
if (patch.value.empty())
return;
@ -350,7 +350,7 @@ u32 PPCDebugInterface::ReadInstruction(const Core::CPUThreadGuard& guard, u32 ad
bool PPCDebugInterface::IsAlive() const
{
return Core::IsRunningAndStarted();
return Core::IsRunning(m_system);
}
bool PPCDebugInterface::IsBreakpoint(u32 address) const
@ -358,12 +358,12 @@ bool PPCDebugInterface::IsBreakpoint(u32 address) const
return m_system.GetPowerPC().GetBreakPoints().IsAddressBreakPoint(address);
}
void PPCDebugInterface::SetBreakpoint(u32 address)
void PPCDebugInterface::AddBreakpoint(u32 address)
{
m_system.GetPowerPC().GetBreakPoints().Add(address);
}
void PPCDebugInterface::ClearBreakpoint(u32 address)
void PPCDebugInterface::RemoveBreakpoint(u32 address)
{
m_system.GetPowerPC().GetBreakPoints().Remove(address);
}
@ -375,11 +375,7 @@ void PPCDebugInterface::ClearAllBreakpoints()
void PPCDebugInterface::ToggleBreakpoint(u32 address)
{
auto& breakpoints = m_system.GetPowerPC().GetBreakPoints();
if (breakpoints.IsAddressBreakPoint(address))
breakpoints.Remove(address);
else
breakpoints.Add(address);
m_system.GetPowerPC().GetBreakPoints().ToggleBreakPoint(address);
}
void PPCDebugInterface::ClearAllMemChecks()
@ -507,8 +503,11 @@ void PPCDebugInterface::SetPC(u32 address)
m_system.GetPPCState().pc = address;
}
void PPCDebugInterface::RunToBreakpoint()
void PPCDebugInterface::RunTo(u32 address)
{
auto& breakpoints = m_system.GetPowerPC().GetBreakPoints();
breakpoints.SetTemporary(address);
m_system.GetCPU().SetStepping(false);
}
std::shared_ptr<Core::NetworkCaptureLogger> PPCDebugInterface::NetworkLogger()

View file

@ -80,8 +80,8 @@ public:
u32 address) const override;
bool IsAlive() const override;
bool IsBreakpoint(u32 address) const override;
void SetBreakpoint(u32 address) override;
void ClearBreakpoint(u32 address) override;
void AddBreakpoint(u32 address) override;
void RemoveBreakpoint(u32 address) override;
void ClearAllBreakpoints() override;
void ToggleBreakpoint(u32 address) override;
void ClearAllMemChecks() override;
@ -100,7 +100,7 @@ public:
u32 GetPC() const override;
void SetPC(u32 address) override;
void Step() override {}
void RunToBreakpoint() override;
void RunTo(u32 address) override;
u32 GetColor(const Core::CPUThreadGuard* guard, u32 address) const override;
std::string_view GetDescription(u32 address) const override;

View file

@ -16,7 +16,7 @@ class PPCSymbolDB;
namespace Core
{
class CPUThreadGuard;
};
}
struct RSOEntry
{

View file

@ -134,6 +134,9 @@ public:
// to once per game run.
void ReportGameQuirk(GameQuirk quirk);
// Get the base builder for building a report
const Common::AnalyticsReportBuilder& BaseBuilder() const { return m_base_builder; }
struct PerformanceSample
{
double speed_ratio; // See SystemTimers::GetEstimatedEmulationPerformance().

View file

@ -228,7 +228,7 @@ public:
IsPlayingBackFifologWithBrokenEFBCopies = m_parent->m_File->HasBrokenEFBCopies();
// Without this call, we deadlock in initialization in dual core, as the FIFO is disabled and
// thus ClearEfb()'s call to WaitForGPUInactive() never returns
m_parent->m_system.GetCPU().EnableStepping(false);
m_parent->m_system.GetCPU().SetStepping(false);
m_parent->m_CurrentFrame = m_parent->m_FrameRangeStart;
m_parent->LoadMemory();
@ -243,7 +243,7 @@ public:
void SingleStep() override
{
// NOTE: AdvanceFrame() will get stuck forever in Dual Core because the FIFO
// is disabled by CPU::EnableStepping(true) so the frame never gets displayed.
// is disabled by CPU::SetStepping(true) so the frame never gets displayed.
PanicAlertFmtT("Cannot SingleStep the FIFO. Use Frame Advance instead.");
}

View file

@ -46,11 +46,7 @@ void Config::Refresh()
}
camera_config.control_type = ::Config::Get(::Config::FL1_CONTROL_TYPE);
#ifdef USE_RETRO_ACHIEVEMENTS
enabled = ::Config::Get(::Config::FREE_LOOK_ENABLED) &&
!AchievementManager::GetInstance().IsHardcoreModeActive();
#else // USE_RETRO_ACHIEVEMENTS
enabled = ::Config::Get(::Config::FREE_LOOK_ENABLED);
#endif // USE_RETRO_ACHIEVEMENTS
}
} // namespace FreeLook

View file

@ -19,6 +19,10 @@
#include "Core/Core.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h"
#include "ConfigManager.h"
#include "Config/MainSettings.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "Core/System.h"
namespace Gecko
@ -116,15 +120,42 @@ std::vector<GeckoCode> SetAndReturnActiveCodes(std::span<const GeckoCode> gcodes
return s_active_codes;
}
const char* GetGeckoCodeHandlerPath()
{
int code_handler_value = Config::Get(Config::MAIN_CODE_HANDLER);
switch (code_handler_value)
{
case 0:
return GECKO_CODE_HANDLER; // Dolphin (Stock)
case 1:
return GECKO_CODE_HANDLER_MPN; // MPN (Extended)
case 2:
return GECKO_CODE_HANDLER_MPN_SUPER; // MPN (Super Extended)
default:
return GECKO_CODE_HANDLER; // Fallback
}
}
bool IsGeckoCodeHandlerEnabled()
{
int code_handler_value = Config::Get(Config::MAIN_CODE_HANDLER);
return code_handler_value == 1 || code_handler_value == 2;
}
bool IsGeckoCodeHandlerSUPER()
{
int code_handler_value = Config::Get(Config::MAIN_CODE_HANDLER);
return code_handler_value == 2;
}
// Requires s_active_codes_lock
// NOTE: Refer to "codehandleronly.s" from Gecko OS.
static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
{
std::string data;
if (!File::ReadFileToString(File::GetSysDirectory() + GECKO_CODE_HANDLER, data))
if (!File::ReadFileToString(File::GetSysDirectory() + GetGeckoCodeHandlerPath(), data))
{
ERROR_LOG_FMT(ACTIONREPLAY,
"Could not enable cheats because " GECKO_CODE_HANDLER " was missing.");
ERROR_LOG_FMT(ACTIONREPLAY, "Could not enable cheats because the selected codehandler was missing.");
return Installation::Failed;
}
@ -156,9 +187,47 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
}
}
const u32 codelist_base_address =
INSTALLER_BASE_ADDRESS + static_cast<u32>(data.size()) - CODE_SIZE;
const u32 codelist_end_address = INSTALLER_END_ADDRESS;
const bool is_mpn_handler_and_game_id_rm8e01 =
IsGeckoCodeHandlerSUPER() && (SConfig::GetInstance().GetGameID() == "RM8E01");
//const bool is_mpn_handler_and_game_id_gp7e01 =
// IsGeckoCodeHandlerSUPER() && (SConfig::GetInstance().GetGameID() == "GP7E01");
const bool is_mpn_handler_and_game_id_gp6e01 =
IsGeckoCodeHandlerSUPER() && (SConfig::GetInstance().GetGameID() == "GP6E01");
const bool is_mpn_handler_and_game_id_gp5e01 =
IsGeckoCodeHandlerSUPER() && (SConfig::GetInstance().GetGameID() == "GP5E01");
const bool is_mpn_handler_and_game_id_gmpe01 =
IsGeckoCodeHandlerSUPER() && (SConfig::GetInstance().GetGameID() == "GMPE01");
u32 codelist_base_address =
is_mpn_handler_and_game_id_rm8e01 ? INSTALLER_BASE_ADDRESS_MP8 :
//is_mpn_handler_and_game_id_gp7e01 ? INSTALLER_BASE_ADDRESS_MP7 :
is_mpn_handler_and_game_id_gp6e01 ? INSTALLER_BASE_ADDRESS_MP6 :
is_mpn_handler_and_game_id_gp5e01 ? INSTALLER_BASE_ADDRESS_MP5 :
is_mpn_handler_and_game_id_gmpe01 ? INSTALLER_BASE_ADDRESS_MP4 :
INSTALLER_BASE_ADDRESS + static_cast<u32>(data.size()) -
CODE_SIZE;
u32 codelist_end_address = is_mpn_handler_and_game_id_rm8e01 ? INSTALLER_END_ADDRESS_MP8 :
// is_mpn_handler_and_game_id_gp7e01 ? INSTALLER_END_ADDRESS_MP7 :
is_mpn_handler_and_game_id_gp6e01 ? INSTALLER_END_ADDRESS_MP6 :
is_mpn_handler_and_game_id_gp5e01 ? INSTALLER_END_ADDRESS_MP5 :
is_mpn_handler_and_game_id_gmpe01 ? INSTALLER_END_ADDRESS_MP4 :
INSTALLER_END_ADDRESS;
if (is_mpn_handler_and_game_id_rm8e01 || // is_mpn_handler_and_game_id_gp7e01 ||
is_mpn_handler_and_game_id_gp6e01 || is_mpn_handler_and_game_id_gp5e01 ||
is_mpn_handler_and_game_id_gmpe01)
{
// Move Gecko code handler to the free mem region
for (u32 addr = codelist_base_address; addr < codelist_end_address; addr += 4)
{
PowerPC::MMU::HostWrite_U32(guard, 0x00000000, addr);
}
PowerPC::MMU::HostWrite_U32(guard, ((codelist_base_address & 0xFFFF0000) >> 16) + 0x3DE00000,
0x800018F8);
PowerPC::MMU::HostWrite_U32(guard, (codelist_base_address & 0x0000FFFF) + 0x61EF0000,
0x800018FC);
}
// Write a magic value to 'gameid' (codehandleronly does not actually read this).
// This value will be read back and modified over time by HLE_Misc::GeckoCodeHandlerICacheFlush.
@ -184,6 +253,12 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
"not write: \"{}\". Need {} bytes, only {} remain.",
active_code.name, active_code.codes.size() * CODE_SIZE,
end_address - next_address);
OSD::AddMessage(
fmt::format("Too many GeckoCodes! Ran out of storage space in Game RAM. Could "
"not write: \"{}\". Need {} bytes, only {} remain.",
active_code.name, active_code.codes.size() * CODE_SIZE,
end_address - next_address),
OSD::Duration::VERY_LONG);
continue;
}
@ -198,6 +273,9 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
WARN_LOG_FMT(ACTIONREPLAY, "GeckoCodes: Using {} of {} bytes", next_address - start_address,
end_address - start_address);
OSD::AddMessage(fmt::format("Gecko Codes: Using {} of {} bytes", next_address - start_address, end_address - start_address));
// Stop code. Tells the handler that this is the end of the list.
PowerPC::MMU::HostWrite_U32(guard, 0xF0000000, next_address);
PowerPC::MMU::HostWrite_U32(guard, 0x00000000, next_address + 4);
@ -208,11 +286,9 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
// Invalidate the icache and any asm codes
auto& ppc_state = guard.GetSystem().GetPPCState();
auto& memory = guard.GetSystem().GetMemory();
auto& jit_interface = guard.GetSystem().GetJitInterface();
for (u32 j = 0; j < (INSTALLER_END_ADDRESS - INSTALLER_BASE_ADDRESS); j += 32)
{
ppc_state.iCache.Invalidate(memory, jit_interface, INSTALLER_BASE_ADDRESS + j);
ppc_state.iCache.Invalidate(INSTALLER_BASE_ADDRESS + j);
}
return Installation::Installed;
}

Some files were not shown because too many files have changed in this diff Show more