Compare commits
3 Commits
dmnt2
...
descriptor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
513b81b965 | ||
|
|
955cc70796 | ||
|
|
b55c8bb2e7 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,8 +37,6 @@ CMakeLists.txt.user*
|
||||
# *nix related
|
||||
# Common convention for backup or temporary files
|
||||
*~
|
||||
*.core
|
||||
dtrace-out/
|
||||
|
||||
# Visual Studio CMake settings
|
||||
CMakeSettings.json
|
||||
|
||||
10
README.md
10
README.md
@@ -25,9 +25,9 @@ It is written in C++ with portability in mind, and we actively maintain builds f
|
||||
<img src="https://img.shields.io/discord/1367654015269339267?color=5865F2&label=Eden&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
</a>
|
||||
<a href="https://stt.gg/qKgFEAbH">
|
||||
<img src="https://img.shields.io/revolt/invite/qKgFEAbH?color=d61f3a&label=Stoat"
|
||||
alt="Stoat">
|
||||
<a href="https://rvlt.gg/qKgFEAbH">
|
||||
<img src="https://img.shields.io/revolt/invite/qKgFEAbH?color=d61f3a&label=Revolt"
|
||||
alt="Revolt">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -52,10 +52,10 @@ Check out our [website](https://eden-emu.dev) for the latest news on exciting fe
|
||||
|
||||
## Development
|
||||
|
||||
Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/HstXbPch7X) or [Stoat](https://stt.gg/qKgFEAbH).
|
||||
Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/HstXbPch7X) or [Revolt](https://rvlt.gg/qKgFEAbH).
|
||||
You can also follow us on [X (Twitter)](https://nitter.poast.org/edenemuofficial) for updates and announcements.
|
||||
|
||||
If you would like to contribute, we are open to new developers and pull requests. Please ensure that your work is of a high standard and properly documented. You can also contact any of the developers on Discord or Stoat to learn more about the current state of the emulator.
|
||||
If you would like to contribute, we are open to new developers and pull requests. Please ensure that your work is of a high standard and properly documented. You can also contact any of the developers on Discord or Revolt to learn more about the current state of the emulator.
|
||||
|
||||
See the [sign-up instructions](docs/SIGNUP.md) for information on registration.
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ If your initial configure failed:
|
||||
- Evaluate the error and find any related settings
|
||||
- See the [CPM docs](CPM.md) to see if you may need to forcefully bundle any packages
|
||||
|
||||
Otherwise, feel free to ask for help in Stoat or Discord.
|
||||
Otherwise, feel free to ask for help in Revolt or Discord.
|
||||
|
||||
## Caveats
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Icon
|
||||
import android.hardware.input.InputManager
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
@@ -64,12 +63,11 @@ import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import androidx.core.os.BundleCompat
|
||||
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager.InputDeviceListener {
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
|
||||
var isActivityRecreated = false
|
||||
private lateinit var nfcReader: NfcReader
|
||||
private lateinit var inputManager: InputManager
|
||||
|
||||
private var touchDownTime: Long = 0
|
||||
private val maxTapDuration = 500L
|
||||
@@ -142,9 +140,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
nfcReader = NfcReader(this)
|
||||
nfcReader.initialize()
|
||||
|
||||
inputManager = getSystemService(INPUT_SERVICE) as InputManager
|
||||
inputManager.registerInputDeviceListener(this, null)
|
||||
|
||||
foregroundService = Intent(this, ForegroundService::class.java)
|
||||
startForegroundService(foregroundService)
|
||||
|
||||
@@ -211,9 +206,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
inputManager.unregisterInputDeviceListener(this)
|
||||
stopForegroundService(this)
|
||||
NativeLibrary.playTimeManagerStop()
|
||||
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
@@ -249,10 +244,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD &&
|
||||
event.device?.isVirtual == false
|
||||
|
||||
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
|
||||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
|
||||
|
||||
if (!isControllerInput &&
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE &&
|
||||
!isPhysicalKeyboard
|
||||
) {
|
||||
@@ -263,18 +256,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
if (isControllerInput && event.action == KeyEvent.ACTION_DOWN) {
|
||||
notifyControllerInput()
|
||||
}
|
||||
|
||||
return InputHandler.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
|
||||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
|
||||
|
||||
if (!isControllerInput &&
|
||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
||||
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
|
||||
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
|
||||
) {
|
||||
@@ -290,54 +277,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
return true
|
||||
}
|
||||
|
||||
if (isControllerInput) {
|
||||
notifyControllerInput()
|
||||
}
|
||||
|
||||
return InputHandler.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
private fun notifyControllerInput() {
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerInputDetected()
|
||||
}
|
||||
|
||||
private fun isGameController(deviceId: Int): Boolean {
|
||||
val device = InputDevice.getDevice(deviceId) ?: return false
|
||||
val sources = device.sources
|
||||
return sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
|
||||
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
|
||||
}
|
||||
|
||||
override fun onInputDeviceAdded(deviceId: Int) {
|
||||
if (isGameController(deviceId)) {
|
||||
InputHandler.updateControllerData()
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerConnected()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInputDeviceRemoved(deviceId: Int) {
|
||||
InputHandler.updateControllerData()
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||
val emulationFragment =
|
||||
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
emulationFragment?.onControllerDisconnected()
|
||||
}
|
||||
|
||||
override fun onInputDeviceChanged(deviceId: Int) {
|
||||
if (isGameController(deviceId)) {
|
||||
InputHandler.updateControllerData()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
val rotation = this.display?.rotation
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
@@ -577,10 +519,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
touchDownTime = System.currentTimeMillis()
|
||||
// show overlay immediately on touch and cancel timer when only auto-hide is enabled
|
||||
if (!emulationViewModel.drawerOpen.value &&
|
||||
BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() &&
|
||||
!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) {
|
||||
// show overlay immediately on touch and cancel timer
|
||||
if (!emulationViewModel.drawerOpen.value) {
|
||||
fragment.handler.removeCallbacksAndMessages(null)
|
||||
fragment.showOverlay()
|
||||
}
|
||||
|
||||
@@ -6,15 +6,10 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
|
||||
import org.yuzu.yuzu_emu.model.Patch
|
||||
import org.yuzu.yuzu_emu.model.PatchType
|
||||
import org.yuzu.yuzu_emu.model.AddonViewModel
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
@@ -32,95 +27,18 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
|
||||
binding.addonSwitch.performClick()
|
||||
}
|
||||
binding.title.text = model.name
|
||||
|
||||
binding.addonSwitch.setOnCheckedChangeListener(null)
|
||||
binding.version.text = model.version
|
||||
binding.addonSwitch.isChecked = model.enabled
|
||||
|
||||
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
|
||||
model.enabled = checked
|
||||
}
|
||||
|
||||
val isCheat = model.isCheat()
|
||||
val indentPx = if (isCheat) {
|
||||
binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_large)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
(binding.addonCard.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
|
||||
it.marginStart = indentPx
|
||||
binding.addonCard.layoutParams = it
|
||||
}
|
||||
|
||||
if (isCheat) {
|
||||
binding.version.visibility = View.GONE
|
||||
binding.deleteCard.visibility = View.GONE
|
||||
binding.buttonDelete.visibility = View.GONE
|
||||
|
||||
binding.addonSwitch.scaleX = 0.7f
|
||||
binding.addonSwitch.scaleY = 0.7f
|
||||
|
||||
val compactPaddingVertical = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_small)
|
||||
binding.root.setPadding(
|
||||
binding.root.paddingLeft,
|
||||
compactPaddingVertical / 2,
|
||||
binding.root.paddingRight,
|
||||
compactPaddingVertical / 2
|
||||
)
|
||||
|
||||
val innerLayout = binding.addonCard.getChildAt(0) as? ViewGroup
|
||||
innerLayout?.let {
|
||||
val leftPadding = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_large)
|
||||
val smallPadding = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_med)
|
||||
it.setPadding(leftPadding, smallPadding, smallPadding, smallPadding)
|
||||
}
|
||||
|
||||
binding.title.textSize = 14f
|
||||
binding.title.gravity = Gravity.CENTER_VERTICAL
|
||||
|
||||
(binding.title.layoutParams as? ConstraintLayout.LayoutParams)?.let { params ->
|
||||
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
params.topMargin = 0
|
||||
binding.title.layoutParams = params
|
||||
}
|
||||
} else {
|
||||
binding.version.visibility = View.VISIBLE
|
||||
binding.version.text = model.version
|
||||
binding.deleteCard.visibility = View.VISIBLE
|
||||
binding.buttonDelete.visibility = View.VISIBLE
|
||||
|
||||
binding.addonSwitch.scaleX = 1.0f
|
||||
binding.addonSwitch.scaleY = 1.0f
|
||||
|
||||
val normalPadding = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_med)
|
||||
binding.root.setPadding(
|
||||
binding.root.paddingLeft,
|
||||
normalPadding,
|
||||
binding.root.paddingRight,
|
||||
normalPadding
|
||||
)
|
||||
|
||||
val innerLayout = binding.addonCard.getChildAt(0) as? ViewGroup
|
||||
innerLayout?.let {
|
||||
val normalInnerPadding = binding.root.context.resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
|
||||
it.setPadding(normalInnerPadding, normalInnerPadding, normalInnerPadding, normalInnerPadding)
|
||||
}
|
||||
|
||||
binding.title.textSize = 16f
|
||||
binding.title.gravity = Gravity.START
|
||||
|
||||
(binding.title.layoutParams as? ConstraintLayout.LayoutParams)?.let { params ->
|
||||
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
params.bottomToBottom = ConstraintLayout.LayoutParams.UNSET
|
||||
params.topMargin = 0
|
||||
binding.title.layoutParams = params
|
||||
}
|
||||
|
||||
val deleteAction = {
|
||||
addonViewModel.setAddonToDelete(model)
|
||||
}
|
||||
binding.deleteCard.setOnClickListener { deleteAction() }
|
||||
binding.buttonDelete.setOnClickListener { deleteAction() }
|
||||
val deleteAction = {
|
||||
addonViewModel.setAddonToDelete(model)
|
||||
}
|
||||
binding.deleteCard.setOnClickListener { deleteAction() }
|
||||
binding.buttonDelete.setOnClickListener { deleteAction() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
|
||||
SOC_OVERLAY_BACKGROUND("soc_overlay_background"),
|
||||
ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
|
||||
HIDE_OVERLAY_ON_CONTROLLER_INPUT("hide_overlay_on_controller_input"),
|
||||
|
||||
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
|
||||
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
||||
|
||||
@@ -387,13 +387,6 @@ abstract class SettingsItem(
|
||||
valueHint = R.string.seconds
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT,
|
||||
titleId = R.string.hide_overlay_on_controller_input,
|
||||
descriptionId = R.string.hide_overlay_on_controller_input_description
|
||||
)
|
||||
)
|
||||
|
||||
put(
|
||||
SwitchSetting(
|
||||
|
||||
@@ -274,7 +274,6 @@ class SettingsFragmentPresenter(
|
||||
sl.apply {
|
||||
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
|
||||
add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class AboutFragment : Fragment() {
|
||||
}
|
||||
|
||||
binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.discord_link)) }
|
||||
binding.buttonStoat.setOnClickListener { openLink(getString(R.string.stoat_link)) }
|
||||
binding.buttonRevolt.setOnClickListener { openLink(getString(R.string.revolt_link)) }
|
||||
binding.buttonX.setOnClickListener { openLink(getString(R.string.x_link)) }
|
||||
binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) }
|
||||
binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -73,7 +73,7 @@ class AddonsFragment : Fragment() {
|
||||
}
|
||||
|
||||
addonViewModel.addonList.collect(viewLifecycleOwner) {
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it.toList())
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||
}
|
||||
addonViewModel.showModInstallPicker.collect(
|
||||
viewLifecycleOwner,
|
||||
@@ -127,9 +127,7 @@ class AddonsFragment : Fragment() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!requireActivity().isChangingConfigurations) {
|
||||
addonViewModel.onCloseAddons()
|
||||
}
|
||||
addonViewModel.onCloseAddons()
|
||||
}
|
||||
|
||||
val installAddon =
|
||||
|
||||
@@ -93,6 +93,7 @@ import org.yuzu.yuzu_emu.utils.collect
|
||||
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@@ -105,7 +106,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
private var isOverlayVisible = true
|
||||
private var controllerInputReceived = false
|
||||
|
||||
private var _binding: FragmentEmulationBinding? = null
|
||||
|
||||
@@ -656,12 +656,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState)
|
||||
updateQuickOverlayMenuEntry(newState)
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
// Sync view visibility with the setting
|
||||
if (newState) {
|
||||
showOverlay()
|
||||
} else {
|
||||
hideOverlay()
|
||||
}
|
||||
NativeConfig.saveGlobalConfig()
|
||||
true
|
||||
}
|
||||
@@ -1907,7 +1901,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): AmiiboState =
|
||||
entries.firstOrNull { it.value == value } ?: Disabled
|
||||
values().firstOrNull { it.value == value } ?: Disabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1920,7 +1914,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): AmiiboLoadResult =
|
||||
entries.firstOrNull { it.value == value } ?: Unknown
|
||||
values().firstOrNull { it.value == value } ?: Unknown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1977,8 +1971,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
fun showOverlay() {
|
||||
if (!isOverlayVisible) {
|
||||
isOverlayVisible = true
|
||||
// Reset controller input flag so controller can hide overlay again
|
||||
controllerInputReceived = false
|
||||
ViewUtils.showView(binding.surfaceInputOverlay, 500)
|
||||
}
|
||||
}
|
||||
@@ -1986,26 +1978,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
private fun hideOverlay() {
|
||||
if (isOverlayVisible) {
|
||||
isOverlayVisible = false
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay, 500)
|
||||
}
|
||||
}
|
||||
|
||||
fun onControllerInputDetected() {
|
||||
if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return
|
||||
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return
|
||||
if (controllerInputReceived) return
|
||||
controllerInputReceived = true
|
||||
hideOverlay()
|
||||
}
|
||||
|
||||
fun onControllerConnected() {
|
||||
controllerInputReceived = false
|
||||
}
|
||||
|
||||
fun onControllerDisconnected() {
|
||||
if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return
|
||||
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return
|
||||
controllerInputReceived = false
|
||||
showOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -31,14 +28,10 @@ class AddonViewModel : ViewModel() {
|
||||
val addonToDelete = _addonToDelete.asStateFlow()
|
||||
|
||||
var game: Game? = null
|
||||
private set
|
||||
|
||||
private val isRefreshing = AtomicBoolean(false)
|
||||
|
||||
fun onOpenAddons(game: Game) {
|
||||
if (this.game?.programId == game.programId && _patchList.value.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
this.game = game
|
||||
refreshAddons()
|
||||
}
|
||||
@@ -54,7 +47,8 @@ class AddonViewModel : ViewModel() {
|
||||
NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
|
||||
?: emptyArray()
|
||||
).toMutableList()
|
||||
_patchList.value = sortPatchesWithCheatsGrouped(patchList)
|
||||
patchList.sortBy { it.name }
|
||||
_patchList.value = patchList
|
||||
isRefreshing.set(false)
|
||||
}
|
||||
}
|
||||
@@ -69,9 +63,7 @@ class AddonViewModel : ViewModel() {
|
||||
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
|
||||
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
||||
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
||||
PatchType.Cheat -> {}
|
||||
}
|
||||
_patchList.value.clear()
|
||||
refreshAddons()
|
||||
}
|
||||
|
||||
@@ -86,7 +78,7 @@ class AddonViewModel : ViewModel() {
|
||||
if (it.enabled) {
|
||||
null
|
||||
} else {
|
||||
it.getStorageKey()
|
||||
it.name
|
||||
}
|
||||
}.toTypedArray()
|
||||
)
|
||||
@@ -102,28 +94,4 @@ class AddonViewModel : ViewModel() {
|
||||
fun showModNoticeDialog(show: Boolean) {
|
||||
_showModNoticeDialog.value = show
|
||||
}
|
||||
|
||||
private fun sortPatchesWithCheatsGrouped(patches: MutableList<Patch>): MutableList<Patch> {
|
||||
val individualCheats = patches.filter { it.isCheat() }
|
||||
val nonCheats = patches.filter { !it.isCheat() }.sortedBy { it.name }
|
||||
|
||||
val cheatsByParent = individualCheats.groupBy { it.parentName }
|
||||
|
||||
val result = mutableListOf<Patch>()
|
||||
for (patch in nonCheats) {
|
||||
result.add(patch)
|
||||
cheatsByParent[patch.name]?.sortedBy { it.name }?.let { childCheats ->
|
||||
result.addAll(childCheats)
|
||||
}
|
||||
}
|
||||
|
||||
val knownParents = nonCheats.map { it.name }.toSet()
|
||||
for ((parentName, orphanCheats) in cheatsByParent) {
|
||||
if (parentName !in knownParents) {
|
||||
result.addAll(orphanCheats.sortedBy { it.name })
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -15,24 +12,5 @@ data class Patch(
|
||||
val version: String,
|
||||
val type: Int,
|
||||
val programId: String,
|
||||
val titleId: String,
|
||||
val parentName: String = "" // For cheats: name of the mod folder containing them
|
||||
) {
|
||||
/**
|
||||
* Returns the storage key used for saving enabled/disabled state.
|
||||
* For cheats with a parent, returns "ParentName::CheatName".
|
||||
*/
|
||||
fun getStorageKey(): String {
|
||||
return if (parentName.isNotEmpty()) {
|
||||
"$parentName::$name"
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this patch is an individual cheat entry (not a cheat mod).
|
||||
* Individual cheats have type=Cheat and a parent mod name.
|
||||
*/
|
||||
fun isCheat(): Boolean = type == PatchType.Cheat.int && parentName.isNotEmpty()
|
||||
}
|
||||
val titleId: String
|
||||
)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -9,8 +6,7 @@ package org.yuzu.yuzu_emu.model
|
||||
enum class PatchType(val int: Int) {
|
||||
Update(0),
|
||||
DLC(1),
|
||||
Mod(2),
|
||||
Cheat(3);
|
||||
Mod(2);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): PatchType = entries.firstOrNull { it.int == int } ?: Update
|
||||
|
||||
@@ -92,11 +92,6 @@ namespace AndroidSettings {
|
||||
Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
|
||||
Settings::Setting<bool> hide_overlay_on_controller_input{linkage, false,
|
||||
"hide_overlay_on_controller_input",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true,
|
||||
true};
|
||||
Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
|
||||
Settings::Category::Overlay,
|
||||
Settings::Specialization::Default, true,
|
||||
|
||||
@@ -1298,10 +1298,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
||||
FileSys::VirtualFile update_raw;
|
||||
loader->ReadUpdateRaw(update_raw);
|
||||
|
||||
// Get build ID for individual cheat enumeration
|
||||
const auto build_id = pm.GetBuildID(update_raw);
|
||||
|
||||
auto patches = pm.GetPatches(update_raw, build_id);
|
||||
auto patches = pm.GetPatches(update_raw);
|
||||
jobjectArray jpatchArray =
|
||||
env->NewObjectArray(patches.size(), Common::Android::GetPatchClass(), nullptr);
|
||||
int i = 0;
|
||||
@@ -1311,8 +1308,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
||||
Common::Android::ToJString(env, patch.name),
|
||||
Common::Android::ToJString(env, patch.version), static_cast<jint>(patch.type),
|
||||
Common::Android::ToJString(env, std::to_string(patch.program_id)),
|
||||
Common::Android::ToJString(env, std::to_string(patch.title_id)),
|
||||
Common::Android::ToJString(env, patch.parent_name));
|
||||
Common::Android::ToJString(env, std::to_string(patch.title_id)));
|
||||
env->SetObjectArrayElement(jpatchArray, i, jpatch);
|
||||
++i;
|
||||
}
|
||||
|
||||
@@ -220,12 +220,12 @@
|
||||
app:iconPadding="0dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_stoat"
|
||||
android:id="@+id/button_revolt"
|
||||
style="@style/EdenButton.Secondary"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:icon="@drawable/ic_stoat"
|
||||
app:icon="@drawable/ic_revolt"
|
||||
app:iconGravity="textStart"
|
||||
app:iconSize="24dp"
|
||||
app:iconPadding="0dp" />
|
||||
@@ -270,4 +270,4 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -215,12 +215,12 @@
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
android:id="@+id/button_stoat"
|
||||
android:id="@+id/button_revolt"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:icon="@drawable/ic_stoat"
|
||||
app:icon="@drawable/ic_revolt"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
@@ -235,7 +235,7 @@
|
||||
app:icon="@drawable/ic_x"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
app:iconPadding="0dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
@@ -267,4 +267,4 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -26,8 +26,6 @@
|
||||
<string name="overlay_auto_hide">Overlay Auto Hide</string>
|
||||
<string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string>
|
||||
<string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
|
||||
<string name="hide_overlay_on_controller_input">Hide Overlay on Controller Input</string>
|
||||
<string name="hide_overlay_on_controller_input_description">Automatically hide the touch controls overlay when a physical controller is used. Overlay reappears when controller is disconnected.</string>
|
||||
|
||||
<string name="input_overlay_options">Input Overlay</string>
|
||||
<string name="input_overlay_options_description">Configure on-screen controls</string>
|
||||
@@ -447,7 +445,7 @@
|
||||
<string name="user_data_export_cancelled">Export cancelled</string>
|
||||
<string name="user_data_import_failed_description">Make sure the user data folders are at the root of the zip folder and contain a config file at config/config.ini and try again.</string>
|
||||
<string name="discord_link" translatable="false">https://discord.gg/HstXbPch7X</string>
|
||||
<string name="stoat_link" translatable="false">https://stt.gg/qKgFEAbH</string>
|
||||
<string name="revolt_link" translatable="false">https://rvlt.gg/qKgFEAbH</string>
|
||||
<string name="x_link" translatable="false">https://nitter.poast.org/edenemuofficial</string>
|
||||
<string name="website_link" translatable="false">https://eden-emu.dev</string>
|
||||
<string name="github_link" translatable="false">https://git.eden-emu.dev/eden-emu</string>
|
||||
|
||||
@@ -515,7 +515,7 @@ namespace Common::Android {
|
||||
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
|
||||
s_patch_constructor = env->GetMethodID(
|
||||
patch_class, "<init>",
|
||||
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V");
|
||||
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
|
||||
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
|
||||
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
|
||||
|
||||
@@ -17,8 +17,6 @@ add_library(core STATIC
|
||||
constants.h
|
||||
core.cpp
|
||||
core.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
cpu_manager.cpp
|
||||
@@ -611,18 +609,6 @@ add_library(core STATIC
|
||||
hle/service/caps/caps_u.h
|
||||
hle/service/cmif_serialization.h
|
||||
hle/service/cmif_types.h
|
||||
hle/service/dmnt/cheat_interface.cpp
|
||||
hle/service/dmnt/cheat_interface.h
|
||||
hle/service/dmnt/cheat_parser.cpp
|
||||
hle/service/dmnt/cheat_parser.h
|
||||
hle/service/dmnt/cheat_process_manager.cpp
|
||||
hle/service/dmnt/cheat_process_manager.h
|
||||
hle/service/dmnt/cheat_virtual_machine.cpp
|
||||
hle/service/dmnt/cheat_virtual_machine.h
|
||||
hle/service/dmnt/dmnt.cpp
|
||||
hle/service/dmnt/dmnt.h
|
||||
hle/service/dmnt/dmnt_results.h
|
||||
hle/service/dmnt/dmnt_types.h
|
||||
hle/service/erpt/erpt.cpp
|
||||
hle/service/erpt/erpt.h
|
||||
hle/service/es/es.cpp
|
||||
@@ -1157,6 +1143,11 @@ add_library(core STATIC
|
||||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory/cheat_engine.cpp
|
||||
memory/cheat_engine.h
|
||||
memory/dmnt_cheat_types.h
|
||||
memory/dmnt_cheat_vm.cpp
|
||||
memory/dmnt_cheat_vm.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
reporter.cpp
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "game_settings.h"
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -52,12 +51,12 @@
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "core/tools/renderdoc.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
@@ -277,18 +276,9 @@ struct System::Impl {
|
||||
audio_core = std::make_unique<AudioCore::AudioCore>(system);
|
||||
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
|
||||
|
||||
// Create cheat_manager BEFORE services, as DMNT::LoopProcess needs it
|
||||
cheat_manager = std::make_unique<Service::DMNT::CheatProcessManager>(system);
|
||||
|
||||
services =
|
||||
std::make_unique<Service::Services>(service_manager, system, stop_event.get_token());
|
||||
|
||||
// Apply any pending cheats that were registered before cheat_manager was initialized
|
||||
if (pending_cheats.has_pending) {
|
||||
ApplyPendingCheats(system);
|
||||
}
|
||||
|
||||
is_powered_on = true;
|
||||
exit_locked = false;
|
||||
exit_requested = false;
|
||||
@@ -302,6 +292,48 @@ struct System::Impl {
|
||||
return SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
void LoadOverrides(u64 programId) const {
|
||||
std::string vendor = gpu_core->Renderer().GetDeviceVendor();
|
||||
LOG_INFO(Core, "GPU Vendor: {}", vendor);
|
||||
|
||||
// Reset all per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
|
||||
// Insert PC overrides here
|
||||
|
||||
#ifdef ANDROID
|
||||
// Example on how to set a setting based on the program ID and vendor
|
||||
if (programId == 0x010028600EBDA000 && vendor == "Mali") { // Mario 3d World
|
||||
// Settings::values.example = true;
|
||||
}
|
||||
|
||||
// Example array of program IDs
|
||||
const std::array<u64, 10> example_array = {
|
||||
//0xprogramId
|
||||
0x0004000000033400, // Game 1
|
||||
0x0004000000033500 // Game 2
|
||||
// And so on
|
||||
};
|
||||
|
||||
for (auto id : example_array) {
|
||||
if (programId == id) {
|
||||
// Settings::values.example = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Ninja Gaiden Ragebound
|
||||
constexpr u64 ngr = 0x0100781020710000ULL;
|
||||
|
||||
if (programId == ngr) {
|
||||
LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend");
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
}
|
||||
}
|
||||
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath,
|
||||
Service::AM::FrontendAppletParameters& params) {
|
||||
@@ -352,6 +384,11 @@ struct System::Impl {
|
||||
return init_result;
|
||||
}
|
||||
|
||||
// Initialize cheat engine
|
||||
if (cheat_engine) {
|
||||
cheat_engine->Initialize();
|
||||
}
|
||||
|
||||
// Register with applet manager
|
||||
// All threads are started, begin main process execution, now that we're in the clear
|
||||
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
|
||||
@@ -382,8 +419,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
LoadOverrides(program_id);
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
@@ -419,6 +455,7 @@ struct System::Impl {
|
||||
services.reset();
|
||||
service_manager.reset();
|
||||
fs_controller.Reset();
|
||||
cheat_engine.reset();
|
||||
core_timing.ClearPendingEvents();
|
||||
app_loader.reset();
|
||||
audio_core.reset();
|
||||
@@ -494,6 +531,7 @@ struct System::Impl {
|
||||
bool nvdec_active{};
|
||||
|
||||
Reporter reporter;
|
||||
std::unique_ptr<Memory::CheatEngine> cheat_engine;
|
||||
std::unique_ptr<Tools::Freezer> memory_freezer;
|
||||
std::array<u8, 0x20> build_id{};
|
||||
|
||||
@@ -522,18 +560,6 @@ struct System::Impl {
|
||||
/// Debugger
|
||||
std::unique_ptr<Core::Debugger> debugger;
|
||||
|
||||
/// Cheat Manager (DMNT)
|
||||
std::unique_ptr<Service::DMNT::CheatProcessManager> cheat_manager;
|
||||
|
||||
/// Pending cheats to register after cheat_manager is initialized
|
||||
struct PendingCheats {
|
||||
std::vector<Service::DMNT::CheatEntry> list;
|
||||
std::array<u8, 32> build_id{};
|
||||
u64 main_region_begin{};
|
||||
u64 main_region_size{};
|
||||
bool has_pending{false};
|
||||
} pending_cheats;
|
||||
|
||||
SystemResultStatus status = SystemResultStatus::Success;
|
||||
std::string status_details = "";
|
||||
|
||||
@@ -569,61 +595,6 @@ struct System::Impl {
|
||||
general_channel_event = std::make_unique<Service::Event>(*general_channel_context);
|
||||
general_channel_initialized = true;
|
||||
}
|
||||
|
||||
void ApplyPendingCheats(System& system) {
|
||||
if (!pending_cheats.has_pending || !cheat_manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Applying {} pending cheats", pending_cheats.list.size());
|
||||
|
||||
const auto result = cheat_manager->AttachToApplicationProcess(
|
||||
pending_cheats.build_id, pending_cheats.main_region_begin,
|
||||
pending_cheats.main_region_size);
|
||||
|
||||
if (result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to attach cheat process: result={}", result.raw);
|
||||
pending_cheats = {};
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Cheat process attached successfully");
|
||||
|
||||
for (const auto& entry : pending_cheats.list) {
|
||||
if (entry.cheat_id == 0 && entry.definition.num_opcodes != 0) {
|
||||
LOG_DEBUG(Core, "Setting master cheat '{}' with {} opcodes",
|
||||
entry.definition.readable_name.data(), entry.definition.num_opcodes);
|
||||
const auto set_result = cheat_manager->SetMasterCheat(entry.definition);
|
||||
if (set_result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to set master cheat: result={}", set_result.raw);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add normal cheats (cheat_id != 0)
|
||||
for (const auto& entry : pending_cheats.list) {
|
||||
if (entry.cheat_id == 0 || entry.definition.num_opcodes == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 assigned_id = 0;
|
||||
LOG_DEBUG(Core, "Adding cheat '{}' (enabled={}, {} opcodes)",
|
||||
entry.definition.readable_name.data(), entry.enabled,
|
||||
entry.definition.num_opcodes);
|
||||
const auto add_result = cheat_manager->AddCheat(assigned_id, entry.enabled,
|
||||
entry.definition);
|
||||
if (add_result.IsError()) {
|
||||
LOG_WARNING(Core,
|
||||
"Failed to add cheat (original_id={} enabled={} name='{}'): result={}",
|
||||
entry.cheat_id, entry.enabled,
|
||||
entry.definition.readable_name.data(), add_result.raw);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear pending cheats
|
||||
pending_cheats = {};
|
||||
}
|
||||
};
|
||||
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
@@ -869,61 +840,11 @@ FileSys::VirtualFilesystem System::GetFilesystem() const {
|
||||
return impl->virtual_filesystem;
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<Service::DMNT::CheatEntry>& list,
|
||||
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
const std::array<u8, 32>& build_id, u64 main_region_begin,
|
||||
u64 main_region_size) {
|
||||
// If cheat_manager is not yet initialized, cache the cheats for later
|
||||
if (!impl->cheat_manager) {
|
||||
impl->pending_cheats.list = list;
|
||||
impl->pending_cheats.build_id = build_id;
|
||||
impl->pending_cheats.main_region_begin = main_region_begin;
|
||||
impl->pending_cheats.main_region_size = main_region_size;
|
||||
impl->pending_cheats.has_pending = true;
|
||||
LOG_INFO(Core, "Cached {} cheats for later registration", list.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach cheat process to the current application process
|
||||
const auto result = impl->cheat_manager->AttachToApplicationProcess(build_id, main_region_begin,
|
||||
main_region_size);
|
||||
if (result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to attach cheat process: result={}", result.raw);
|
||||
return;
|
||||
}
|
||||
|
||||
// Empty list: nothing more to do
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set master cheat if present (cheat_id == 0)
|
||||
for (const auto& entry : list) {
|
||||
if (entry.cheat_id == 0 && entry.definition.num_opcodes != 0) {
|
||||
const auto set_result = impl->cheat_manager->SetMasterCheat(entry.definition);
|
||||
if (set_result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to set master cheat: result={}", set_result.raw);
|
||||
}
|
||||
// Only one master cheat allowed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add normal cheats (cheat_id != 0)
|
||||
for (const auto& entry : list) {
|
||||
if (entry.cheat_id == 0 || entry.definition.num_opcodes == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 assigned_id = 0;
|
||||
const auto add_result = impl->cheat_manager->AddCheat(assigned_id, entry.enabled,
|
||||
entry.definition);
|
||||
if (add_result.IsError()) {
|
||||
LOG_WARNING(Core,
|
||||
"Failed to add cheat (original_id={} enabled={} name='{}'): result={}",
|
||||
entry.cheat_id, entry.enabled,
|
||||
entry.definition.readable_name.data(), add_result.raw);
|
||||
}
|
||||
}
|
||||
impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
|
||||
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
|
||||
}
|
||||
|
||||
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
|
||||
@@ -1067,15 +988,6 @@ Tools::RenderdocAPI& System::GetRenderdocAPI() {
|
||||
return *impl->renderdoc_api;
|
||||
}
|
||||
|
||||
Service::DMNT::CheatProcessManager& System::GetCheatManager()
|
||||
{
|
||||
return *impl->cheat_manager;
|
||||
}
|
||||
|
||||
const Service::DMNT::CheatProcessManager& System::GetCheatManager() const {
|
||||
return *impl->cheat_manager;
|
||||
}
|
||||
|
||||
void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
||||
return impl->kernel.RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/os/event.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
|
||||
@@ -46,14 +45,10 @@ enum class ResultStatus : u16;
|
||||
} // namespace Loader
|
||||
|
||||
namespace Core::Memory {
|
||||
struct CheatEntry;
|
||||
class Memory;
|
||||
} // namespace Core::Memory
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatProcessManager;
|
||||
struct CheatEntry;
|
||||
}
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace Account {
|
||||
@@ -344,7 +339,7 @@ public:
|
||||
|
||||
[[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const;
|
||||
|
||||
void RegisterCheatList(const std::vector<Service::DMNT::CheatEntry>& list,
|
||||
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
const std::array<u8, 0x20>& build_id, u64 main_region_begin,
|
||||
u64 main_region_size);
|
||||
|
||||
@@ -388,9 +383,6 @@ public:
|
||||
|
||||
[[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
|
||||
|
||||
[[nodiscard]] Service::DMNT::CheatProcessManager& GetCheatManager();
|
||||
[[nodiscard]] const Service::DMNT::CheatProcessManager& GetCheatManager() const;
|
||||
|
||||
void SetExitLocked(bool locked);
|
||||
bool GetExitLocked() const;
|
||||
|
||||
|
||||
@@ -27,13 +27,12 @@
|
||||
#include "core/file_sys/vfs/vfs_cached.h"
|
||||
#include "core/file_sys/vfs/vfs_layered.h"
|
||||
#include "core/file_sys/vfs/vfs_vector.h"
|
||||
#include "core/hle/service/dmnt/cheat_parser.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/set/settings_server.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
|
||||
namespace FileSys {
|
||||
namespace {
|
||||
@@ -65,15 +64,16 @@ std::string FormatTitleVersion(u32 version,
|
||||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||
}
|
||||
|
||||
// Returns a directory with name matching case-insensitively.
|
||||
// Returns nullptr if directory doesn't contain a subdirectory with the given name.
|
||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir& dir, std::string_view name) {
|
||||
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
|
||||
// doesn't have a directory with name.
|
||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
|
||||
#ifdef _WIN32
|
||||
return dir->GetSubdirectory(name);
|
||||
#else
|
||||
const auto target = Common::ToLower(std::string(name));
|
||||
for (const auto& subdir : dir->GetSubdirectories()) {
|
||||
if (Common::ToLower(subdir->GetName()) == target) {
|
||||
const auto subdirs = dir->GetSubdirectories();
|
||||
for (const auto& subdir : subdirs) {
|
||||
std::string dir_name = Common::ToLower(subdir->GetName());
|
||||
if (dir_name == name) {
|
||||
return subdir;
|
||||
}
|
||||
}
|
||||
@@ -82,35 +82,36 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir& dir, std::string_view name
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<std::vector<Service::DMNT::CheatEntry>> ReadCheatFileFromFolder(
|
||||
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
|
||||
u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
|
||||
|
||||
const auto build_id_raw = Common::HexToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, std::min(build_id_raw.size(), sizeof(u64) * 2));
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
|
||||
if (file == nullptr) {
|
||||
LOG_DEBUG(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != data.size()) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const Service::DMNT::CheatParser parser;
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
}
|
||||
|
||||
void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
||||
if (!to.empty()) {
|
||||
if (to.empty()) {
|
||||
to += with;
|
||||
} else {
|
||||
to += ", ";
|
||||
to += with;
|
||||
}
|
||||
to += with;
|
||||
}
|
||||
|
||||
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
@@ -315,7 +316,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name)
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
std::vector<Service::DMNT::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
|
||||
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
@@ -324,71 +325,36 @@ std::vector<Service::DMNT::CheatEntry> PatchManager::CreateCheatList(const Build
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<Service::DMNT::CheatEntry> out;
|
||||
|
||||
// Load cheats from: <mod dir>/<folder>/cheats/<build_id>.txt
|
||||
// <mod dir> / <folder> / cheats / <build id>.txt
|
||||
std::vector<Core::Memory::CheatEntry> out;
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
const auto mod_name = subdir->GetName();
|
||||
|
||||
// Skip entirely disabled mods
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), mod_name) != disabled.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
|
||||
if (cheats_dir == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try uppercase build_id first, then lowercase
|
||||
std::optional<std::vector<Service::DMNT::CheatEntry>> cheat_entries;
|
||||
if (auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
|
||||
cheat_entries = std::move(res);
|
||||
} else if (auto res_lower = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
|
||||
cheat_entries = std::move(res_lower);
|
||||
}
|
||||
|
||||
if (cheat_entries) {
|
||||
for (auto& entry : *cheat_entries) {
|
||||
// Check if this individual cheat is disabled
|
||||
const std::string cheat_name = entry.definition.readable_name.data();
|
||||
const std::string cheat_key = mod_name + "::" + cheat_name;
|
||||
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), cheat_key) != disabled.cend()) {
|
||||
// Individual cheat is disabled - mark it as disabled but still include it
|
||||
entry.enabled = false;
|
||||
}
|
||||
|
||||
out.push_back(entry);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) == disabled.cend()) {
|
||||
if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); cheats_dir != nullptr) {
|
||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true))
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false))
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User-friendly cheat loading from: <mod dir>/cheat_*.txt
|
||||
for (const auto& file : load_dir->GetFiles()) {
|
||||
const auto& name = file->GetName();
|
||||
if (!name.starts_with("cheat_")) {
|
||||
continue;
|
||||
// Uncareless user-friendly loading of patches (must start with 'cheat_')
|
||||
// <mod dir> / <cheat file>.txt
|
||||
auto const patch_files = load_dir->GetFiles();
|
||||
for (auto const& f : patch_files) {
|
||||
auto const name = f->GetName();
|
||||
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
|
||||
std::vector<u8> data(f->GetSize());
|
||||
if (f->Read(data.data(), data.size()) == data.size()) {
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
std::copy(res.begin(), res.end(), std::back_inserter(out));
|
||||
} else {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
|
||||
}
|
||||
}
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), name) != disabled.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != static_cast<size_t>(data.size())) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to read cheat file '{}' for title_id={:016X}",
|
||||
name, title_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const Service::DMNT::CheatParser parser;
|
||||
auto entries = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
out.insert(out.end(), entries.begin(), entries.end());
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -515,53 +481,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
return romfs;
|
||||
}
|
||||
|
||||
PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const {
|
||||
BuildID build_id{};
|
||||
|
||||
// Get the base NCA
|
||||
const auto base_nca = content_provider.GetEntry(title_id, ContentRecordType::Program);
|
||||
if (base_nca == nullptr) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
// Try to get ExeFS from update first, then base
|
||||
VirtualDir exefs;
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
|
||||
|
||||
if (update != nullptr && update->GetExeFS() != nullptr) {
|
||||
exefs = update->GetExeFS();
|
||||
} else if (update_raw != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca.get());
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetExeFS() != nullptr) {
|
||||
exefs = new_nca->GetExeFS();
|
||||
}
|
||||
}
|
||||
|
||||
if (exefs == nullptr) {
|
||||
exefs = base_nca->GetExeFS();
|
||||
}
|
||||
|
||||
if (exefs == nullptr) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
// Try to read the main NSO header
|
||||
const auto main_file = exefs->GetFile("main");
|
||||
if (main_file == nullptr || main_file->GetSize() < sizeof(Loader::NSOHeader)) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
Loader::NSOHeader header{};
|
||||
if (main_file->Read(reinterpret_cast<u8*>(&header), sizeof(header)) == sizeof(header)) {
|
||||
build_id = header.build_id;
|
||||
}
|
||||
|
||||
return build_id;
|
||||
}
|
||||
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildID& build_id) const {
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
if (title_id == 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -582,8 +502,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
.version = "",
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = ""};
|
||||
.title_id = title_id};
|
||||
|
||||
if (nacp != nullptr) {
|
||||
update_patch.version = nacp->GetVersionString();
|
||||
@@ -603,15 +522,11 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have a valid build_id for cheat enumeration
|
||||
const bool has_build_id = std::any_of(build_id.begin(), build_id.end(), [](u8 b) { return b != 0; });
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
bool has_cheats = false;
|
||||
|
||||
const auto exefs_dir = FindSubdirectoryCaseless(mod, "exefs");
|
||||
if (IsDirValidAndNonEmpty(exefs_dir)) {
|
||||
@@ -640,12 +555,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) ||
|
||||
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
|
||||
const auto cheats_dir = FindSubdirectoryCaseless(mod, "cheats");
|
||||
if (IsDirValidAndNonEmpty(cheats_dir)) {
|
||||
has_cheats = true;
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats")))
|
||||
AppendCommaIfNotEmpty(types, "Cheats");
|
||||
}
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
@@ -657,46 +568,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
.version = types,
|
||||
.type = PatchType::Mod,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = ""});
|
||||
|
||||
// Add individual cheats as sub-entries if we have a build_id
|
||||
if (has_cheats && has_build_id && !mod_disabled) {
|
||||
// Try to read cheat file (uppercase first, then lowercase)
|
||||
std::optional<std::vector<Service::DMNT::CheatEntry>> cheat_entries;
|
||||
if (auto res = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, true)) {
|
||||
cheat_entries = std::move(res);
|
||||
} else if (auto res_lower = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, false)) {
|
||||
cheat_entries = std::move(res_lower);
|
||||
}
|
||||
|
||||
if (cheat_entries) {
|
||||
for (const auto& cheat : *cheat_entries) {
|
||||
// Skip master cheat (id 0) with no readable name
|
||||
if (cheat.cheat_id == 0 && cheat.definition.readable_name[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string cheat_name = cheat.definition.readable_name.data();
|
||||
if (cheat_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create unique key for this cheat: "ModName::CheatName"
|
||||
const std::string cheat_key = mod->GetName() + "::" + cheat_name;
|
||||
const auto cheat_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), cheat_key) != disabled.end();
|
||||
|
||||
out.push_back({.enabled = !cheat_disabled,
|
||||
.name = cheat_name,
|
||||
.version = types,
|
||||
.type = PatchType::Cheat,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = mod->GetName()});
|
||||
}
|
||||
}
|
||||
}
|
||||
.title_id = title_id});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,8 +592,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
.version = types,
|
||||
.type = PatchType::Mod,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = ""});
|
||||
.title_id = title_id});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,8 +624,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
||||
.version = std::move(list),
|
||||
.type = PatchType::DLC,
|
||||
.program_id = title_id,
|
||||
.title_id = dlc_match.back().title_id,
|
||||
.parent_name = ""});
|
||||
.title_id = dlc_match.back().title_id});
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -13,6 +10,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -22,17 +20,13 @@ namespace Service::FileSystem {
|
||||
class FileSystemController;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatEntry;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class ContentProvider;
|
||||
class NCA;
|
||||
class NACP;
|
||||
|
||||
enum class PatchType { Update, DLC, Mod, Cheat };
|
||||
enum class PatchType { Update, DLC, Mod };
|
||||
|
||||
struct Patch {
|
||||
bool enabled;
|
||||
@@ -41,7 +35,6 @@ struct Patch {
|
||||
PatchType type;
|
||||
u64 program_id;
|
||||
u64 title_id;
|
||||
std::string parent_name;
|
||||
};
|
||||
|
||||
// A centralized class to manage patches to games.
|
||||
@@ -72,7 +65,7 @@ public:
|
||||
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
[[nodiscard]] std::vector<Service::DMNT::CheatEntry> CreateCheatList(
|
||||
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
|
||||
const BuildID& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
@@ -83,11 +76,8 @@ public:
|
||||
VirtualFile packed_update_raw = nullptr,
|
||||
bool apply_layeredfs = true) const;
|
||||
|
||||
// Returns a vector of patches including individual cheats
|
||||
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr,
|
||||
const BuildID& build_id = {}) const;
|
||||
|
||||
[[nodiscard]] BuildID GetBuildID(VirtualFile update_raw = nullptr) const;
|
||||
// Returns a vector of patches
|
||||
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const;
|
||||
|
||||
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
|
||||
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/game_settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
namespace Core::GameSettings {
|
||||
|
||||
static GPUVendor GetGPU(const std::string& gpu_vendor_string) {
|
||||
struct Entry { const char* name; GPUVendor vendor; };
|
||||
static constexpr Entry GpuVendor[] = {
|
||||
// NVIDIA
|
||||
{"NVIDIA", GPUVendor::Nvidia},
|
||||
{"Nouveau", GPUVendor::Nvidia},
|
||||
{"NVK", GPUVendor::Nvidia},
|
||||
{"Tegra", GPUVendor::Nvidia},
|
||||
// AMD
|
||||
{"AMD", GPUVendor::AMD},
|
||||
{"RadeonSI", GPUVendor::AMD},
|
||||
{"RADV", GPUVendor::AMD},
|
||||
{"AMDVLK", GPUVendor::AMD},
|
||||
{"R600", GPUVendor::AMD},
|
||||
// Intel
|
||||
{"Intel", GPUVendor::Intel},
|
||||
{"ANV", GPUVendor::Intel},
|
||||
{"i965", GPUVendor::Intel},
|
||||
{"i915", GPUVendor::Intel},
|
||||
{"OpenSWR", GPUVendor::Intel},
|
||||
// Apple
|
||||
{"Apple", GPUVendor::Apple},
|
||||
{"MoltenVK", GPUVendor::Apple},
|
||||
// Qualcomm / Adreno
|
||||
{"Qualcomm", GPUVendor::Qualcomm},
|
||||
{"Turnip", GPUVendor::Qualcomm},
|
||||
// ARM / Mali
|
||||
{"Mali", GPUVendor::ARM},
|
||||
{"PanVK", GPUVendor::ARM},
|
||||
// Imagination / PowerVR
|
||||
{"PowerVR", GPUVendor::Imagination},
|
||||
{"PVR", GPUVendor::Imagination},
|
||||
// Microsoft / WARP / D3D12 GL
|
||||
{"D3D12", GPUVendor::Microsoft},
|
||||
{"Microsoft", GPUVendor::Microsoft},
|
||||
{"WARP", GPUVendor::Microsoft},
|
||||
};
|
||||
|
||||
for (const auto& entry : GpuVendor) {
|
||||
if (gpu_vendor_string == entry.name) {
|
||||
return entry.vendor;
|
||||
}
|
||||
}
|
||||
|
||||
// legacy (shouldn't be needed anymore, but just in case)
|
||||
std::string gpu = gpu_vendor_string;
|
||||
std::transform(gpu.begin(), gpu.end(), gpu.begin(), [](unsigned char c){ return (char)std::tolower(c); });
|
||||
if (gpu.find("geforce") != std::string::npos) {
|
||||
return GPUVendor::Nvidia;
|
||||
}
|
||||
if (gpu.find("radeon") != std::string::npos || gpu.find("ati") != std::string::npos) {
|
||||
return GPUVendor::AMD;
|
||||
}
|
||||
|
||||
return GPUVendor::Unknown;
|
||||
}
|
||||
|
||||
static OS DetectOS() {
|
||||
#if defined(_WIN32)
|
||||
return OS::Windows;
|
||||
#elif defined(__FIREOS__)
|
||||
return OS::FireOS;
|
||||
#elif defined(__ANDROID__)
|
||||
return OS::Android;
|
||||
#elif defined(__OHOS__)
|
||||
return OS::HarmonyOS;
|
||||
#elif defined(__HAIKU__)
|
||||
return OS::HaikuOS;
|
||||
#elif defined(__DragonFly__)
|
||||
return OS::DragonFlyBSD;
|
||||
#elif defined(__NetBSD__)
|
||||
return OS::NetBSD;
|
||||
#elif defined(__OpenBSD__)
|
||||
return OS::OpenBSD;
|
||||
#elif defined(_AIX)
|
||||
return OS::AIX;
|
||||
#elif defined(__managarm__)
|
||||
return OS::Managarm;
|
||||
#elif defined(__redox__)
|
||||
return OS::RedoxOS;
|
||||
#elif defined(__APPLE__) && defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
return OS::IOS;
|
||||
#elif defined(__APPLE__)
|
||||
return OS::MacOS;
|
||||
#elif defined(__FreeBSD__)
|
||||
return OS::FreeBSD;
|
||||
#elif defined(__sun) && defined(__SVR4)
|
||||
return OS::Solaris;
|
||||
#elif defined(__linux__)
|
||||
return OS::Linux;
|
||||
#else
|
||||
return OS::Unknown;
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer) {
|
||||
EnvironmentInfo env{};
|
||||
env.os = DetectOS();
|
||||
env.vendor_string = renderer.GetDeviceVendor();
|
||||
env.vendor = GetGPU(env.vendor_string);
|
||||
return env;
|
||||
}
|
||||
|
||||
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer) {
|
||||
const auto env = DetectEnvironment(renderer);
|
||||
|
||||
switch (static_cast<TitleID>(program_id)) {
|
||||
case TitleID::NinjaGaidenRagebound:
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_INFO(Core, "Applied game settings for title ID {:016X} on OS {}, GPU vendor {} ({})",
|
||||
program_id,
|
||||
static_cast<int>(env.os),
|
||||
static_cast<int>(env.vendor),
|
||||
env.vendor_string);
|
||||
}
|
||||
|
||||
} // namespace Core::GameSettings
|
||||
@@ -1,60 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace VideoCore { class RendererBase; }
|
||||
|
||||
namespace Core::GameSettings {
|
||||
|
||||
enum class OS {
|
||||
Windows,
|
||||
Linux,
|
||||
MacOS,
|
||||
IOS,
|
||||
Android,
|
||||
FireOS,
|
||||
HarmonyOS,
|
||||
FreeBSD,
|
||||
DragonFlyBSD,
|
||||
NetBSD,
|
||||
OpenBSD,
|
||||
HaikuOS,
|
||||
AIX,
|
||||
Managarm,
|
||||
RedoxOS,
|
||||
Solaris,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class GPUVendor {
|
||||
Nvidia,
|
||||
AMD,
|
||||
Intel,
|
||||
Apple,
|
||||
Qualcomm,
|
||||
ARM,
|
||||
Imagination,
|
||||
Microsoft,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class TitleID : std::uint64_t {
|
||||
NinjaGaidenRagebound = 0x0100781020710000ULL
|
||||
};
|
||||
|
||||
struct EnvironmentInfo {
|
||||
OS os{OS::Unknown};
|
||||
GPUVendor vendor{GPUVendor::Unknown};
|
||||
std::string vendor_string; // raw string from driver
|
||||
};
|
||||
|
||||
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer);
|
||||
|
||||
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer);
|
||||
|
||||
} // namespace Core::GameSettings
|
||||
@@ -36,6 +36,10 @@ void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* pro
|
||||
m_buffer_sharing_enabled = false;
|
||||
m_blending_enabled = mode == LibraryAppletMode::PartialForeground ||
|
||||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay;
|
||||
|
||||
if (m_applet_id != AppletId::Application) {
|
||||
(void)this->IsSystemBufferSharingEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayLayerManager::Finalize() {
|
||||
@@ -76,10 +80,11 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
|
||||
if (m_applet_id != AppletId::Application) {
|
||||
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
|
||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||
(void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
|
||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
|
||||
static constexpr s32 kOverlayBackgroundZ = -1;
|
||||
(void)m_manager_display_service->SetLayerZIndex(kOverlayBackgroundZ, *out_layer_id);
|
||||
} else {
|
||||
(void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
|
||||
static constexpr s32 kOverlayZ = 3;
|
||||
(void)m_manager_display_service->SetLayerZIndex(kOverlayZ, *out_layer_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +131,6 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
|
||||
s32 initial_z = 1;
|
||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||
initial_z = -1;
|
||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
|
||||
}
|
||||
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Service::AM {
|
||||
{20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"},
|
||||
{21, nullptr, "SetHandlingTouchScreenInputEnabled"},
|
||||
{30, nullptr, "SetHealthWarningShowingState"},
|
||||
{31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"},
|
||||
{31, nullptr, "IsHealthWarningRequired"},
|
||||
{40, nullptr, "GetApplicationNintendoLogo"},
|
||||
{41, nullptr, "GetApplicationStartupMovie"},
|
||||
{50, nullptr, "SetGpuTimeSliceBoostForApplication"},
|
||||
@@ -69,33 +69,12 @@ namespace Service::AM {
|
||||
Result IOverlayFunctions::GetApplicationIdForLogo(Out<u64> out_application_id) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
std::shared_ptr<Applet> target_applet;
|
||||
|
||||
auto* window_system = system.GetAppletManager().GetWindowSystem();
|
||||
if (window_system) {
|
||||
target_applet = window_system->GetMainApplet();
|
||||
if (target_applet) {
|
||||
std::scoped_lock lk{target_applet->lock};
|
||||
LOG_DEBUG(Service_AM, "applet_id={}, program_id={:016X}, type={}",
|
||||
static_cast<u32>(target_applet->applet_id), target_applet->program_id,
|
||||
static_cast<u32>(target_applet->type));
|
||||
|
||||
u64 id = target_applet->screen_shot_identity.application_id;
|
||||
if (id == 0) {
|
||||
id = target_applet->program_id;
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "application_id={:016X}", id);
|
||||
*out_application_id = id;
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer explicit application_id if available, else fall back to program_id
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
u64 id = m_applet->screen_shot_identity.application_id;
|
||||
if (id == 0) {
|
||||
id = m_applet->program_id;
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "application_id={:016X} (fallback)", id);
|
||||
*out_application_id = id;
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -107,13 +86,6 @@ namespace Service::AM {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IOverlayFunctions::IsHealthWarningRequired(Out<bool> is_required) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
*is_required = false;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled(bool enabled) {
|
||||
LOG_DEBUG(Service_AM, "called, enabled={}", enabled);
|
||||
std::scoped_lock lk{m_applet->lock};
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace Service::AM {
|
||||
Result EndToWatchShortHomeButtonMessage();
|
||||
Result GetApplicationIdForLogo(Out<u64> out_application_id);
|
||||
Result SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled);
|
||||
Result IsHealthWarningRequired(Out<bool> is_required);
|
||||
Result SetHandlingHomeButtonShortPressedEnabled(bool enabled);
|
||||
Result Unknown70();
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ void WindowSystem::OnSystemButtonPress(SystemButtonType type) {
|
||||
if (m_overlay_display) {
|
||||
std::scoped_lock lk_overlay{m_overlay_display->lock};
|
||||
m_overlay_display->overlay_in_foreground = !m_overlay_display->overlay_in_foreground;
|
||||
// Tie window visibility to foreground state so hidden when not active
|
||||
m_overlay_display->window_visible = m_overlay_display->overlay_in_foreground;
|
||||
LOG_INFO(Service_AM, "Overlay long-press toggle: overlay_in_foreground={} window_visible={}", m_overlay_display->overlay_in_foreground, m_overlay_display->window_visible);
|
||||
}
|
||||
SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingHomeButton);
|
||||
@@ -391,7 +393,7 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
|
||||
s32 z_index = 0;
|
||||
const bool now_foreground = inherited_foreground;
|
||||
if (applet->applet_id == AppletId::OverlayDisplay) {
|
||||
z_index = applet->overlay_in_foreground ? 100000 : -1;
|
||||
z_index = applet->overlay_in_foreground ? 100000 : -100000;
|
||||
} else if (now_foreground && !is_obscured) {
|
||||
z_index = 2;
|
||||
} else if (now_foreground) {
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/dmnt/cheat_interface.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/dmnt_results.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
ICheatInterface::ICheatInterface(Core::System& system_, CheatProcessManager& manager)
|
||||
: ServiceFramework{system_, "dmnt:cht"}, cheat_process_manager{manager} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{65000, C<&ICheatInterface::HasCheatProcess>, "HasCheatProcess"},
|
||||
{65001, C<&ICheatInterface::GetCheatProcessEvent>, "GetCheatProcessEvent"},
|
||||
{65002, C<&ICheatInterface::GetCheatProcessMetadata>, "GetCheatProcessMetadata"},
|
||||
{65003, C<&ICheatInterface::ForceOpenCheatProcess>, "ForceOpenCheatProcess"},
|
||||
{65004, C<&ICheatInterface::PauseCheatProcess>, "PauseCheatProcess"},
|
||||
{65005, C<&ICheatInterface::ResumeCheatProcess>, "ResumeCheatProcess"},
|
||||
{65006, C<&ICheatInterface::ForceCloseCheatProcess>, "ForceCloseCheatProcess"},
|
||||
{65100, C<&ICheatInterface::GetCheatProcessMappingCount>, "GetCheatProcessMappingCount"},
|
||||
{65101, C<&ICheatInterface::GetCheatProcessMappings>, "GetCheatProcessMappings"},
|
||||
{65102, C<&ICheatInterface::ReadCheatProcessMemory>, "ReadCheatProcessMemory"},
|
||||
{65103, C<&ICheatInterface::WriteCheatProcessMemory>, "WriteCheatProcessMemory"},
|
||||
{65104, C<&ICheatInterface::QueryCheatProcessMemory>, "QueryCheatProcessMemory"},
|
||||
{65200, C<&ICheatInterface::GetCheatCount>, "GetCheatCount"},
|
||||
{65201, C<&ICheatInterface::GetCheats>, "GetCheats"},
|
||||
{65202, C<&ICheatInterface::GetCheatById>, "GetCheatById"},
|
||||
{65203, C<&ICheatInterface::ToggleCheat>, "ToggleCheat"},
|
||||
{65204, C<&ICheatInterface::AddCheat>, "AddCheat"},
|
||||
{65205, C<&ICheatInterface::RemoveCheat>, "RemoveCheat"},
|
||||
{65206, C<&ICheatInterface::ReadStaticRegister>, "ReadStaticRegister"},
|
||||
{65207, C<&ICheatInterface::WriteStaticRegister>, "WriteStaticRegister"},
|
||||
{65208, C<&ICheatInterface::ResetStaticRegisters>, "ResetStaticRegisters"},
|
||||
{65209, C<&ICheatInterface::SetMasterCheat>, "SetMasterCheat"},
|
||||
{65300, C<&ICheatInterface::GetFrozenAddressCount>, "GetFrozenAddressCount"},
|
||||
{65301, C<&ICheatInterface::GetFrozenAddresses>, "GetFrozenAddresses"},
|
||||
{65302, C<&ICheatInterface::GetFrozenAddress>, "GetFrozenAddress"},
|
||||
{65303, C<&ICheatInterface::EnableFrozenAddress>, "EnableFrozenAddress"},
|
||||
{65304, C<&ICheatInterface::DisableFrozenAddress>, "DisableFrozenAddress"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ICheatInterface::~ICheatInterface() = default;
|
||||
|
||||
Result ICheatInterface::HasCheatProcess(Out<bool> out_has_cheat) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
*out_has_cheat = cheat_process_manager.HasCheatProcess();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
*out_event = &cheat_process_manager.GetCheatProcessEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMetadata(*out_metadata));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ForceOpenCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_UNLESS(R_SUCCEEDED(cheat_process_manager.ForceOpenCheatProcess()), ResultCheatNotAttached);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::PauseCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.PauseCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResumeCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResumeCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ForceCloseCheatProcess() {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called");
|
||||
R_RETURN(cheat_process_manager.ForceCloseCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMappingCount(Out<u64> out_count) {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called");
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMappingCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMappings(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_mappings.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMappings(*out_count, offset, out_mappings));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadCheatProcessMemory(u64 address, u64 size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer) {
|
||||
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size);
|
||||
R_UNLESS(!out_buffer.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.ReadCheatProcessMemory(address, size, out_buffer));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteCheatProcessMemory(u64 address, u64 size,
|
||||
InBuffer<BufferAttr_HipcMapAlias> buffer) {
|
||||
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size);
|
||||
R_UNLESS(!buffer.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.WriteCheatProcessMemory(address, size, buffer));
|
||||
}
|
||||
|
||||
Result ICheatInterface::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping,
|
||||
u64 address) {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.QueryCheatProcessMemory(out_mapping, address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatCount(Out<u64> out_count) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetCheatCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheats(Out<u64> out_count, u64 offset,
|
||||
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_cheats.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetCheats(*out_count, offset, out_cheats));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat,
|
||||
u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.GetCheatById(out_cheat, cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ToggleCheat(u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.ToggleCheat(cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::AddCheat(
|
||||
Out<u32> out_cheat_id, bool is_enabled,
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) {
|
||||
LOG_INFO(CheatEngine, "called, is_enabled={}", is_enabled);
|
||||
R_RETURN(cheat_process_manager.AddCheat(*out_cheat_id, is_enabled, *cheat_definition));
|
||||
}
|
||||
|
||||
Result ICheatInterface::RemoveCheat(u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.RemoveCheat(cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadStaticRegister(Out<u64> out_value, u8 register_index) {
|
||||
LOG_DEBUG(CheatEngine, "called, register_index={}", register_index);
|
||||
R_RETURN(cheat_process_manager.ReadStaticRegister(*out_value, register_index));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteStaticRegister(u8 register_index, u64 value) {
|
||||
LOG_DEBUG(CheatEngine, "called, register_index={}, value={}", register_index, value);
|
||||
R_RETURN(cheat_process_manager.WriteStaticRegister(register_index, value));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResetStaticRegisters() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResetStaticRegisters());
|
||||
}
|
||||
|
||||
Result ICheatInterface::SetMasterCheat(
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) {
|
||||
LOG_INFO(CheatEngine, "called, name={}, num_opcodes={}", cheat_definition->readable_name.data(),
|
||||
cheat_definition->num_opcodes);
|
||||
R_RETURN(cheat_process_manager.SetMasterCheat(*cheat_definition));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddressCount(Out<u64> out_count) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddressCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddresses(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_frozen_address.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddresses(*out_count, offset, out_frozen_address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry,
|
||||
u64 address) {
|
||||
LOG_INFO(CheatEngine, "called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddress(*out_frozen_address_entry, address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width) {
|
||||
LOG_INFO(CheatEngine, "called, address={}, width={}", address, width);
|
||||
R_UNLESS(width > 0, ResultFrozenAddressInvalidWidth);
|
||||
R_UNLESS(width <= sizeof(u64), ResultFrozenAddressInvalidWidth);
|
||||
R_UNLESS((width & (width - 1)) == 0, ResultFrozenAddressInvalidWidth);
|
||||
R_RETURN(cheat_process_manager.EnableFrozenAddress(*out_value, address, width));
|
||||
}
|
||||
|
||||
Result ICheatInterface::DisableFrozenAddress(u64 address) {
|
||||
LOG_INFO(CheatEngine, "called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.DisableFrozenAddress(address));
|
||||
}
|
||||
|
||||
void ICheatInterface::InitializeCheatManager() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data,
|
||||
size_t size) {
|
||||
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size);
|
||||
R_RETURN(cheat_process_manager.ReadCheatProcessMemoryUnsafe(process_addr, &out_data, size));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data,
|
||||
size_t size) {
|
||||
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size);
|
||||
R_RETURN(cheat_process_manager.WriteCheatProcessMemoryUnsafe(process_addr, &data, size));
|
||||
}
|
||||
|
||||
Result ICheatInterface::PauseCheatProcessUnsafe() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.PauseCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResumeCheatProcessUnsafe() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResumeCheatProcessUnsafe());
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,88 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Kernel::Svc {
|
||||
struct MemoryInfo;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatDefinition;
|
||||
struct CheatEntry;
|
||||
struct CheatProcessMetadata;
|
||||
struct FrozenAddressEntry;
|
||||
class CheatProcessManager;
|
||||
|
||||
class ICheatInterface final : public ServiceFramework<ICheatInterface> {
|
||||
public:
|
||||
explicit ICheatInterface(Core::System& system_, CheatProcessManager& manager);
|
||||
~ICheatInterface() override;
|
||||
|
||||
private:
|
||||
Result HasCheatProcess(Out<bool> out_has_cheat);
|
||||
Result GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata);
|
||||
Result ForceOpenCheatProcess();
|
||||
Result PauseCheatProcess();
|
||||
Result ResumeCheatProcess();
|
||||
Result ForceCloseCheatProcess();
|
||||
|
||||
Result GetCheatProcessMappingCount(Out<u64> out_count);
|
||||
Result GetCheatProcessMappings(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings);
|
||||
Result ReadCheatProcessMemory(u64 address, u64 size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer);
|
||||
Result WriteCheatProcessMemory(u64 address, u64 size, InBuffer<BufferAttr_HipcMapAlias> buffer);
|
||||
|
||||
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping, u64 address);
|
||||
Result GetCheatCount(Out<u64> out_count);
|
||||
Result GetCheats(Out<u64> out_count, u64 offset,
|
||||
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats);
|
||||
Result GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
|
||||
Result AddCheat(Out<u32> out_cheat_id, bool enabled,
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
Result ReadStaticRegister(Out<u64> out_value, u8 register_index);
|
||||
Result WriteStaticRegister(u8 register_index, u64 value);
|
||||
Result ResetStaticRegisters();
|
||||
Result SetMasterCheat(InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition);
|
||||
Result GetFrozenAddressCount(Out<u64> out_count);
|
||||
Result GetFrozenAddresses(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address);
|
||||
Result GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry, u64 address);
|
||||
Result EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
private:
|
||||
void InitializeCheatManager();
|
||||
|
||||
Result ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data, size_t size);
|
||||
Result WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data, size_t size);
|
||||
|
||||
Result PauseCheatProcessUnsafe();
|
||||
Result ResumeCheatProcessUnsafe();
|
||||
|
||||
CheatProcessManager& cheat_process_manager;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,121 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "core/hle/service/dmnt/cheat_parser.h"
|
||||
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
CheatParser::CheatParser() {}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
std::vector<CheatEntry> CheatParser::Parse(std::string_view data) const {
|
||||
std::vector<CheatEntry> out(1);
|
||||
std::optional<u64> current_entry;
|
||||
|
||||
for (std::size_t i = 0; i < data.size(); ++i) {
|
||||
if (std::isspace(data[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data[i] == '{') {
|
||||
current_entry = 0;
|
||||
|
||||
if (out[*current_entry].definition.num_opcodes > 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, '}');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (data[i] == '[') {
|
||||
current_entry = out.size();
|
||||
out.emplace_back();
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, ']');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (std::isxdigit(data[i])) {
|
||||
if (!current_entry || out[*current_entry].definition.num_opcodes >=
|
||||
out[*current_entry].definition.opcodes.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto hex = std::string(data.substr(i, 8));
|
||||
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
|
||||
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
|
||||
value;
|
||||
|
||||
i += 7; // 7 because the for loop will increment by 1 more
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out[0].enabled = out[0].definition.num_opcodes > 0;
|
||||
out[0].cheat_id = 0;
|
||||
|
||||
for (u32 i = 1; i < out.size(); ++i) {
|
||||
out[i].enabled = out[i].definition.num_opcodes > 0;
|
||||
out[i].cheat_id = i;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string_view CheatParser::ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||
std::size_t start_index, char match) const {
|
||||
auto end_index = start_index;
|
||||
while (data[end_index] != match) {
|
||||
++end_index;
|
||||
if (end_index > data.size()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out_name_size = end_index - start_index;
|
||||
|
||||
// Clamp name if it's too big
|
||||
if (out_name_size > sizeof(CheatDefinition::readable_name)) {
|
||||
end_index = start_index + sizeof(CheatDefinition::readable_name);
|
||||
}
|
||||
|
||||
return data.substr(start_index, end_index - start_index);
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatEntry;
|
||||
|
||||
class CheatParser final {
|
||||
public:
|
||||
CheatParser();
|
||||
~CheatParser();
|
||||
|
||||
std::vector<CheatEntry> Parse(std::string_view data) const;
|
||||
|
||||
private:
|
||||
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||
std::size_t start_index, char match) const;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,599 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/arm/debug.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
|
||||
#include "core/hle/service/dmnt/dmnt_results.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "hid_core/resource_manager.h"
|
||||
#include "hid_core/resources/npad/npad.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
|
||||
|
||||
CheatProcessManager::CheatProcessManager(Core::System& system_)
|
||||
: system{system_}, service_context{system_, "dmnt:cht"}, core_timing{system_.CoreTiming()} {
|
||||
update_event = Core::Timing::CreateEvent("CheatEngine::FrameCallback",
|
||||
[this](s64 time, std::chrono::nanoseconds ns_late)
|
||||
-> std::optional<std::chrono::nanoseconds> {
|
||||
FrameCallback(ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < MaxCheatCount; i++) {
|
||||
ResetCheatEntry(i);
|
||||
}
|
||||
|
||||
cheat_vm = std::make_unique<CheatVirtualMachine>(*this);
|
||||
|
||||
cheat_process_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent");
|
||||
unsafe_break_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent");
|
||||
}
|
||||
|
||||
CheatProcessManager::~CheatProcessManager() {
|
||||
service_context.CloseEvent(cheat_process_event);
|
||||
service_context.CloseEvent(unsafe_break_event);
|
||||
core_timing.UnscheduleEvent(update_event);
|
||||
}
|
||||
|
||||
void CheatProcessManager::SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm) {
|
||||
if (vm) {
|
||||
cheat_vm = std::move(vm);
|
||||
SetNeedsReloadVm(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool CheatProcessManager::HasActiveCheatProcess() {
|
||||
// Note: This function *MUST* be called only with the cheat lock held.
|
||||
bool has_cheat_process =
|
||||
cheat_process_debug_handle != InvalidHandle &&
|
||||
system.ApplicationProcess()->GetProcessId() == cheat_process_metadata.process_id;
|
||||
|
||||
if (!has_cheat_process) {
|
||||
CloseActiveCheatProcess();
|
||||
}
|
||||
|
||||
return has_cheat_process;
|
||||
}
|
||||
|
||||
void CheatProcessManager::CloseActiveCheatProcess() {
|
||||
if (cheat_process_debug_handle != InvalidHandle) {
|
||||
broken_unsafe = false;
|
||||
unsafe_break_event->Signal();
|
||||
core_timing.UnscheduleEvent(update_event);
|
||||
|
||||
// Close resources.
|
||||
cheat_process_debug_handle = InvalidHandle;
|
||||
|
||||
// Save cheat toggles.
|
||||
if (always_save_cheat_toggles || should_save_cheat_toggles) {
|
||||
// TODO: save cheat toggles
|
||||
should_save_cheat_toggles = false;
|
||||
}
|
||||
|
||||
cheat_process_metadata = {};
|
||||
|
||||
ResetAllCheatEntries();
|
||||
|
||||
{
|
||||
auto it = frozen_addresses_map.begin();
|
||||
while (it != frozen_addresses_map.end()) {
|
||||
it = frozen_addresses_map.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
cheat_process_event->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
Result CheatProcessManager::EnsureCheatProcess() {
|
||||
R_UNLESS(HasActiveCheatProcess(), ResultCheatNotAttached);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void CheatProcessManager::SetNeedsReloadVm(bool reload) {
|
||||
needs_reload_vm = reload;
|
||||
}
|
||||
|
||||
void CheatProcessManager::ResetCheatEntry(size_t i) {
|
||||
if (i < MaxCheatCount) {
|
||||
cheat_entries[i] = {};
|
||||
cheat_entries[i].cheat_id = static_cast<u32>(i);
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatProcessManager::ResetAllCheatEntries() {
|
||||
for (size_t i = 0; i < MaxCheatCount; i++) {
|
||||
ResetCheatEntry(i);
|
||||
}
|
||||
|
||||
cheat_vm->ResetStaticRegisters();
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetCheatEntryById(size_t i) {
|
||||
if (i < MaxCheatCount) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetCheatEntryByReadableName(const char* readable_name) {
|
||||
for (size_t i = 1; i < MaxCheatCount; i++) {
|
||||
if (std::strncmp(cheat_entries[i].definition.readable_name.data(), readable_name,
|
||||
sizeof(cheat_entries[i].definition.readable_name)) == 0) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetFreeCheatEntry() {
|
||||
// Check all non-master cheats for availability.
|
||||
for (size_t i = 1; i < MaxCheatCount; i++) {
|
||||
if (cheat_entries[i].definition.num_opcodes == 0) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CheatProcessManager::HasCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
return HasActiveCheatProcess();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& CheatProcessManager::GetCheatProcessEvent() const {
|
||||
return cheat_process_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::AttachToApplicationProcess(const std::array<u8, 0x20>& build_id,
|
||||
VAddr main_region_begin,
|
||||
u64 main_region_size) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
{
|
||||
if (this->HasActiveCheatProcess()) {
|
||||
this->CloseActiveCheatProcess();
|
||||
}
|
||||
}
|
||||
|
||||
cheat_process_metadata.process_id = system.ApplicationProcess()->GetProcessId();
|
||||
|
||||
{
|
||||
const auto& page_table = system.ApplicationProcess()->GetPageTable();
|
||||
cheat_process_metadata.program_id = system.GetApplicationProcessProgramID();
|
||||
cheat_process_metadata.heap_extents = {
|
||||
.base = GetInteger(page_table.GetHeapRegionStart()),
|
||||
.size = page_table.GetHeapRegionSize(),
|
||||
};
|
||||
cheat_process_metadata.aslr_extents = {
|
||||
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
|
||||
.size = page_table.GetAliasCodeRegionSize(),
|
||||
};
|
||||
cheat_process_metadata.alias_extents = {
|
||||
.base = GetInteger(page_table.GetAliasRegionStart()),
|
||||
.size = page_table.GetAliasRegionSize(),
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
cheat_process_metadata.main_nso_extents = {
|
||||
.base = main_region_begin,
|
||||
.size = main_region_size,
|
||||
};
|
||||
cheat_process_metadata.main_nso_build_id = build_id;
|
||||
}
|
||||
|
||||
cheat_process_debug_handle = cheat_process_metadata.process_id;
|
||||
|
||||
broken_unsafe = false;
|
||||
unsafe_break_event->Signal();
|
||||
|
||||
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, update_event);
|
||||
LOG_INFO(CheatEngine, "Cheat engine started");
|
||||
|
||||
// Signal to our fans.
|
||||
cheat_process_event->Signal();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMetadata(CheatProcessMetadata& out_metadata) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_metadata = cheat_process_metadata;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ForceOpenCheatProcess() {
|
||||
// R_RETURN(AttachToApplicationProcess(false));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::PauseCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(PauseCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result CheatProcessManager::PauseCheatProcessUnsafe() {
|
||||
broken_unsafe = true;
|
||||
unsafe_break_event->Clear();
|
||||
if (system.ApplicationProcess()->IsSuspended()) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
R_RETURN(system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResumeCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(ResumeCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResumeCheatProcessUnsafe() {
|
||||
broken_unsafe = true;
|
||||
unsafe_break_event->Clear();
|
||||
if (!system.ApplicationProcess()->IsSuspended()) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ForceCloseCheatProcess() {
|
||||
CloseActiveCheatProcess();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMappingCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
||||
out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMappings(
|
||||
u64& out_count, u64 offset, std::span<Kernel::Svc::MemoryInfo> out_mappings) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
||||
out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadCheatProcessMemory(u64 process_address, u64 size,
|
||||
std::span<u8> out_data) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(ReadCheatProcessMemoryUnsafe(process_address, &out_data, size));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data,
|
||||
size_t size) {
|
||||
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) {
|
||||
std::memset(out_data, 0, size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
system.ApplicationMemory().ReadBlock(process_address, out_data, size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteCheatProcessMemory(u64 process_address, u64 size,
|
||||
std::span<const u8> data) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(WriteCheatProcessMemoryUnsafe(process_address, &data, size));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data,
|
||||
size_t size) {
|
||||
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
if (system.ApplicationMemory().WriteBlock(process_address, data, size)) {
|
||||
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), process_address, size);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping,
|
||||
u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_count = std::count_if(cheat_entries.begin(), cheat_entries.end(),
|
||||
[](const auto& entry) { return entry.definition.num_opcodes != 0; });
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheats(u64& out_count, u64 offset,
|
||||
std::span<CheatEntry> out_cheats) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
size_t count = 0, total_count = 0;
|
||||
for (size_t i = 0; i < MaxCheatCount && count < out_cheats.size(); i++) {
|
||||
if (cheat_entries[i].definition.num_opcodes) {
|
||||
total_count++;
|
||||
if (total_count > offset) {
|
||||
out_cheats[count++] = cheat_entries[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out_count = count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatById(CheatEntry* out_cheat, u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const CheatEntry* entry = GetCheatEntryById(cheat_id);
|
||||
R_UNLESS(entry != nullptr, ResultCheatUnknownId);
|
||||
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId);
|
||||
|
||||
*out_cheat = *entry;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ToggleCheat(u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
CheatEntry* entry = GetCheatEntryById(cheat_id);
|
||||
R_UNLESS(entry != nullptr, ResultCheatUnknownId);
|
||||
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId);
|
||||
|
||||
R_UNLESS(cheat_id != 0, ResultCheatCannotDisable);
|
||||
|
||||
entry->enabled = !entry->enabled;
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::AddCheat(u32& out_cheat_id, bool enabled,
|
||||
const CheatDefinition& cheat_definition) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid);
|
||||
R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid);
|
||||
|
||||
CheatEntry* new_entry = GetFreeCheatEntry();
|
||||
R_UNLESS(new_entry != nullptr, ResultCheatOutOfResource);
|
||||
|
||||
new_entry->enabled = enabled;
|
||||
new_entry->definition = cheat_definition;
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
out_cheat_id = new_entry->cheat_id;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::RemoveCheat(u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(cheat_id < MaxCheatCount, ResultCheatUnknownId);
|
||||
|
||||
ResetCheatEntry(cheat_id);
|
||||
SetNeedsReloadVm(true);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadStaticRegister(u64& out_value, u64 register_index) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid);
|
||||
|
||||
out_value = cheat_vm->GetStaticRegister(register_index);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteStaticRegister(u64 register_index, u64 value) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid);
|
||||
|
||||
cheat_vm->SetStaticRegister(register_index, value);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResetStaticRegisters() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
cheat_vm->ResetStaticRegisters();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::SetMasterCheat(const CheatDefinition& cheat_definition) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid);
|
||||
R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid);
|
||||
|
||||
cheat_entries[0] = {
|
||||
.enabled = true,
|
||||
.definition = cheat_definition,
|
||||
};
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddressCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_count = std::distance(frozen_addresses_map.begin(), frozen_addresses_map.end());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddresses(u64& out_count, u64 offset,
|
||||
std::span<FrozenAddressEntry> out_frozen_address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
u64 total_count = 0, written_count = 0;
|
||||
for (const auto& [address, value] : frozen_addresses_map) {
|
||||
if (written_count >= out_frozen_address.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset <= total_count) {
|
||||
out_frozen_address[written_count].address = address;
|
||||
out_frozen_address[written_count].value = value;
|
||||
written_count++;
|
||||
}
|
||||
total_count++;
|
||||
}
|
||||
|
||||
out_count = written_count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry,
|
||||
u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound);
|
||||
|
||||
out_frozen_address_entry = {
|
||||
.address = it->first,
|
||||
.value = it->second,
|
||||
};
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::EnableFrozenAddress(u64& out_value, u64 address, u64 width) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it == frozen_addresses_map.end(), ResultFrozenAddressAlreadyExists);
|
||||
|
||||
FrozenAddressValue value{};
|
||||
value.width = static_cast<u8>(width);
|
||||
R_TRY(ReadCheatProcessMemoryUnsafe(address, &value.value, width));
|
||||
|
||||
frozen_addresses_map.insert({address, value});
|
||||
out_value = value.value;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::DisableFrozenAddress(u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound);
|
||||
|
||||
frozen_addresses_map.erase(it);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
u64 CheatProcessManager::HidKeysDown() const {
|
||||
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
|
||||
if (hid == nullptr) {
|
||||
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto applet_resource = hid->GetResourceManager();
|
||||
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) {
|
||||
LOG_WARNING(CheatEngine,
|
||||
"Attempted to read input state, but applet resource is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
|
||||
return static_cast<u64>(press_state & Core::HID::NpadButton::All);
|
||||
}
|
||||
|
||||
void CheatProcessManager::DebugLog(u8 id, u64 value) const {
|
||||
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
|
||||
}
|
||||
|
||||
void CheatProcessManager::CommandLog(std::string_view data) const {
|
||||
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
|
||||
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
|
||||
}
|
||||
|
||||
void CheatProcessManager::FrameCallback(std::chrono::nanoseconds ns_late) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
if (cheat_vm == nullptr) {
|
||||
LOG_DEBUG(CheatEngine, "FrameCallback: VM is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (needs_reload_vm) {
|
||||
LOG_INFO(CheatEngine, "Reloading cheat VM with {} entries", cheat_entries.size());
|
||||
|
||||
size_t enabled_count = 0;
|
||||
for (const auto& entry : cheat_entries) {
|
||||
if (entry.enabled && entry.definition.num_opcodes > 0) {
|
||||
enabled_count++;
|
||||
LOG_INFO(CheatEngine, " Cheat '{}': {} opcodes, enabled={}",
|
||||
entry.definition.readable_name.data(),
|
||||
entry.definition.num_opcodes, entry.enabled);
|
||||
}
|
||||
}
|
||||
LOG_INFO(CheatEngine, "Total enabled cheats: {}", enabled_count);
|
||||
|
||||
cheat_vm->LoadProgram(cheat_entries);
|
||||
LOG_INFO(CheatEngine, "Cheat VM loaded, program size: {}", cheat_vm->GetProgramSize());
|
||||
needs_reload_vm = false;
|
||||
}
|
||||
|
||||
if (cheat_vm->GetProgramSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cheat_vm->Execute(cheat_process_metadata);
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,126 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <span>
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
#include "common/intrusive_red_black_tree.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Kernel::Svc {
|
||||
struct MemoryInfo;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatVirtualMachine;
|
||||
|
||||
class CheatProcessManager final {
|
||||
public:
|
||||
static constexpr size_t MaxCheatCount = 0x80;
|
||||
static constexpr size_t MaxFrozenAddressCount = 0x80;
|
||||
|
||||
CheatProcessManager(Core::System& system_);
|
||||
~CheatProcessManager();
|
||||
|
||||
void SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm);
|
||||
|
||||
bool HasCheatProcess();
|
||||
Kernel::KReadableEvent& GetCheatProcessEvent() const;
|
||||
Result GetCheatProcessMetadata(CheatProcessMetadata& out_metadata);
|
||||
Result AttachToApplicationProcess(const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
|
||||
u64 main_region_size);
|
||||
Result ForceOpenCheatProcess();
|
||||
Result PauseCheatProcess();
|
||||
Result PauseCheatProcessUnsafe();
|
||||
Result ResumeCheatProcess();
|
||||
Result ResumeCheatProcessUnsafe();
|
||||
Result ForceCloseCheatProcess();
|
||||
|
||||
Result GetCheatProcessMappingCount(u64& out_count);
|
||||
Result GetCheatProcessMappings(u64& out_count, u64 offset,
|
||||
std::span<Kernel::Svc::MemoryInfo> out_mappings);
|
||||
Result ReadCheatProcessMemory(u64 process_address, u64 size, std::span<u8> out_data);
|
||||
Result ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, size_t size);
|
||||
Result WriteCheatProcessMemory(u64 process_address, u64 size, std::span<const u8> data);
|
||||
Result WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, size_t size);
|
||||
|
||||
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping, u64 address);
|
||||
Result GetCheatCount(u64& out_count);
|
||||
Result GetCheats(u64& out_count, u64 offset, std::span<CheatEntry> out_cheats);
|
||||
Result GetCheatById(CheatEntry* out_cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
|
||||
Result AddCheat(u32& out_cheat_id, bool enabled, const CheatDefinition& cheat_definition);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
Result ReadStaticRegister(u64& out_value, u64 register_index);
|
||||
Result WriteStaticRegister(u64 register_index, u64 value);
|
||||
Result ResetStaticRegisters();
|
||||
Result SetMasterCheat(const CheatDefinition& cheat_definition);
|
||||
Result GetFrozenAddressCount(u64& out_count);
|
||||
Result GetFrozenAddresses(u64& out_count, u64 offset,
|
||||
std::span<FrozenAddressEntry> out_frozen_address);
|
||||
Result GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry, u64 address);
|
||||
Result EnableFrozenAddress(u64& out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
u64 HidKeysDown() const;
|
||||
void DebugLog(u8 id, u64 value) const;
|
||||
void CommandLog(std::string_view data) const;
|
||||
|
||||
private:
|
||||
bool HasActiveCheatProcess();
|
||||
void CloseActiveCheatProcess();
|
||||
Result EnsureCheatProcess();
|
||||
void SetNeedsReloadVm(bool reload);
|
||||
void ResetCheatEntry(size_t i);
|
||||
void ResetAllCheatEntries();
|
||||
CheatEntry* GetCheatEntryById(size_t i);
|
||||
CheatEntry* GetCheatEntryByReadableName(const char* readable_name);
|
||||
CheatEntry* GetFreeCheatEntry();
|
||||
|
||||
void FrameCallback(std::chrono::nanoseconds ns_late);
|
||||
|
||||
static constexpr u64 InvalidHandle = 0;
|
||||
|
||||
mutable std::mutex cheat_lock;
|
||||
Kernel::KEvent* unsafe_break_event;
|
||||
|
||||
Kernel::KEvent* cheat_process_event;
|
||||
u64 cheat_process_debug_handle = InvalidHandle;
|
||||
CheatProcessMetadata cheat_process_metadata = {};
|
||||
|
||||
bool broken_unsafe = false;
|
||||
bool needs_reload_vm = false;
|
||||
std::unique_ptr<CheatVirtualMachine> cheat_vm;
|
||||
|
||||
bool enable_cheats_by_default = true;
|
||||
bool always_save_cheat_toggles = false;
|
||||
bool should_save_cheat_toggles = false;
|
||||
std::array<CheatEntry, MaxCheatCount> cheat_entries = {};
|
||||
// TODO: Replace with IntrusiveRedBlackTree
|
||||
std::map<u64, FrozenAddressValue> frozen_addresses_map = {};
|
||||
|
||||
Core::System& system;
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
std::shared_ptr<Core::Timing::EventType> update_event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,323 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <variant>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatProcessManager;
|
||||
|
||||
enum class CheatVmOpcodeType : u32 {
|
||||
StoreStatic = 0,
|
||||
BeginConditionalBlock = 1,
|
||||
EndConditionalBlock = 2,
|
||||
ControlLoop = 3,
|
||||
LoadRegisterStatic = 4,
|
||||
LoadRegisterMemory = 5,
|
||||
StoreStaticToAddress = 6,
|
||||
PerformArithmeticStatic = 7,
|
||||
BeginKeypressConditionalBlock = 8,
|
||||
|
||||
// These are not implemented by Gateway's VM.
|
||||
PerformArithmeticRegister = 9,
|
||||
StoreRegisterToAddress = 10,
|
||||
Reserved11 = 11,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
ExtendedWidth = 12,
|
||||
|
||||
// Extended width opcodes.
|
||||
BeginRegisterConditionalBlock = 0xC0,
|
||||
SaveRestoreRegister = 0xC1,
|
||||
SaveRestoreRegisterMask = 0xC2,
|
||||
ReadWriteStaticRegister = 0xC3,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
DoubleExtendedWidth = 0xF0,
|
||||
|
||||
// Double-extended width opcodes.
|
||||
PauseProcess = 0xFF0,
|
||||
ResumeProcess = 0xFF1,
|
||||
DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum class MemoryAccessType : u32 {
|
||||
MainNso = 0,
|
||||
Heap = 1,
|
||||
Alias = 2,
|
||||
Aslr = 3,
|
||||
};
|
||||
|
||||
enum class ConditionalComparisonType : u32 {
|
||||
GT = 1,
|
||||
GE = 2,
|
||||
LT = 3,
|
||||
LE = 4,
|
||||
EQ = 5,
|
||||
NE = 6,
|
||||
};
|
||||
|
||||
enum class RegisterArithmeticType : u32 {
|
||||
Addition = 0,
|
||||
Subtraction = 1,
|
||||
Multiplication = 2,
|
||||
LeftShift = 3,
|
||||
RightShift = 4,
|
||||
|
||||
// These are not supported by Gateway's VM.
|
||||
LogicalAnd = 5,
|
||||
LogicalOr = 6,
|
||||
LogicalNot = 7,
|
||||
LogicalXor = 8,
|
||||
|
||||
None = 9,
|
||||
};
|
||||
|
||||
enum class StoreRegisterOffsetType : u32 {
|
||||
None = 0,
|
||||
Reg = 1,
|
||||
Imm = 2,
|
||||
MemReg = 3,
|
||||
MemImm = 4,
|
||||
MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum class CompareRegisterValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
StaticValue = 4,
|
||||
OtherRegister = 5,
|
||||
};
|
||||
|
||||
enum class SaveRestoreRegisterOpType : u32 {
|
||||
Restore = 0,
|
||||
Save = 1,
|
||||
ClearSaved = 2,
|
||||
ClearRegs = 3,
|
||||
};
|
||||
|
||||
enum class DebugLogValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
RegisterValue = 4,
|
||||
};
|
||||
|
||||
union VmInt {
|
||||
u8 bit8;
|
||||
u16 bit16;
|
||||
u32 bit32;
|
||||
u64 bit64;
|
||||
};
|
||||
|
||||
struct StoreStaticOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 offset_register{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct BeginConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {
|
||||
bool is_else;
|
||||
};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop{};
|
||||
u32 reg_index{};
|
||||
u32 num_iters{};
|
||||
};
|
||||
|
||||
struct LoadRegisterStaticOpcode {
|
||||
u32 reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct LoadRegisterMemoryOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 reg_index{};
|
||||
bool load_from_reg{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct StoreStaticToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
bool increment_reg{};
|
||||
bool add_offset_reg{};
|
||||
u32 offset_reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticStaticOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 value{};
|
||||
};
|
||||
|
||||
struct BeginKeypressConditionalOpcode {
|
||||
u32 key_mask{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticRegisterOpcode {
|
||||
u32 bit_width{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 dst_reg_index{};
|
||||
u32 src_reg_1_index{};
|
||||
u32 src_reg_2_index{};
|
||||
bool has_immediate{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct StoreRegisterToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 str_reg_index{};
|
||||
u32 addr_reg_index{};
|
||||
bool increment_reg{};
|
||||
StoreRegisterOffsetType ofs_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u32 val_reg_index{};
|
||||
CompareRegisterValueType comp_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 other_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterOpcode {
|
||||
u32 dst_index{};
|
||||
u32 src_index{};
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterMaskOpcode {
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
std::array<bool, 0x10> should_operate{};
|
||||
};
|
||||
|
||||
struct ReadWriteStaticRegisterOpcode {
|
||||
u32 static_idx{};
|
||||
u32 idx{};
|
||||
};
|
||||
|
||||
struct PauseProcessOpcode {};
|
||||
|
||||
struct ResumeProcessOpcode {};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width{};
|
||||
u32 log_id{};
|
||||
DebugLogValueType val_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 val_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct UnrecognizedInstruction {
|
||||
CheatVmOpcodeType opcode{};
|
||||
};
|
||||
|
||||
struct CheatVmOpcode {
|
||||
bool begin_conditional_block{};
|
||||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
|
||||
opcode{};
|
||||
};
|
||||
|
||||
class CheatVirtualMachine {
|
||||
public:
|
||||
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
|
||||
static constexpr std::size_t NumRegisters = 0x10;
|
||||
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumWritableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumStaticRegisters =
|
||||
NumReadableStaticRegisters + NumWritableStaticRegisters;
|
||||
|
||||
explicit CheatVirtualMachine(CheatProcessManager& cheat_manager);
|
||||
~CheatVirtualMachine();
|
||||
|
||||
std::size_t GetProgramSize() const {
|
||||
return this->num_opcodes;
|
||||
}
|
||||
|
||||
bool LoadProgram(std::span<const CheatEntry> cheats);
|
||||
void Execute(const CheatProcessMetadata& metadata);
|
||||
|
||||
u64 GetStaticRegister(std::size_t register_index) const {
|
||||
return static_registers[register_index];
|
||||
}
|
||||
|
||||
void SetStaticRegister(std::size_t register_index, u64 value) {
|
||||
static_registers[register_index] = value;
|
||||
}
|
||||
|
||||
void ResetStaticRegisters() {
|
||||
static_registers = {};
|
||||
}
|
||||
|
||||
private:
|
||||
bool DecodeNextOpcode(CheatVmOpcode& out);
|
||||
void SkipConditionalBlock(bool is_if);
|
||||
void ResetState();
|
||||
|
||||
// For implementing the DebugLog opcode.
|
||||
void DebugLog(u32 log_id, u64 value) const;
|
||||
|
||||
void LogOpcode(const CheatVmOpcode& opcode) const;
|
||||
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
|
||||
MemoryAccessType mem_type, u64 rel_address);
|
||||
|
||||
CheatProcessManager& manager;
|
||||
|
||||
std::size_t num_opcodes = 0;
|
||||
std::size_t instruction_ptr = 0;
|
||||
std::size_t condition_depth = 0;
|
||||
bool decode_success = false;
|
||||
std::array<u32, MaximumProgramOpcodeCount> program{};
|
||||
std::array<u64, NumRegisters> registers{};
|
||||
std::array<u64, NumRegisters> saved_values{};
|
||||
std::array<u64, NumStaticRegisters> static_registers{};
|
||||
std::array<std::size_t, NumRegisters> loop_tops{};
|
||||
};
|
||||
}// namespace Service::DMNT
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/dmnt/cheat_interface.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
|
||||
#include "core/hle/service/dmnt/dmnt.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
auto& cheat_manager = system.GetCheatManager();
|
||||
auto cheat_vm = std::make_unique<CheatVirtualMachine>(cheat_manager);
|
||||
cheat_manager.SetVirtualMachine(std::move(cheat_vm));
|
||||
|
||||
server_manager->RegisterNamedService("dmnt:cht",
|
||||
std::make_shared<ICheatInterface>(system, cheat_manager));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,15 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
};
|
||||
|
||||
namespace Service::DMNT {
|
||||
void LoopProcess(Core::System& system);
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
constexpr Result ResultDebuggingDisabled(ErrorModule::DMNT, 2);
|
||||
|
||||
constexpr Result ResultCheatNotAttached(ErrorModule::DMNT, 6500);
|
||||
constexpr Result ResultCheatNullBuffer(ErrorModule::DMNT, 6501);
|
||||
constexpr Result ResultCheatInvalidBuffer(ErrorModule::DMNT, 6502);
|
||||
constexpr Result ResultCheatUnknownId(ErrorModule::DMNT, 6503);
|
||||
constexpr Result ResultCheatOutOfResource(ErrorModule::DMNT, 6504);
|
||||
constexpr Result ResultCheatInvalid(ErrorModule::DMNT, 6505);
|
||||
constexpr Result ResultCheatCannotDisable(ErrorModule::DMNT, 6506);
|
||||
constexpr Result ResultFrozenAddressInvalidWidth(ErrorModule::DMNT, 6600);
|
||||
constexpr Result ResultFrozenAddressAlreadyExists(ErrorModule::DMNT, 6601);
|
||||
constexpr Result ResultFrozenAddressNotFound(ErrorModule::DMNT, 6602);
|
||||
constexpr Result ResultFrozenAddressOutOfResource(ErrorModule::DMNT, 6603);
|
||||
constexpr Result ResultVirtualMachineInvalidConditionDepth(ErrorModule::DMNT, 6700);
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,55 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct MemoryRegionExtents {
|
||||
u64 base{};
|
||||
u64 size{};
|
||||
};
|
||||
static_assert(sizeof(MemoryRegionExtents) == 0x10, "MemoryRegionExtents is an invalid size");
|
||||
|
||||
struct CheatProcessMetadata {
|
||||
u64 process_id{};
|
||||
u64 program_id{};
|
||||
MemoryRegionExtents main_nso_extents{};
|
||||
MemoryRegionExtents heap_extents{};
|
||||
MemoryRegionExtents alias_extents{};
|
||||
MemoryRegionExtents aslr_extents{};
|
||||
std::array<u8, 0x20> main_nso_build_id{};
|
||||
};
|
||||
static_assert(sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata is an invalid size");
|
||||
|
||||
struct CheatDefinition {
|
||||
std::array<char, 0x40> readable_name;
|
||||
u32 num_opcodes;
|
||||
std::array<u32, 0x100> opcodes;
|
||||
};
|
||||
static_assert(sizeof(CheatDefinition) == 0x444, "CheatDefinition is an invalid size");
|
||||
|
||||
struct CheatEntry {
|
||||
bool enabled;
|
||||
u32 cheat_id;
|
||||
CheatDefinition definition;
|
||||
};
|
||||
static_assert(sizeof(CheatEntry) == 0x44C, "CheatEntry is an invalid size");
|
||||
static_assert(std::is_trivial_v<CheatEntry>, "CheatEntry type must be trivially copyable.");
|
||||
|
||||
struct FrozenAddressValue {
|
||||
u64 value;
|
||||
u8 width;
|
||||
};
|
||||
static_assert(sizeof(FrozenAddressValue) == 0x10, "FrozenAddressValue is an invalid size");
|
||||
|
||||
struct FrozenAddressEntry {
|
||||
u64 address;
|
||||
FrozenAddressValue value;
|
||||
};
|
||||
static_assert(sizeof(FrozenAddressEntry) == 0x18, "FrozenAddressEntry is an invalid size");
|
||||
} // namespace Service::DMNT
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -70,7 +67,7 @@ public:
|
||||
{20701, &IFriendService::GetPlayHistoryStatistics, "GetPlayHistoryStatistics"},
|
||||
{20800, &IFriendService::LoadUserSetting, "LoadUserSetting"},
|
||||
{20801, nullptr, "SyncUserSetting"},
|
||||
{20900, &IFriendService::RequestListSummaryOverlayNotification, "RequestListSummaryOverlayNotification"},
|
||||
{20900, nullptr, "RequestListSummaryOverlayNotification"},
|
||||
{21000, nullptr, "GetExternalApplicationCatalog"},
|
||||
{22000, nullptr, "GetReceivedFriendInvitationList"},
|
||||
{22001, nullptr, "GetReceivedFriendInvitationDetailedInfo"},
|
||||
@@ -320,13 +317,6 @@ private:
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void RequestListSummaryOverlayNotification(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_Friend, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void GetReceivedFriendInvitationCountCache(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Friend, "(STUBBED) called, check in out");
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/application_manager_interface.h"
|
||||
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/hle/service/ns/content_management_interface.h"
|
||||
#include "core/hle/service/ns/read_only_application_control_data_interface.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -56,7 +54,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
||||
{37, nullptr, "ListRequiredVersion"},
|
||||
{38, D<&IApplicationManagerInterface::CheckApplicationLaunchVersion>, "CheckApplicationLaunchVersion"},
|
||||
{39, nullptr, "CheckApplicationLaunchRights"},
|
||||
{40, D<&IApplicationManagerInterface::GetApplicationLogoData>, "GetApplicationLogoData"},
|
||||
{40, nullptr, "GetApplicationLogoData"},
|
||||
{41, nullptr, "CalculateApplicationDownloadRequiredSize"},
|
||||
{42, nullptr, "CleanupSdCard"},
|
||||
{43, D<&IApplicationManagerInterface::CheckSdCardMountStatus>, "CheckSdCardMountStatus"},
|
||||
@@ -333,53 +331,6 @@ Result IApplicationManagerInterface::UnregisterNetworkServiceAccountWithUserSave
|
||||
LOG_DEBUG(Service_NS, "called, user_id={}", user_id.FormattedString());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationLogoData(
|
||||
Out<s64> out_size, OutBuffer<BufferAttr_HipcMapAlias> out_buffer, u64 application_id,
|
||||
InBuffer<BufferAttr_HipcMapAlias> logo_path_buffer) {
|
||||
const std::string path_view{reinterpret_cast<const char*>(logo_path_buffer.data()),
|
||||
logo_path_buffer.size()};
|
||||
|
||||
// Find null terminator and trim the path
|
||||
auto null_pos = path_view.find('\0');
|
||||
std::string path = (null_pos != std::string::npos) ? path_view.substr(0, null_pos) : path_view;
|
||||
|
||||
LOG_DEBUG(Service_NS, "called, application_id={:016X}, logo_path={}", application_id, path);
|
||||
|
||||
auto& content_provider = system.GetContentProviderUnion();
|
||||
|
||||
auto program = content_provider.GetEntry(application_id, FileSys::ContentRecordType::Program);
|
||||
if (!program) {
|
||||
LOG_WARNING(Service_NS, "Application program not found for id={:016X}", application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto logo_dir = program->GetLogoPartition();
|
||||
if (!logo_dir) {
|
||||
LOG_WARNING(Service_NS, "Logo partition not found for id={:016X}", application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto file = logo_dir->GetFile(path);
|
||||
if (!file) {
|
||||
LOG_WARNING(Service_NS, "Logo path not found: {} for id={:016X}", path,
|
||||
application_id);
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
const auto data = file->ReadAllBytes();
|
||||
if (data.size() > out_buffer.size()) {
|
||||
LOG_WARNING(Service_NS, "Logo buffer too small: have={}, need={}", out_buffer.size(),
|
||||
data.size());
|
||||
R_RETURN(ResultUnknown);
|
||||
}
|
||||
|
||||
std::memcpy(out_buffer.data(), data.data(), data.size());
|
||||
*out_size = static_cast<s64>(data.size());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationControlData(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
|
||||
ApplicationControlSource application_control_source, u64 application_id) {
|
||||
|
||||
@@ -60,10 +60,6 @@ public:
|
||||
u64 application_id);
|
||||
Result CheckApplicationLaunchVersion(u64 application_id);
|
||||
Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
|
||||
Result GetApplicationLogoData(Out<s64> out_size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
u64 application_id,
|
||||
InBuffer<BufferAttr_HipcMapAlias> logo_path_buffer);
|
||||
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result Unknown4023(Out<u64> out_result);
|
||||
Result Unknown4053();
|
||||
|
||||
@@ -179,9 +179,4 @@ struct ApplicationDisplayData {
|
||||
};
|
||||
static_assert(sizeof(ApplicationDisplayData) == 0x300, "ApplicationDisplayData has incorrect size.");
|
||||
|
||||
struct LogoPath {
|
||||
std::array<char, 0x300> path;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<LogoPath>, "LogoPath must be trivially copyable.");
|
||||
|
||||
} // namespace Service::NS
|
||||
|
||||
@@ -15,7 +15,7 @@ struct Layer {
|
||||
explicit Layer(std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer_,
|
||||
s32 consumer_id_)
|
||||
: buffer_item_consumer(std::move(buffer_item_consumer_)), consumer_id(consumer_id_),
|
||||
blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false) {}
|
||||
blending(LayerBlending::None), visible(true), z_index(0) {}
|
||||
~Layer() {
|
||||
buffer_item_consumer->Abandon();
|
||||
}
|
||||
@@ -25,7 +25,6 @@ struct Layer {
|
||||
LayerBlending blending;
|
||||
bool visible;
|
||||
s32 z_index;
|
||||
bool is_overlay;
|
||||
};
|
||||
|
||||
struct LayerStack {
|
||||
|
||||
@@ -78,25 +78,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
for (auto& layer : display.stack.layers) {
|
||||
auto consumer_id = layer->consumer_id;
|
||||
|
||||
bool should_try_acquire = true;
|
||||
if (!layer->is_overlay) {
|
||||
auto fb_it = m_framebuffers.find(consumer_id);
|
||||
if (fb_it != m_framebuffers.end() && fb_it->second.is_acquired) {
|
||||
const u64 frames_since_last_acquire = m_frame_number - fb_it->second.last_acquire_frame;
|
||||
const s32 expected_interval = NormalizeSwapInterval(nullptr, fb_it->second.item.swap_interval);
|
||||
|
||||
if (frames_since_last_acquire < static_cast<u64>(expected_interval)) {
|
||||
should_try_acquire = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to fetch the framebuffer (either new or stale).
|
||||
const auto result = should_try_acquire
|
||||
? this->CacheFramebufferLocked(*layer, consumer_id)
|
||||
: (m_framebuffers.find(consumer_id) != m_framebuffers.end() && m_framebuffers[consumer_id].is_acquired
|
||||
? CacheStatus::CachedBufferReused
|
||||
: CacheStatus::NoBufferAvailable);
|
||||
const auto result = this->CacheFramebufferLocked(*layer, consumer_id);
|
||||
|
||||
// If we failed, skip this layer.
|
||||
if (result == CacheStatus::NoBufferAvailable) {
|
||||
@@ -128,12 +111,6 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
});
|
||||
}
|
||||
|
||||
// Overlay layers run at their own framerate independently of the game.
|
||||
// Skip them when calculating the swap interval for the main game.
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to compose again either before this frame is supposed to
|
||||
// be released, or exactly on the vsync period it should be released.
|
||||
const s32 item_swap_interval = NormalizeSwapInterval(out_speed_scale, item.swap_interval);
|
||||
@@ -161,44 +138,33 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
// Batch framebuffer releases, instead of one-into-one.
|
||||
std::vector<std::pair<Layer*, Framebuffer*>> to_release;
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired)
|
||||
if (framebuffer.release_frame_number > m_frame_number || !framebuffer.is_acquired)
|
||||
continue;
|
||||
|
||||
auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer)
|
||||
continue;
|
||||
|
||||
// Overlay layers always release after every compose
|
||||
// Non-overlay layers release based on their swap interval
|
||||
if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) {
|
||||
if (auto layer = display.stack.FindLayer(layer_id); layer)
|
||||
to_release.emplace_back(layer.get(), &framebuffer);
|
||||
}
|
||||
}
|
||||
for (auto& [layer, framebuffer] : to_release) {
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence());
|
||||
framebuffer->is_acquired = false;
|
||||
}
|
||||
|
||||
// Advance by 1 frame (60 FPS compositing)
|
||||
m_frame_number += 1;
|
||||
// Advance by at least one frame.
|
||||
const u32 frame_advance = swap_interval.value_or(1);
|
||||
m_frame_number += frame_advance;
|
||||
|
||||
// Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
|
||||
// Release any necessary framebuffers.
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
// Not yet ready to release this framebuffer.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!framebuffer.is_acquired) {
|
||||
// Already released.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
|
||||
// Skip overlay layers as they were already released above
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: support release fence
|
||||
// This is needed to prevent screen tearing
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
@@ -206,7 +172,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
return frame_advance;
|
||||
}
|
||||
|
||||
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
|
||||
@@ -234,9 +200,8 @@ bool HardwareComposer::TryAcquireFramebufferLocked(Layer& layer, Framebuffer& fr
|
||||
}
|
||||
|
||||
// We succeeded, so set the new release frame info.
|
||||
const s32 swap_interval = layer.is_overlay ? 1 : NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval);
|
||||
framebuffer.release_frame_number = m_frame_number + swap_interval;
|
||||
framebuffer.last_acquire_frame = m_frame_number;
|
||||
framebuffer.release_frame_number =
|
||||
NormalizeSwapInterval(nullptr, framebuffer.item.swap_interval);
|
||||
framebuffer.is_acquired = true;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -37,7 +34,6 @@ private:
|
||||
struct Framebuffer {
|
||||
android::BufferItem item{};
|
||||
ReleaseFrameNumber release_frame_number{};
|
||||
u64 last_acquire_frame{0};
|
||||
bool is_acquired{false};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -104,13 +101,6 @@ void SurfaceFlinger::SetLayerBlending(s32 consumer_binder_id, LayerBlending blen
|
||||
}
|
||||
}
|
||||
|
||||
void SurfaceFlinger::SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay) {
|
||||
if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
|
||||
layer->is_overlay = is_overlay;
|
||||
LOG_DEBUG(Service_VI, "Layer {} marked as overlay: {}", consumer_binder_id, is_overlay);
|
||||
}
|
||||
}
|
||||
|
||||
Display* SurfaceFlinger::FindDisplay(u64 display_id) {
|
||||
for (auto& display : m_displays) {
|
||||
if (display.id == display_id) {
|
||||
|
||||
@@ -47,7 +47,6 @@ public:
|
||||
|
||||
void SetLayerVisibility(s32 consumer_binder_id, bool visible);
|
||||
void SetLayerBlending(s32 consumer_binder_id, LayerBlending blending);
|
||||
void SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay);
|
||||
|
||||
std::shared_ptr<Layer> FindLayer(s32 consumer_binder_id);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "core/hle/service/btdrv/btdrv.h"
|
||||
#include "core/hle/service/btm/btm.h"
|
||||
#include "core/hle/service/caps/caps.h"
|
||||
#include "core/hle/service/dmnt/dmnt.h"
|
||||
#include "core/hle/service/erpt/erpt.h"
|
||||
#include "core/hle/service/es/es.h"
|
||||
#include "core/hle/service/eupld/eupld.h"
|
||||
@@ -108,7 +107,6 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
{"btdrv", &BtDrv::LoopProcess},
|
||||
{"btm", &BTM::LoopProcess},
|
||||
{"capsrv", &Capture::LoopProcess},
|
||||
{"dmnt", &DMNT::LoopProcess},
|
||||
{"erpt", &ERPT::LoopProcess},
|
||||
{"es", &ES::LoopProcess},
|
||||
{"eupld", &EUPLD::LoopProcess},
|
||||
|
||||
@@ -36,29 +36,6 @@ struct SettingsHeader {
|
||||
u32 version;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
void SyncGlobalLanguageFromCode(LanguageCode language_code) {
|
||||
const auto it = std::find_if(available_language_codes.begin(), available_language_codes.end(),
|
||||
[language_code](LanguageCode code) { return code == language_code; });
|
||||
if (it == available_language_codes.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t index = static_cast<std::size_t>(std::distance(available_language_codes.begin(), it));
|
||||
if (index >= static_cast<std::size_t>(Settings::values.language_index.GetValue())) {
|
||||
Settings::values.language_index.SetValue(static_cast<Settings::Language>(index));
|
||||
}
|
||||
}
|
||||
|
||||
void SyncGlobalRegionFromCode(SystemRegionCode region_code) {
|
||||
const auto region_index = static_cast<std::size_t>(region_code);
|
||||
if (region_index > static_cast<std::size_t>(Settings::Region::Taiwan)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Settings::values.region_index.SetValue(static_cast<Settings::Region>(region_index));
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system,
|
||||
@@ -480,7 +457,6 @@ Result ISystemSettingsServer::SetLanguageCode(LanguageCode language_code) {
|
||||
LOG_INFO(Service_SET, "called, language_code={}", language_code);
|
||||
|
||||
m_system_settings.language_code = language_code;
|
||||
SyncGlobalLanguageFromCode(language_code);
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -913,7 +889,6 @@ Result ISystemSettingsServer::SetRegionCode(SystemRegionCode region_code) {
|
||||
LOG_INFO(Service_SET, "called, region_code={}", region_code);
|
||||
|
||||
m_system_settings.region_code = region_code;
|
||||
SyncGlobalRegionFromCode(region_code);
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -1249,7 +1224,6 @@ Result ISystemSettingsServer::SetKeyboardLayout(KeyboardLayout keyboard_layout)
|
||||
LOG_INFO(Service_SET, "called, keyboard_layout={}", keyboard_layout);
|
||||
|
||||
m_system_settings.keyboard_layout = keyboard_layout;
|
||||
SetSaveNeeded();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -578,7 +578,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<con
|
||||
}
|
||||
|
||||
std::vector<Network::PollFD> host_pollfds(fds.size());
|
||||
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [](PollFD pollfd) {
|
||||
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) {
|
||||
Network::PollFD result;
|
||||
result.socket = file_descriptors[pollfd.fd]->socket.get();
|
||||
result.events = Translate(pollfd.events);
|
||||
@@ -657,11 +657,7 @@ Errno BSD::ConnectImpl(s32 fd, std::span<const u8> addr) {
|
||||
|
||||
const auto result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
|
||||
if (result != Errno::SUCCESS) {
|
||||
if (result == Errno::INPROGRESS || result == Errno::AGAIN) {
|
||||
LOG_DEBUG(Service, "Connect fd={} in progress (non-blocking), errno={}", fd, static_cast<int>(result));
|
||||
} else {
|
||||
LOG_ERROR(Service, "Connect fd={} failed with errno={}", fd, static_cast<int>(result));
|
||||
}
|
||||
LOG_ERROR(Service, "Connect fd={} failed with errno={}", fd, static_cast<int>(result));
|
||||
} else {
|
||||
LOG_INFO(Service, "Connect fd={} succeeded", fd);
|
||||
}
|
||||
@@ -971,11 +967,7 @@ Expected<s32, Errno> BSD::DuplicateSocketImpl(s32 fd) {
|
||||
return Unexpected(Errno::MFILE);
|
||||
}
|
||||
|
||||
file_descriptors[new_fd] = FileDescriptor{
|
||||
.socket = file_descriptors[fd]->socket,
|
||||
.flags = file_descriptors[fd]->flags,
|
||||
.is_connection_based = file_descriptors[fd]->is_connection_based,
|
||||
};
|
||||
file_descriptors[new_fd] = file_descriptors[fd];
|
||||
return new_fd;
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ private:
|
||||
|
||||
void BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept;
|
||||
|
||||
static inline std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors{};
|
||||
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
|
||||
|
||||
/// Callback to parse and handle a received wifi packet.
|
||||
void OnProxyPacketReceived(const Network::ProxyPacket& packet);
|
||||
|
||||
@@ -157,24 +157,22 @@ private:
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
|
||||
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
|
||||
|
||||
auto res = bsd->DuplicateSocketImpl(fd);
|
||||
if (!res.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
|
||||
const s32 duplicated_fd = *res;
|
||||
|
||||
// Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor
|
||||
if (do_not_close_socket) {
|
||||
*out_fd = duplicated_fd;
|
||||
auto res = bsd->DuplicateSocketImpl(fd);
|
||||
if (!res.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
fd = *res;
|
||||
fd_to_close = fd;
|
||||
*out_fd = fd;
|
||||
} else {
|
||||
*out_fd = -1;
|
||||
fd_to_close = duplicated_fd;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
|
||||
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(fd);
|
||||
if (!sock.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
|
||||
LOG_ERROR(Service_SSL, "invalid socket fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
socket = std::move(*sock);
|
||||
@@ -327,19 +325,7 @@ private:
|
||||
res = backend->GetServerCerts(&certs);
|
||||
if (res == ResultSuccess) {
|
||||
const std::vector<u8> certs_buf = SerializeServerCerts(certs);
|
||||
if (ctx.CanWriteBuffer()) {
|
||||
const size_t buffer_size = ctx.GetWriteBufferSize();
|
||||
if (certs_buf.size() <= buffer_size) {
|
||||
ctx.WriteBuffer(certs_buf);
|
||||
} else {
|
||||
LOG_WARNING(Service_SSL, "Certificate buffer too small: {} bytes needed, {} bytes available",
|
||||
certs_buf.size(), buffer_size);
|
||||
ctx.WriteBuffer(std::span<const u8>(certs_buf.data(), buffer_size));
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(Service_SSL, "No output buffer provided for certificates ({} bytes)", certs_buf.size());
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(certs_buf);
|
||||
out.certs_count = static_cast<u32>(certs.size());
|
||||
out.certs_size = static_cast<u32>(certs_buf.size());
|
||||
}
|
||||
@@ -678,119 +664,119 @@ class ISslServiceForSystem final : public ServiceFramework<ISslServiceForSystem>
|
||||
{103, D<&ISslServiceForSystem::VerifySignature>, "VerifySignature"}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
RegisterHandlers(functions);
|
||||
};
|
||||
|
||||
|
||||
Result CreateContext() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetContextCount() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetCertificates() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetCertificateBufSize() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result DebugIoctl() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result SetInterfaceVersion() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result FlushSessionCache() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result SetDebugOption() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetDebugOption() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result ClearTls12FallbackFlag() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result CreateContextForSystem() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result SetThreadCoreMask() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result GetThreadCoreMask() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
|
||||
Result VerifySignature() {
|
||||
LOG_DEBUG(Service_SSL, "(STUBBED) called.");
|
||||
|
||||
|
||||
// TODO (jarrodnorwell)
|
||||
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -166,16 +166,6 @@ Result Container::GetLayerZIndex(u64 layer_id, s32* out_z_index) {
|
||||
R_RETURN(VI::ResultNotFound);
|
||||
}
|
||||
|
||||
Result Container::SetLayerIsOverlay(u64 layer_id, bool is_overlay) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
|
||||
auto* const layer = m_layers.GetLayerById(layer_id);
|
||||
R_UNLESS(layer != nullptr, VI::ResultNotFound);
|
||||
|
||||
m_surface_flinger->SetLayerIsOverlay(layer->GetConsumerBinderId(), is_overlay);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Container::LinkVsyncEvent(u64 display_id, Event* event) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
m_conductor->LinkVsyncEvent(display_id, event);
|
||||
|
||||
@@ -67,7 +67,6 @@ public:
|
||||
Result SetLayerBlending(u64 layer_id, bool enabled);
|
||||
Result SetLayerZIndex(u64 layer_id, s32 z_index);
|
||||
Result GetLayerZIndex(u64 layer_id, s32* out_z_index);
|
||||
Result SetLayerIsOverlay(u64 layer_id, bool is_overlay);
|
||||
|
||||
void LinkVsyncEvent(u64 display_id, Event* event);
|
||||
void UnlinkVsyncEvent(u64 display_id, Event* event);
|
||||
|
||||
@@ -46,8 +46,8 @@ Result AllocateSharedBufferMemory(std::unique_ptr<Kernel::KPageGroup>* out_page_
|
||||
u32* start = system.DeviceMemory().GetPointer<u32>(block.GetAddress());
|
||||
u32* end = system.DeviceMemory().GetPointer<u32>(block.GetAddress() + block.GetSize());
|
||||
|
||||
for (; start < end; start++) {
|
||||
*start = 0xFF0000FF;
|
||||
for (; start < end; ++start) {
|
||||
*start = 0x00000000; // ARGB/RGBA with alpha=0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +257,7 @@ Result SharedBufferManager::CreateSession(Kernel::KProcess* owner_process, u64*
|
||||
|
||||
// Configure blending and z-index
|
||||
R_ASSERT(m_container.SetLayerBlending(session.layer_id, enable_blending));
|
||||
R_ASSERT(m_container.SetLayerZIndex(session.layer_id, 100000));
|
||||
|
||||
// Get the producer and set preallocated buffers.
|
||||
std::shared_ptr<android::BufferQueueProducer> producer;
|
||||
@@ -373,6 +374,11 @@ Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence,
|
||||
android::Status::NoError,
|
||||
VI::ResultOperationFailed);
|
||||
|
||||
// Ensure the layer is visible when content is presented.
|
||||
// Re-assert overlay priority in case clients reset it.
|
||||
(void)m_container.SetLayerZIndex(layer_id, 100000);
|
||||
(void)m_container.SetLayerVisibility(layer_id, true);
|
||||
|
||||
// We succeeded.
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -409,22 +415,51 @@ Result SharedBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32*
|
||||
// TODO: this could be optimized
|
||||
s64 e = -1280 * 768 * 4;
|
||||
for (auto& block : *m_buffer_page_group) {
|
||||
u8* start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
|
||||
u8* end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
|
||||
u8* const block_start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
|
||||
u8* ptr = block_start;
|
||||
u8* const block_end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
|
||||
|
||||
for (; start < end; start++) {
|
||||
*start = 0;
|
||||
for (; ptr < block_end; ++ptr) {
|
||||
if (e >= 0 && e < static_cast<s64>(capture_buffer.size())) {
|
||||
*start = capture_buffer[e];
|
||||
*ptr = capture_buffer[static_cast<size_t>(e)];
|
||||
} else {
|
||||
*ptr = 0;
|
||||
}
|
||||
e++;
|
||||
++e;
|
||||
}
|
||||
|
||||
m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(start, scratch, [&](DAddr addr) {
|
||||
m_system.GPU().InvalidateRegion(addr, end - start);
|
||||
m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(block_start, scratch, [&](DAddr addr) {
|
||||
m_system.GPU().InvalidateRegion(addr, block_end - block_start);
|
||||
});
|
||||
}
|
||||
|
||||
// After writing, present a frame on each active shared layer so it becomes visible.
|
||||
for (auto& [aruid, session] : m_sessions) {
|
||||
std::shared_ptr<android::BufferQueueProducer> producer;
|
||||
if (R_FAILED(m_container.GetLayerProducerHandle(std::addressof(producer), session.layer_id))) {
|
||||
continue;
|
||||
}
|
||||
s32 slot = -1;
|
||||
android::Fence fence = android::Fence::NoFence();
|
||||
if (producer->DequeueBuffer(&slot, &fence, SharedBufferAsync != 0, SharedBufferWidth,
|
||||
SharedBufferHeight, SharedBufferBlockLinearFormat, 0) !=
|
||||
android::Status::NoError) {
|
||||
continue;
|
||||
}
|
||||
std::shared_ptr<android::GraphicBuffer> gb;
|
||||
if (producer->RequestBuffer(slot, &gb) != android::Status::NoError) {
|
||||
producer->CancelBuffer(slot, android::Fence::NoFence());
|
||||
continue;
|
||||
}
|
||||
android::QueueBufferInput qin{};
|
||||
android::QueueBufferOutput qout{};
|
||||
qin.crop = {0, 0, static_cast<s32>(SharedBufferWidth), static_cast<s32>(SharedBufferHeight)};
|
||||
qin.fence = android::Fence::NoFence();
|
||||
qin.transform = static_cast<android::NativeWindowTransform>(0);
|
||||
qin.swap_interval = 1;
|
||||
(void)producer->QueueBuffer(slot, qin, &qout);
|
||||
}
|
||||
|
||||
*out_was_written = true;
|
||||
*out_layer_index = 1;
|
||||
R_SUCCEED();
|
||||
|
||||
286
src/core/memory/cheat_engine.cpp
Normal file
286
src/core/memory/cheat_engine.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <locale>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/arm/debug.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_page_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_process_page_table.h"
|
||||
#include "core/hle/kernel/svc_types.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "hid_core/resource_manager.h"
|
||||
#include "hid_core/resources/npad/npad.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
namespace {
|
||||
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
|
||||
|
||||
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||
std::size_t start_index, char match) {
|
||||
auto end_index = start_index;
|
||||
while (data[end_index] != match) {
|
||||
++end_index;
|
||||
if (end_index > data.size()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out_name_size = end_index - start_index;
|
||||
|
||||
// Clamp name if it's too big
|
||||
if (out_name_size > sizeof(CheatDefinition::readable_name)) {
|
||||
end_index = start_index + sizeof(CheatDefinition::readable_name);
|
||||
}
|
||||
|
||||
return data.substr(start_index, end_index - start_index);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_)
|
||||
: metadata{metadata_}, system{system_} {}
|
||||
|
||||
StandardVmCallbacks::~StandardVmCallbacks() = default;
|
||||
|
||||
void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) {
|
||||
// Return zero on invalid address
|
||||
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
|
||||
std::memset(data, 0, size);
|
||||
return;
|
||||
}
|
||||
|
||||
system.ApplicationMemory().ReadBlock(address, data, size);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) {
|
||||
// Skip invalid memory write address
|
||||
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (system.ApplicationMemory().WriteBlock(address, data, size)) {
|
||||
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size);
|
||||
}
|
||||
}
|
||||
|
||||
u64 StandardVmCallbacks::HidKeysDown() {
|
||||
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
|
||||
if (hid == nullptr) {
|
||||
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto applet_resource = hid->GetResourceManager();
|
||||
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) {
|
||||
LOG_WARNING(CheatEngine,
|
||||
"Attempted to read input state, but applet resource is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
|
||||
return static_cast<u64>(press_state & HID::NpadButton::All);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::PauseProcess() {
|
||||
if (system.ApplicationProcess()->IsSuspended()) {
|
||||
return;
|
||||
}
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::ResumeProcess() {
|
||||
if (!system.ApplicationProcess()->IsSuspended()) {
|
||||
return;
|
||||
}
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
|
||||
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::CommandLog(std::string_view data) {
|
||||
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
|
||||
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
|
||||
}
|
||||
|
||||
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
|
||||
if ((in < metadata.main_nso_extents.base ||
|
||||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
|
||||
(in < metadata.heap_extents.base ||
|
||||
in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
|
||||
(in < metadata.alias_extents.base ||
|
||||
in >= metadata.alias_extents.base + metadata.alias_extents.size) &&
|
||||
(in < metadata.aslr_extents.base ||
|
||||
in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) {
|
||||
LOG_DEBUG(CheatEngine,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this "
|
||||
"persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return false; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
TextCheatParser::~TextCheatParser() = default;
|
||||
|
||||
std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
|
||||
std::vector<CheatEntry> out(1);
|
||||
std::optional<u64> current_entry;
|
||||
|
||||
for (std::size_t i = 0; i < data.size(); ++i) {
|
||||
if (::isspace(data[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data[i] == '{') {
|
||||
current_entry = 0;
|
||||
|
||||
if (out[*current_entry].definition.num_opcodes > 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, '}');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (data[i] == '[') {
|
||||
current_entry = out.size();
|
||||
out.emplace_back();
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, ']');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (::isxdigit(data[i])) {
|
||||
if (!current_entry || out[*current_entry].definition.num_opcodes >=
|
||||
out[*current_entry].definition.opcodes.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto hex = std::string(data.substr(i, 8));
|
||||
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
|
||||
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
|
||||
value;
|
||||
|
||||
i += 8;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out[0].enabled = out[0].definition.num_opcodes > 0;
|
||||
out[0].cheat_id = 0;
|
||||
|
||||
for (u32 i = 1; i < out.size(); ++i) {
|
||||
out[i].enabled = out[i].definition.num_opcodes > 0;
|
||||
out[i].cheat_id = i;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
|
||||
const std::array<u8, 0x20>& build_id_)
|
||||
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
|
||||
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
|
||||
metadata.main_nso_build_id = build_id_;
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
core_timing.UnscheduleEvent(event);
|
||||
}
|
||||
|
||||
void CheatEngine::Initialize() {
|
||||
event = Core::Timing::CreateEvent(
|
||||
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
|
||||
[this](s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
FrameCallback(ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
|
||||
|
||||
metadata.process_id = system.ApplicationProcess()->GetProcessId();
|
||||
metadata.title_id = system.GetApplicationProcessProgramID();
|
||||
|
||||
const auto& page_table = system.ApplicationProcess()->GetPageTable();
|
||||
metadata.heap_extents = {
|
||||
.base = GetInteger(page_table.GetHeapRegionStart()),
|
||||
.size = page_table.GetHeapRegionSize(),
|
||||
};
|
||||
metadata.aslr_extents = {
|
||||
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
|
||||
.size = page_table.GetAliasCodeRegionSize(),
|
||||
};
|
||||
metadata.alias_extents = {
|
||||
.base = GetInteger(page_table.GetAliasRegionStart()),
|
||||
.size = page_table.GetAliasRegionSize(),
|
||||
};
|
||||
|
||||
is_pending_reload.exchange(true);
|
||||
}
|
||||
|
||||
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
|
||||
metadata.main_nso_extents = {
|
||||
.base = main_region_begin,
|
||||
.size = main_region_size,
|
||||
};
|
||||
}
|
||||
|
||||
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
|
||||
cheats = std::move(reload_cheats);
|
||||
is_pending_reload.exchange(true);
|
||||
}
|
||||
|
||||
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
|
||||
if (is_pending_reload.exchange(false)) {
|
||||
vm.LoadProgram(cheats);
|
||||
}
|
||||
|
||||
if (vm.GetProgramSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm.Execute(metadata);
|
||||
}
|
||||
|
||||
} // namespace Core::Memory
|
||||
88
src/core/memory/cheat_engine.h
Normal file
88
src/core/memory/cheat_engine.h
Normal file
@@ -0,0 +1,88 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
#include "core/memory/dmnt_cheat_vm.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace Core::Memory {
|
||||
|
||||
class StandardVmCallbacks : public DmntCheatVm::Callbacks {
|
||||
public:
|
||||
StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_);
|
||||
~StandardVmCallbacks() override;
|
||||
|
||||
void MemoryReadUnsafe(VAddr address, void* data, u64 size) override;
|
||||
void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) override;
|
||||
u64 HidKeysDown() override;
|
||||
void PauseProcess() override;
|
||||
void ResumeProcess() override;
|
||||
void DebugLog(u8 id, u64 value) override;
|
||||
void CommandLog(std::string_view data) override;
|
||||
|
||||
private:
|
||||
bool IsAddressInRange(VAddr address) const;
|
||||
|
||||
const CheatProcessMetadata& metadata;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
// CheatList object, that can be used for execution.
|
||||
class CheatParser {
|
||||
public:
|
||||
virtual ~CheatParser();
|
||||
|
||||
[[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0;
|
||||
};
|
||||
|
||||
// CheatParser implementation that parses text files
|
||||
class TextCheatParser final : public CheatParser {
|
||||
public:
|
||||
~TextCheatParser() override;
|
||||
|
||||
[[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override;
|
||||
};
|
||||
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
|
||||
class CheatEngine final {
|
||||
public:
|
||||
CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
|
||||
const std::array<u8, 0x20>& build_id_);
|
||||
~CheatEngine();
|
||||
|
||||
void Initialize();
|
||||
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
|
||||
|
||||
void Reload(std::vector<CheatEntry> reload_cheats);
|
||||
|
||||
private:
|
||||
void FrameCallback(std::chrono::nanoseconds ns_late);
|
||||
|
||||
DmntCheatVm vm;
|
||||
CheatProcessMetadata metadata;
|
||||
|
||||
std::vector<CheatEntry> cheats;
|
||||
std::atomic_bool is_pending_reload{false};
|
||||
|
||||
std::shared_ptr<Core::Timing::EventType> event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace Core::Memory
|
||||
37
src/core/memory/dmnt_cheat_types.h
Normal file
37
src/core/memory/dmnt_cheat_types.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
|
||||
struct MemoryRegionExtents {
|
||||
u64 base{};
|
||||
u64 size{};
|
||||
};
|
||||
|
||||
struct CheatProcessMetadata {
|
||||
u64 process_id{};
|
||||
u64 title_id{};
|
||||
MemoryRegionExtents main_nso_extents{};
|
||||
MemoryRegionExtents heap_extents{};
|
||||
MemoryRegionExtents alias_extents{};
|
||||
MemoryRegionExtents aslr_extents{};
|
||||
std::array<u8, 0x20> main_nso_build_id{};
|
||||
};
|
||||
|
||||
struct CheatDefinition {
|
||||
std::array<char, 0x40> readable_name{};
|
||||
u32 num_opcodes{};
|
||||
std::array<u32, 0x100> opcodes{};
|
||||
};
|
||||
|
||||
struct CheatEntry {
|
||||
bool enabled{};
|
||||
u32 cheat_id{};
|
||||
CheatDefinition definition{};
|
||||
};
|
||||
|
||||
} // namespace Core::Memory
|
||||
1268
src/core/memory/dmnt_cheat_vm.cpp
Normal file
1268
src/core/memory/dmnt_cheat_vm.cpp
Normal file
File diff suppressed because it is too large
Load Diff
330
src/core/memory/dmnt_cheat_vm.h
Normal file
330
src/core/memory/dmnt_cheat_vm.h
Normal file
@@ -0,0 +1,330 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <fmt/printf.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
|
||||
enum class CheatVmOpcodeType : u32 {
|
||||
StoreStatic = 0,
|
||||
BeginConditionalBlock = 1,
|
||||
EndConditionalBlock = 2,
|
||||
ControlLoop = 3,
|
||||
LoadRegisterStatic = 4,
|
||||
LoadRegisterMemory = 5,
|
||||
StoreStaticToAddress = 6,
|
||||
PerformArithmeticStatic = 7,
|
||||
BeginKeypressConditionalBlock = 8,
|
||||
|
||||
// These are not implemented by Gateway's VM.
|
||||
PerformArithmeticRegister = 9,
|
||||
StoreRegisterToAddress = 10,
|
||||
Reserved11 = 11,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
ExtendedWidth = 12,
|
||||
|
||||
// Extended width opcodes.
|
||||
BeginRegisterConditionalBlock = 0xC0,
|
||||
SaveRestoreRegister = 0xC1,
|
||||
SaveRestoreRegisterMask = 0xC2,
|
||||
ReadWriteStaticRegister = 0xC3,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
DoubleExtendedWidth = 0xF0,
|
||||
|
||||
// Double-extended width opcodes.
|
||||
PauseProcess = 0xFF0,
|
||||
ResumeProcess = 0xFF1,
|
||||
DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum class MemoryAccessType : u32 {
|
||||
MainNso = 0,
|
||||
Heap = 1,
|
||||
Alias = 2,
|
||||
Aslr = 3,
|
||||
};
|
||||
|
||||
enum class ConditionalComparisonType : u32 {
|
||||
GT = 1,
|
||||
GE = 2,
|
||||
LT = 3,
|
||||
LE = 4,
|
||||
EQ = 5,
|
||||
NE = 6,
|
||||
};
|
||||
|
||||
enum class RegisterArithmeticType : u32 {
|
||||
Addition = 0,
|
||||
Subtraction = 1,
|
||||
Multiplication = 2,
|
||||
LeftShift = 3,
|
||||
RightShift = 4,
|
||||
|
||||
// These are not supported by Gateway's VM.
|
||||
LogicalAnd = 5,
|
||||
LogicalOr = 6,
|
||||
LogicalNot = 7,
|
||||
LogicalXor = 8,
|
||||
|
||||
None = 9,
|
||||
};
|
||||
|
||||
enum class StoreRegisterOffsetType : u32 {
|
||||
None = 0,
|
||||
Reg = 1,
|
||||
Imm = 2,
|
||||
MemReg = 3,
|
||||
MemImm = 4,
|
||||
MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum class CompareRegisterValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
StaticValue = 4,
|
||||
OtherRegister = 5,
|
||||
};
|
||||
|
||||
enum class SaveRestoreRegisterOpType : u32 {
|
||||
Restore = 0,
|
||||
Save = 1,
|
||||
ClearSaved = 2,
|
||||
ClearRegs = 3,
|
||||
};
|
||||
|
||||
enum class DebugLogValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
RegisterValue = 4,
|
||||
};
|
||||
|
||||
union VmInt {
|
||||
u8 bit8;
|
||||
u16 bit16;
|
||||
u32 bit32;
|
||||
u64 bit64;
|
||||
};
|
||||
|
||||
struct StoreStaticOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 offset_register{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct BeginConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {
|
||||
bool is_else;
|
||||
};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop{};
|
||||
u32 reg_index{};
|
||||
u32 num_iters{};
|
||||
};
|
||||
|
||||
struct LoadRegisterStaticOpcode {
|
||||
u32 reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct LoadRegisterMemoryOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 reg_index{};
|
||||
bool load_from_reg{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct StoreStaticToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
bool increment_reg{};
|
||||
bool add_offset_reg{};
|
||||
u32 offset_reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticStaticOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 value{};
|
||||
};
|
||||
|
||||
struct BeginKeypressConditionalOpcode {
|
||||
u32 key_mask{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticRegisterOpcode {
|
||||
u32 bit_width{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 dst_reg_index{};
|
||||
u32 src_reg_1_index{};
|
||||
u32 src_reg_2_index{};
|
||||
bool has_immediate{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct StoreRegisterToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 str_reg_index{};
|
||||
u32 addr_reg_index{};
|
||||
bool increment_reg{};
|
||||
StoreRegisterOffsetType ofs_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u32 val_reg_index{};
|
||||
CompareRegisterValueType comp_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 other_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterOpcode {
|
||||
u32 dst_index{};
|
||||
u32 src_index{};
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterMaskOpcode {
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
std::array<bool, 0x10> should_operate{};
|
||||
};
|
||||
|
||||
struct ReadWriteStaticRegisterOpcode {
|
||||
u32 static_idx{};
|
||||
u32 idx{};
|
||||
};
|
||||
|
||||
struct PauseProcessOpcode {};
|
||||
|
||||
struct ResumeProcessOpcode {};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width{};
|
||||
u32 log_id{};
|
||||
DebugLogValueType val_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 val_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct UnrecognizedInstruction {
|
||||
CheatVmOpcodeType opcode{};
|
||||
};
|
||||
|
||||
struct CheatVmOpcode {
|
||||
bool begin_conditional_block{};
|
||||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
|
||||
opcode{};
|
||||
};
|
||||
|
||||
class DmntCheatVm {
|
||||
public:
|
||||
/// Helper Type for DmntCheatVm <=> yuzu Interface
|
||||
class Callbacks {
|
||||
public:
|
||||
virtual ~Callbacks();
|
||||
|
||||
virtual void MemoryReadUnsafe(VAddr address, void* data, u64 size) = 0;
|
||||
virtual void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) = 0;
|
||||
|
||||
virtual u64 HidKeysDown() = 0;
|
||||
|
||||
virtual void PauseProcess() = 0;
|
||||
virtual void ResumeProcess() = 0;
|
||||
|
||||
virtual void DebugLog(u8 id, u64 value) = 0;
|
||||
virtual void CommandLog(std::string_view data) = 0;
|
||||
};
|
||||
|
||||
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
|
||||
static constexpr std::size_t NumRegisters = 0x10;
|
||||
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumWritableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumStaticRegisters =
|
||||
NumReadableStaticRegisters + NumWritableStaticRegisters;
|
||||
|
||||
explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks_);
|
||||
~DmntCheatVm();
|
||||
|
||||
std::size_t GetProgramSize() const {
|
||||
return this->num_opcodes;
|
||||
}
|
||||
|
||||
bool LoadProgram(const std::vector<CheatEntry>& cheats);
|
||||
void Execute(const CheatProcessMetadata& metadata);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Callbacks> callbacks;
|
||||
|
||||
std::size_t num_opcodes = 0;
|
||||
std::size_t instruction_ptr = 0;
|
||||
std::size_t condition_depth = 0;
|
||||
bool decode_success = false;
|
||||
std::array<u32, MaximumProgramOpcodeCount> program{};
|
||||
std::array<u64, NumRegisters> registers{};
|
||||
std::array<u64, NumRegisters> saved_values{};
|
||||
std::array<u64, NumStaticRegisters> static_registers{};
|
||||
std::array<std::size_t, NumRegisters> loop_tops{};
|
||||
|
||||
bool DecodeNextOpcode(CheatVmOpcode& out);
|
||||
void SkipConditionalBlock(bool is_if);
|
||||
void ResetState();
|
||||
|
||||
// For implementing the DebugLog opcode.
|
||||
void DebugLog(u32 log_id, u64 value);
|
||||
|
||||
void LogOpcode(const CheatVmOpcode& opcode);
|
||||
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
|
||||
MemoryAccessType mem_type, u64 rel_address);
|
||||
};
|
||||
|
||||
}; // namespace Core::Memory
|
||||
@@ -217,13 +217,13 @@ void A32EmitX64::ClearFastDispatchTable() {
|
||||
}
|
||||
|
||||
void A32EmitX64::GenTerminalHandlers() {
|
||||
// PC ends up in edi, location_descriptor ends up in rbx
|
||||
// PC ends up in ebp, location_descriptor ends up in rbx
|
||||
const auto calculate_location_descriptor = [this] {
|
||||
// This calculation has to match up with IREmitter::PushRSB
|
||||
code.mov(ebx, dword[code.ABI_JIT_PTR + offsetof(A32JitState, upper_location_descriptor)]);
|
||||
code.shl(rbx, 32);
|
||||
code.mov(ecx, MJitStateReg(A32::Reg::PC));
|
||||
code.mov(edi, ecx);
|
||||
code.mov(ebp, ecx);
|
||||
code.or_(rbx, rcx);
|
||||
};
|
||||
|
||||
@@ -238,7 +238,7 @@ void A32EmitX64::GenTerminalHandlers() {
|
||||
code.mov(dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)], eax);
|
||||
code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_location_descriptors) + rax * sizeof(u64)]);
|
||||
if (conf.HasOptimization(OptimizationFlag::FastDispatch)) {
|
||||
code.jne(rsb_cache_miss, code.T_NEAR);
|
||||
code.jne(rsb_cache_miss);
|
||||
} else {
|
||||
code.jne(code.GetReturnFromRunCodeAddress());
|
||||
}
|
||||
@@ -251,21 +251,20 @@ void A32EmitX64::GenTerminalHandlers() {
|
||||
terminal_handler_fast_dispatch_hint = code.getCurr<const void*>();
|
||||
calculate_location_descriptor();
|
||||
code.L(rsb_cache_miss);
|
||||
code.mov(r8, reinterpret_cast<u64>(fast_dispatch_table.data()));
|
||||
//code.mov(r12d, MJitStateReg(A32::Reg::PC));
|
||||
code.mov(r12, rbx);
|
||||
code.mov(r12, reinterpret_cast<u64>(fast_dispatch_table.data()));
|
||||
code.mov(rbp, rbx);
|
||||
if (code.HasHostFeature(HostFeature::SSE42)) {
|
||||
code.crc32(r12, r8);
|
||||
code.crc32(rbp, r12);
|
||||
}
|
||||
code.and_(r12d, fast_dispatch_table_mask);
|
||||
code.lea(r12, ptr[r8 + r12]);
|
||||
code.cmp(rbx, qword[r12 + offsetof(FastDispatchEntry, location_descriptor)]);
|
||||
code.jne(fast_dispatch_cache_miss, code.T_NEAR);
|
||||
code.jmp(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)]);
|
||||
code.and_(ebp, fast_dispatch_table_mask);
|
||||
code.lea(rbp, ptr[r12 + rbp]);
|
||||
code.cmp(rbx, qword[rbp + offsetof(FastDispatchEntry, location_descriptor)]);
|
||||
code.jne(fast_dispatch_cache_miss);
|
||||
code.jmp(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)]);
|
||||
code.L(fast_dispatch_cache_miss);
|
||||
code.mov(qword[r12 + offsetof(FastDispatchEntry, location_descriptor)], rbx);
|
||||
code.mov(qword[rbp + offsetof(FastDispatchEntry, location_descriptor)], rbx);
|
||||
code.LookupBlock();
|
||||
code.mov(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)], rax);
|
||||
code.mov(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)], rax);
|
||||
code.jmp(rax);
|
||||
PerfMapRegister(terminal_handler_fast_dispatch_hint, code.getCurr(), "a32_terminal_handler_fast_dispatch_hint");
|
||||
|
||||
|
||||
@@ -188,14 +188,13 @@ void A64EmitX64::ClearFastDispatchTable() {
|
||||
}
|
||||
|
||||
void A64EmitX64::GenTerminalHandlers() {
|
||||
// PC ends up in rcx, location_descriptor ends up in rbx
|
||||
static_assert(std::find(ABI_ALL_CALLEE_SAVE.begin(), ABI_ALL_CALLEE_SAVE.end(), HostLoc::R12) != ABI_ALL_CALLEE_SAVE.end());
|
||||
// PC ends up in rbp, location_descriptor ends up in rbx
|
||||
const auto calculate_location_descriptor = [this] {
|
||||
// This calculation has to match up with A64::LocationDescriptor::UniqueHash
|
||||
// TODO: Optimization is available here based on known state of fpcr.
|
||||
code.mov(rdi, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
|
||||
code.mov(rbp, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
|
||||
code.mov(rcx, A64::LocationDescriptor::pc_mask);
|
||||
code.and_(rcx, rdi);
|
||||
code.and_(rcx, rbp);
|
||||
code.mov(ebx, dword[code.ABI_JIT_PTR + offsetof(A64JitState, fpcr)]);
|
||||
code.and_(ebx, A64::LocationDescriptor::fpcr_mask);
|
||||
code.shl(rbx, A64::LocationDescriptor::fpcr_shift);
|
||||
@@ -227,21 +226,20 @@ void A64EmitX64::GenTerminalHandlers() {
|
||||
terminal_handler_fast_dispatch_hint = code.getCurr<const void*>();
|
||||
calculate_location_descriptor();
|
||||
code.L(rsb_cache_miss);
|
||||
code.mov(r8, reinterpret_cast<u64>(fast_dispatch_table.data()));
|
||||
//code.mov(r12, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]);
|
||||
code.mov(r12, rbx);
|
||||
code.mov(r12, reinterpret_cast<u64>(fast_dispatch_table.data()));
|
||||
code.mov(rbp, rbx);
|
||||
if (code.HasHostFeature(HostFeature::SSE42)) {
|
||||
code.crc32(r12, r8);
|
||||
code.crc32(rbp, r12);
|
||||
}
|
||||
code.and_(r12d, fast_dispatch_table_mask);
|
||||
code.lea(r12, ptr[r8 + r12]);
|
||||
code.cmp(rbx, qword[r12 + offsetof(FastDispatchEntry, location_descriptor)]);
|
||||
code.jne(fast_dispatch_cache_miss, code.T_NEAR);
|
||||
code.jmp(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)]);
|
||||
code.and_(ebp, fast_dispatch_table_mask);
|
||||
code.lea(rbp, ptr[r12 + rbp]);
|
||||
code.cmp(rbx, qword[rbp + offsetof(FastDispatchEntry, location_descriptor)]);
|
||||
code.jne(fast_dispatch_cache_miss);
|
||||
code.jmp(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)]);
|
||||
code.L(fast_dispatch_cache_miss);
|
||||
code.mov(qword[r12 + offsetof(FastDispatchEntry, location_descriptor)], rbx);
|
||||
code.mov(qword[rbp + offsetof(FastDispatchEntry, location_descriptor)], rbx);
|
||||
code.LookupBlock();
|
||||
code.mov(ptr[r12 + offsetof(FastDispatchEntry, code_ptr)], rax);
|
||||
code.mov(ptr[rbp + offsetof(FastDispatchEntry, code_ptr)], rax);
|
||||
code.jmp(rax);
|
||||
PerfMapRegister(terminal_handler_fast_dispatch_hint, code.getCurr(), "a64_terminal_handler_fast_dispatch_hint");
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
|
||||
cmp(dword[ABI_JIT_PTR + jsi.offsetof_halt_reason], 0);
|
||||
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
|
||||
lock(); or_(dword[ABI_JIT_PTR + jsi.offsetof_halt_reason], u32(HaltReason::Step));
|
||||
lock(); or_(dword[ABI_JIT_PTR + jsi.offsetof_halt_reason], static_cast<u32>(HaltReason::Step));
|
||||
|
||||
SwitchMxcsrOnEntry();
|
||||
jmp(ABI_PARAM2);
|
||||
|
||||
@@ -37,9 +37,6 @@
|
||||
#include "dynarmic/ir/basic_block.h"
|
||||
#include "dynarmic/ir/opt_passes.h"
|
||||
|
||||
#include "./A32/testenv.h"
|
||||
#include "./A64/testenv.h"
|
||||
|
||||
using namespace Dynarmic;
|
||||
|
||||
std::string_view GetNameOfA32Instruction(u32 instruction) {
|
||||
@@ -68,10 +65,7 @@ void PrintA32Instruction(u32 instruction) {
|
||||
fmt::print("should_continue: {}\n\n", should_continue);
|
||||
fmt::print("IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
ArmTestEnv jit_env{};
|
||||
Dynarmic::A32::UserConfig jit_user_config{};
|
||||
jit_user_config.callbacks = &jit_env;
|
||||
Optimization::Optimize(ir_block, jit_user_config, {});
|
||||
Optimization::Optimize(ir_block, A32::UserConfig{}, {});
|
||||
fmt::print("Optimized IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
}
|
||||
@@ -86,10 +80,7 @@ void PrintA64Instruction(u32 instruction) {
|
||||
fmt::print("should_continue: {}\n\n", should_continue);
|
||||
fmt::print("IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
A64TestEnv jit_env{};
|
||||
Dynarmic::A64::UserConfig jit_user_config{};
|
||||
jit_user_config.callbacks = &jit_env;
|
||||
Optimization::Optimize(ir_block, jit_user_config, {});
|
||||
Optimization::Optimize(ir_block, A64::UserConfig{}, {});
|
||||
fmt::print("Optimized IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
}
|
||||
@@ -107,10 +98,7 @@ void PrintThumbInstruction(u32 instruction) {
|
||||
fmt::print("should_continue: {}\n\n", should_continue);
|
||||
fmt::print("IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
ThumbTestEnv jit_env{};
|
||||
Dynarmic::A32::UserConfig jit_user_config{};
|
||||
jit_user_config.callbacks = &jit_env;
|
||||
Optimization::Optimize(ir_block, jit_user_config, {});
|
||||
Optimization::Optimize(ir_block, A32::UserConfig{}, {});
|
||||
fmt::print("Optimized IR:\n");
|
||||
fmt::print("{}\n", IR::DumpBlock(ir_block));
|
||||
}
|
||||
@@ -231,7 +219,7 @@ void ExecuteA32Instruction(u32 instruction) {
|
||||
*(iter->second) = *value;
|
||||
fmt::print("> {} = 0x{:08x}\n", reg_name, *value);
|
||||
}
|
||||
} else if (reg_name.starts_with("m")) {
|
||||
} else if (reg_name == "mem" || reg_name == "memory") {
|
||||
fmt::print("address: ");
|
||||
if (const auto address = get_value()) {
|
||||
fmt::print("value: ");
|
||||
@@ -240,7 +228,7 @@ void ExecuteA32Instruction(u32 instruction) {
|
||||
fmt::print("> mem[0x{:08x}] = 0x{:08x}\n", *address, *value);
|
||||
}
|
||||
}
|
||||
} else if (reg_name == "exit" || reg_name == "end" || reg_name.starts_with("q")) {
|
||||
} else if (reg_name == "end") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -256,7 +244,6 @@ void ExecuteA32Instruction(u32 instruction) {
|
||||
env.MemoryWrite32(initial_pc + 4, 0xEAFFFFFE); // B +0
|
||||
|
||||
cpu.Run();
|
||||
fmt::print("{}", fmt::join(cpu.Disassemble(), "\n"));
|
||||
|
||||
fmt::print("Registers modified:\n");
|
||||
for (size_t i = 0; i < regs.size(); ++i) {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
namespace Vulkan {
|
||||
|
||||
// Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines
|
||||
constexpr size_t SETS_GROW_RATE = 16;
|
||||
//constexpr size_t SETS_GROW_RATE = 32; //test difference between 16 and 32
|
||||
constexpr s32 SCORE_THRESHOLD = 3;
|
||||
|
||||
struct DescriptorBank {
|
||||
@@ -29,9 +29,12 @@ struct DescriptorBank {
|
||||
};
|
||||
|
||||
bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept {
|
||||
return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers &&
|
||||
texture_buffers >= subset.texture_buffers && image_buffers >= subset.image_buffers &&
|
||||
textures >= subset.textures && images >= subset.images;
|
||||
return uniform_buffers >= subset.uniform_buffers &&
|
||||
storage_buffers >= subset.storage_buffers &&
|
||||
texture_buffers >= subset.texture_buffers &&
|
||||
image_buffers >= subset.image_buffers &&
|
||||
textures >= subset.textures &&
|
||||
images >= subset.images;
|
||||
}
|
||||
|
||||
template <typename Descriptors>
|
||||
@@ -45,6 +48,19 @@ static u32 Accumulate(const Descriptors& descriptors) {
|
||||
|
||||
static DescriptorBankInfo MakeBankInfo(std::span<const Shader::Info> infos) {
|
||||
DescriptorBankInfo bank;
|
||||
if (infos.size() == 1) {
|
||||
const auto& info = infos.front();
|
||||
const auto acc = [](const auto& ds){ u32 c=0; for (const auto& d: ds) c+=d.count; return c; };
|
||||
bank.uniform_buffers += acc(info.constant_buffer_descriptors);
|
||||
bank.storage_buffers += acc(info.storage_buffers_descriptors);
|
||||
bank.texture_buffers += acc(info.texture_buffer_descriptors);
|
||||
bank.image_buffers += acc(info.image_buffer_descriptors);
|
||||
bank.textures += acc(info.texture_descriptors);
|
||||
bank.images += acc(info.image_descriptors);
|
||||
bank.score = bank.uniform_buffers + bank.storage_buffers + bank.texture_buffers +
|
||||
bank.image_buffers + bank.textures + bank.images;
|
||||
return bank;
|
||||
}
|
||||
for (const Shader::Info& info : infos) {
|
||||
bank.uniform_buffers += Accumulate(info.constant_buffer_descriptors);
|
||||
bank.storage_buffers += Accumulate(info.storage_buffers_descriptors);
|
||||
@@ -87,14 +103,35 @@ static void AllocatePool(const Device& device, DescriptorBank& bank) {
|
||||
}));
|
||||
}
|
||||
|
||||
static size_t GetGrowRate() {
|
||||
if (Settings::getDebugKnobAt(0))
|
||||
return 8;
|
||||
else if (Settings::getDebugKnobAt(1))
|
||||
return 16;
|
||||
else if (Settings::getDebugKnobAt(2))
|
||||
return 24;
|
||||
else if (Settings::getDebugKnobAt(3))
|
||||
return 32;
|
||||
else if (Settings::getDebugKnobAt(4))
|
||||
return 40;
|
||||
else if (Settings::getDebugKnobAt(5))
|
||||
return 48;
|
||||
else if (Settings::getDebugKnobAt(6))
|
||||
return 56;
|
||||
else if (Settings::getDebugKnobAt(7))
|
||||
return 64;
|
||||
else
|
||||
return 16;
|
||||
}
|
||||
|
||||
DescriptorAllocator::DescriptorAllocator(const Device& device_, MasterSemaphore& master_semaphore_,
|
||||
DescriptorBank& bank_, VkDescriptorSetLayout layout_)
|
||||
: ResourcePool(master_semaphore_, SETS_GROW_RATE), device{&device_}, bank{&bank_},
|
||||
layout{layout_} {}
|
||||
: ResourcePool(master_semaphore_, /*SETS_GROW_RATE*/ GetGrowRate()), device{&device_}, bank{&bank_},
|
||||
layout{layout_}, grow_rate{GetGrowRate()} {}
|
||||
|
||||
VkDescriptorSet DescriptorAllocator::Commit() {
|
||||
const size_t index = CommitResource();
|
||||
return sets[index / SETS_GROW_RATE][index % SETS_GROW_RATE];
|
||||
return sets[index / grow_rate][index % grow_rate];
|
||||
}
|
||||
|
||||
void DescriptorAllocator::Allocate(size_t begin, size_t end) {
|
||||
@@ -102,13 +139,22 @@ void DescriptorAllocator::Allocate(size_t begin, size_t end) {
|
||||
}
|
||||
|
||||
vk::DescriptorSets DescriptorAllocator::AllocateDescriptors(size_t count) {
|
||||
const std::vector<VkDescriptorSetLayout> layouts(count, layout);
|
||||
std::array<VkDescriptorSetLayout, 64> stack{};
|
||||
const VkDescriptorSetLayout* p_layouts = nullptr;
|
||||
std::vector<VkDescriptorSetLayout> heap;
|
||||
if (count <= stack.size()) {
|
||||
stack.fill(layout);
|
||||
p_layouts = stack.data();
|
||||
} else {
|
||||
heap.assign(count, layout);
|
||||
p_layouts = heap.data();
|
||||
}
|
||||
VkDescriptorSetAllocateInfo allocate_info{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.descriptorPool = *bank->pools.back(),
|
||||
.descriptorSetCount = static_cast<u32>(count),
|
||||
.pSetLayouts = layouts.data(),
|
||||
.pSetLayouts = p_layouts,
|
||||
};
|
||||
vk::DescriptorSets new_sets = bank->pools.back().Allocate(allocate_info);
|
||||
if (!new_sets.IsOutOfPoolMemory()) {
|
||||
@@ -146,21 +192,58 @@ DescriptorAllocator DescriptorPool::Allocator(VkDescriptorSetLayout layout,
|
||||
}
|
||||
|
||||
DescriptorBank& DescriptorPool::Bank(const DescriptorBankInfo& reqs) {
|
||||
{
|
||||
std::scoped_lock lk(cache_mutex);
|
||||
DescriptorBank* best = nullptr; u64 best_stamp = 0;
|
||||
for (const auto& e : cache_) {
|
||||
if (!e.bank) continue;
|
||||
if (std::abs(e.info.score - reqs.score) < SCORE_THRESHOLD && e.info.IsSuperset(reqs)) {
|
||||
if (e.stamp >= best_stamp) { best_stamp = e.stamp; best = e.bank; }
|
||||
}
|
||||
}
|
||||
if (best) return *best;
|
||||
}
|
||||
std::shared_lock read_lock{banks_mutex};
|
||||
const auto it = std::ranges::find_if(bank_infos, [&reqs](const DescriptorBankInfo& bank) {
|
||||
return std::abs(bank.score - reqs.score) < SCORE_THRESHOLD && bank.IsSuperset(reqs);
|
||||
});
|
||||
if (it != bank_infos.end()) {
|
||||
return *banks[std::distance(bank_infos.begin(), it)].get();
|
||||
DescriptorBank& found = *banks[std::distance(bank_infos.begin(), it)].get();
|
||||
read_lock.unlock();
|
||||
// update cache
|
||||
std::scoped_lock lk(cache_mutex);
|
||||
size_t victim = 0; u64 oldest = UINT64_MAX;
|
||||
for (size_t i=0;i<cache_.size();++i) if (cache_[i].stamp < oldest) { oldest = cache_[i].stamp; victim = i; }
|
||||
cache_[victim] = CacheEntry{found.info, &found, ++cache_tick_};
|
||||
return found;
|
||||
}
|
||||
read_lock.unlock();
|
||||
|
||||
std::unique_lock write_lock{banks_mutex};
|
||||
auto it2 = std::ranges::find_if(bank_infos, [&reqs](const DescriptorBankInfo& bank) {
|
||||
return std::abs(bank.score - reqs.score) < SCORE_THRESHOLD && bank.IsSuperset(reqs);
|
||||
});
|
||||
if (it2 != bank_infos.end()) {
|
||||
DescriptorBank& found = *banks[std::distance(bank_infos.begin(), it2)].get();
|
||||
// update cache
|
||||
std::scoped_lock lk(cache_mutex);
|
||||
size_t victim = 0; u64 oldest = UINT64_MAX;
|
||||
for (size_t i=0;i<cache_.size();++i) if (cache_[i].stamp < oldest) { oldest = cache_[i].stamp; victim = i; }
|
||||
cache_[victim] = CacheEntry{found.info, &found, ++cache_tick_};
|
||||
return found;
|
||||
}
|
||||
bank_infos.push_back(reqs);
|
||||
|
||||
auto& bank = *banks.emplace_back(std::make_unique<DescriptorBank>());
|
||||
bank.info = reqs;
|
||||
AllocatePool(device, bank);
|
||||
// update cache
|
||||
{
|
||||
std::scoped_lock lk(cache_mutex);
|
||||
size_t victim = 0; u64 oldest = UINT64_MAX;
|
||||
for (size_t i=0;i<cache_.size();++i) if (cache_[i].stamp < oldest) { oldest = cache_[i].stamp; victim = i; }
|
||||
cache_[victim] = CacheEntry{bank.info, &bank, ++cache_tick_};
|
||||
}
|
||||
return bank;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -6,7 +9,8 @@
|
||||
#include <shared_mutex>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
@@ -58,6 +62,7 @@ private:
|
||||
VkDescriptorSetLayout layout{};
|
||||
|
||||
std::vector<vk::DescriptorSets> sets;
|
||||
size_t grow_rate = 32;
|
||||
};
|
||||
|
||||
class DescriptorPool {
|
||||
@@ -75,6 +80,14 @@ public:
|
||||
|
||||
private:
|
||||
DescriptorBank& Bank(const DescriptorBankInfo& reqs);
|
||||
struct CacheEntry {
|
||||
DescriptorBankInfo info{};
|
||||
DescriptorBank* bank{nullptr};
|
||||
u64 stamp{0};
|
||||
};
|
||||
std::mutex cache_mutex{};
|
||||
std::array<CacheEntry, 8> cache_{}; //test and then adjust
|
||||
u64 cache_tick_{0};
|
||||
|
||||
const Device& device;
|
||||
MasterSemaphore& master_semaphore;
|
||||
|
||||
@@ -146,7 +146,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
||||
.pNext = nullptr,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
});
|
||||
free_queue.push_back(&frame);
|
||||
free_queue.push(&frame);
|
||||
}
|
||||
|
||||
if (use_present_thread) {
|
||||
@@ -164,7 +164,7 @@ Frame* PresentManager::GetRenderFrame() {
|
||||
|
||||
// Take the frame from the queue
|
||||
Frame* frame = free_queue.front();
|
||||
free_queue.pop_front();
|
||||
free_queue.pop();
|
||||
|
||||
// Wait for the presentation to be finished so all frame resources are free
|
||||
frame->present_done.Wait();
|
||||
@@ -174,17 +174,18 @@ Frame* PresentManager::GetRenderFrame() {
|
||||
}
|
||||
|
||||
void PresentManager::Present(Frame* frame) {
|
||||
if (use_present_thread) {
|
||||
scheduler.Record([this, frame](vk::CommandBuffer) {
|
||||
std::unique_lock lock{queue_mutex};
|
||||
present_queue.push_back(frame);
|
||||
frame_cv.notify_one();
|
||||
});
|
||||
} else {
|
||||
if (!use_present_thread) {
|
||||
scheduler.WaitWorker();
|
||||
CopyToSwapchain(frame);
|
||||
free_queue.push_back(frame);
|
||||
free_queue.push(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.Record([this, frame](vk::CommandBuffer) {
|
||||
std::unique_lock lock{queue_mutex};
|
||||
present_queue.push(frame);
|
||||
frame_cv.notify_one();
|
||||
});
|
||||
}
|
||||
|
||||
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
|
||||
@@ -276,25 +277,29 @@ void PresentManager::PresentThread(std::stop_token token) {
|
||||
Common::SetCurrentThreadName("VulkanPresent");
|
||||
while (!token.stop_requested()) {
|
||||
std::unique_lock lock{queue_mutex};
|
||||
|
||||
// Wait for presentation frames
|
||||
frame_cv.wait(lock, token, [this] { return !present_queue.empty(); });
|
||||
if (!token.stop_requested()) {
|
||||
// Take the frame and notify anyone waiting
|
||||
Frame* frame = present_queue.front();
|
||||
present_queue.pop_front();
|
||||
frame_cv.notify_one();
|
||||
|
||||
// By exchanging the lock ownership we take the swapchain lock
|
||||
// before the queue lock goes out of scope. This way the swapchain
|
||||
// lock in WaitPresent is guaranteed to occur after here.
|
||||
std::exchange(lock, std::unique_lock{swapchain_mutex});
|
||||
CopyToSwapchain(frame);
|
||||
|
||||
// Free the frame for reuse
|
||||
std::scoped_lock fl{free_mutex};
|
||||
free_queue.push_back(frame);
|
||||
free_cv.notify_one();
|
||||
if (token.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Take the frame and notify anyone waiting
|
||||
Frame* frame = present_queue.front();
|
||||
present_queue.pop();
|
||||
frame_cv.notify_one();
|
||||
|
||||
// By exchanging the lock ownership we take the swapchain lock
|
||||
// before the queue lock goes out of scope. This way the swapchain
|
||||
// lock in WaitPresent is guaranteed to occur after here.
|
||||
std::exchange(lock, std::unique_lock{swapchain_mutex});
|
||||
|
||||
CopyToSwapchain(frame);
|
||||
|
||||
// Free the frame for reuse
|
||||
std::scoped_lock fl{free_mutex};
|
||||
free_queue.push(frame);
|
||||
free_cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -8,7 +5,7 @@
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <boost/container/deque.hpp>
|
||||
#include <queue>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
@@ -91,8 +88,8 @@ private:
|
||||
#endif
|
||||
vk::CommandPool cmdpool;
|
||||
std::vector<Frame> frames;
|
||||
boost::container::deque<Frame*> present_queue;
|
||||
boost::container::deque<Frame*> free_queue;
|
||||
std::queue<Frame*> present_queue;
|
||||
std::queue<Frame*> free_queue;
|
||||
std::condition_variable_any frame_cv;
|
||||
std::condition_variable free_cv;
|
||||
std::mutex swapchain_mutex;
|
||||
|
||||
@@ -20,12 +20,18 @@
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool AreExtensionsSupported(const vk::InstanceDispatch& dld, std::vector<VkExtensionProperties> const& properties, std::span<const char* const> extensions) {
|
||||
[[nodiscard]] bool AreExtensionsSupported(const vk::InstanceDispatch& dld,
|
||||
std::span<const char* const> extensions) {
|
||||
const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld);
|
||||
if (!properties) {
|
||||
LOG_ERROR(Render_Vulkan, "Failed to query extension properties");
|
||||
return false;
|
||||
}
|
||||
for (const char* extension : extensions) {
|
||||
const auto it = std::ranges::find_if(properties, [extension](const auto& prop) {
|
||||
const auto it = std::ranges::find_if(*properties, [extension](const auto& prop) {
|
||||
return std::strcmp(extension, prop.extensionName) == 0;
|
||||
});
|
||||
if (it == properties.end()) {
|
||||
if (it == properties->end()) {
|
||||
LOG_ERROR(Render_Vulkan, "Required instance extension {} is not available", extension);
|
||||
return false;
|
||||
}
|
||||
@@ -72,14 +78,14 @@ namespace {
|
||||
if (window_type != Core::Frontend::WindowSystemType::Headless) {
|
||||
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
|
||||
}
|
||||
if (auto const properties = vk::EnumerateInstanceExtensionProperties(dld); properties) {
|
||||
#ifdef __APPLE__
|
||||
if (AreExtensionsSupported(dld, *properties, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME}))
|
||||
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
if (AreExtensionsSupported(dld, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME})) {
|
||||
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
if (enable_validation && AreExtensionsSupported(dld, *properties, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}))
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
|
||||
if (enable_validation &&
|
||||
AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) {
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
@@ -127,10 +133,11 @@ vk::Instance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceD
|
||||
LOG_ERROR(Render_Vulkan, "Failed to load Vulkan function pointers");
|
||||
throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
|
||||
}
|
||||
std::vector<const char*> const extensions = RequiredExtensions(dld, window_type, enable_validation);
|
||||
auto const properties = vk::EnumerateInstanceExtensionProperties(dld);
|
||||
if (!properties || !AreExtensionsSupported(dld, *properties, extensions))
|
||||
const std::vector<const char*> extensions =
|
||||
RequiredExtensions(dld, window_type, enable_validation);
|
||||
if (!AreExtensionsSupported(dld, extensions)) {
|
||||
throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT);
|
||||
}
|
||||
std::vector<const char*> layers = Layers(enable_validation);
|
||||
RemoveUnavailableLayers(dld, layers);
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ li.checked::marker { content: "\2612"; }
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLinks">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://stt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Stoat</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string>
|
||||
<string><html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://rvlt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Revolt</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
@@ -75,32 +73,10 @@ ConfigurePerGameAddons::~ConfigurePerGameAddons() = default;
|
||||
void ConfigurePerGameAddons::ApplyConfiguration() {
|
||||
std::vector<std::string> disabled_addons;
|
||||
|
||||
// Helper function to recursively collect disabled items
|
||||
std::function<void(QStandardItem*)> collect_disabled = [&](QStandardItem* item) {
|
||||
if (item == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this item is disabled
|
||||
if (item->isCheckable() && item->checkState() == Qt::Unchecked) {
|
||||
// Use the stored key from UserRole, falling back to text
|
||||
const auto key = item->data(Qt::UserRole).toString();
|
||||
if (!key.isEmpty()) {
|
||||
disabled_addons.push_back(key.toStdString());
|
||||
} else {
|
||||
disabled_addons.push_back(item->text().toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
// Process children (for cheats under mods)
|
||||
for (int row = 0; row < item->rowCount(); ++row) {
|
||||
collect_disabled(item->child(row, 0));
|
||||
}
|
||||
};
|
||||
|
||||
// Process all root items
|
||||
for (int row = 0; row < item_model->rowCount(); ++row) {
|
||||
collect_disabled(item_model->item(row, 0));
|
||||
for (const auto& item : list_items) {
|
||||
const auto disabled = item.front()->checkState() == Qt::Unchecked;
|
||||
if (disabled)
|
||||
disabled_addons.push_back(item.front()->text().toStdString());
|
||||
}
|
||||
|
||||
auto current = Settings::values.disabled_addons[title_id];
|
||||
@@ -147,61 +123,24 @@ void ConfigurePerGameAddons::LoadConfiguration() {
|
||||
FileSys::VirtualFile update_raw;
|
||||
loader->ReadUpdateRaw(update_raw);
|
||||
|
||||
// Get the build ID from the main executable for cheat enumeration
|
||||
const auto build_id = pm.GetBuildID(update_raw);
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
// Map to store parent items for mods (for adding cheat children)
|
||||
std::map<std::string, QStandardItem*> mod_items;
|
||||
|
||||
for (const auto& patch : pm.GetPatches(update_raw, build_id)) {
|
||||
for (const auto& patch : pm.GetPatches(update_raw)) {
|
||||
const auto name = QString::fromStdString(patch.name);
|
||||
|
||||
// For cheats, we need to use the full key (parent::name) for storage
|
||||
std::string storage_key;
|
||||
if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) {
|
||||
storage_key = patch.parent_name + "::" + patch.name;
|
||||
} else {
|
||||
storage_key = patch.name;
|
||||
}
|
||||
|
||||
auto* const first_item = new QStandardItem;
|
||||
first_item->setText(name);
|
||||
first_item->setCheckable(true);
|
||||
|
||||
// Store the storage key as user data for later retrieval
|
||||
first_item->setData(QString::fromStdString(storage_key), Qt::UserRole);
|
||||
|
||||
const auto patch_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), storage_key) != disabled.end();
|
||||
std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
|
||||
|
||||
first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
|
||||
|
||||
auto* const version_item = new QStandardItem{QString::fromStdString(patch.version)};
|
||||
|
||||
if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) {
|
||||
// This is a cheat - add as child of its parent mod
|
||||
auto parent_it = mod_items.find(patch.parent_name);
|
||||
if (parent_it != mod_items.end()) {
|
||||
parent_it->second->appendRow(QList<QStandardItem*>{first_item, version_item});
|
||||
} else {
|
||||
// Parent not found (shouldn't happen), add to root
|
||||
list_items.push_back(QList<QStandardItem*>{first_item, version_item});
|
||||
item_model->appendRow(list_items.back());
|
||||
}
|
||||
} else {
|
||||
// This is a top-level item (Update, Mod, DLC)
|
||||
list_items.push_back(QList<QStandardItem*>{first_item, version_item});
|
||||
item_model->appendRow(list_items.back());
|
||||
|
||||
// Store mod items for later cheat attachment
|
||||
if (patch.type == FileSys::PatchType::Mod) {
|
||||
mod_items[patch.name] = first_item;
|
||||
}
|
||||
}
|
||||
list_items.push_back(QList<QStandardItem*>{
|
||||
first_item, new QStandardItem{QString::fromStdString(patch.version)}});
|
||||
item_model->appendRow(list_items.back());
|
||||
}
|
||||
|
||||
tree_view->expandAll();
|
||||
tree_view->resizeColumnToContents(1);
|
||||
}
|
||||
|
||||
@@ -1900,7 +1900,7 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar
|
||||
tr("Error while loading ROM! %1", "%1 signifies a numeric error code.")
|
||||
.arg(QString::fromStdString(error_code));
|
||||
const auto description =
|
||||
tr("%1<br>Please redump your files or ask on Discord/Stoat for help.",
|
||||
tr("%1<br>Please redump your files or ask on Discord/Revolt for help.",
|
||||
"%1 signifies an error string.")
|
||||
.arg(QString::fromStdString(
|
||||
GetResultStatusString(static_cast<Loader::ResultStatus>(error_id))));
|
||||
|
||||
59
tools/dtrace-tool.sh
Executable file
59
tools/dtrace-tool.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/local/bin/bash -ex
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Basic script to run dtrace sampling over the program (requires Flamegraph)
|
||||
# Usage is either running as: ./dtrace-tool.sh pid (then input the pid of the process)
|
||||
# Or just run directly with: ./dtrace-tool.sh <command>
|
||||
|
||||
FLAMEGRAPH_DIR=".."
|
||||
fail() {
|
||||
printf '%s\n' "$1" >&2
|
||||
exit "${2-1}"
|
||||
}
|
||||
|
||||
[ -f $FLAMEGRAPH_DIR/FlameGraph/stackcollapse.pl ] || fail 'Where is flamegraph?'
|
||||
#[ which dtrace ] || fail 'Needs DTrace installed'
|
||||
|
||||
read -r "Sampling Hz [800]: " TRACE_CFG_HZ
|
||||
if [ -z "${TRACE_CFG_HZ}" ]; then
|
||||
TRACE_CFG_HZ=800
|
||||
fi
|
||||
|
||||
read -r "Sampling time [5] sec: " TRACE_CFG_TIME
|
||||
if [ -z "${TRACE_CFG_TIME}" ]; then
|
||||
TRACE_CFG_TIME=5
|
||||
fi
|
||||
|
||||
TRACE_FILE=dtrace-out.user_stacks
|
||||
TRACE_FOLD=dtrace-out.fold
|
||||
TRACE_SVG=dtrace-out.svg
|
||||
ps
|
||||
|
||||
if [ "$1" = 'pid' ]; then
|
||||
read -r "PID: " TRACE_CFG_PID
|
||||
sudo echo 'Sudo!'
|
||||
else
|
||||
if [ -f "$1" ] && [ "$1" ]; then
|
||||
fail 'Usage: ./tools/dtrace-profile.sh <path to program>'
|
||||
fi
|
||||
|
||||
printf "Executing: "
|
||||
echo "$@"
|
||||
sudo echo 'Sudo!'
|
||||
"$@" &
|
||||
TRACE_CFG_PID=$!
|
||||
fi
|
||||
|
||||
TRACE_PROBE="profile-${TRACE_CFG_HZ} /pid == ${TRACE_CFG_PID} && arg1/ { @[ustack()] = count(); } tick-${TRACE_CFG_TIME}s { exit(0); }"
|
||||
|
||||
rm -- $TRACE_SVG || echo 'Skip'
|
||||
|
||||
sudo dtrace -x ustackframes=100 -Z -n "$TRACE_PROBE" -o $TRACE_FILE 2>/dev/null || exit
|
||||
|
||||
perl $FLAMEGRAPH_DIR/FlameGraph/stackcollapse.pl $TRACE_FILE > $TRACE_FOLD || exit
|
||||
perl $FLAMEGRAPH_DIR/FlameGraph/flamegraph.pl $TRACE_FOLD > $TRACE_SVG || exit
|
||||
|
||||
sudo chmod 0666 $TRACE_FILE
|
||||
rm -- $TRACE_FILE $TRACE_FOLD
|
||||
Reference in New Issue
Block a user