Compare commits

...

34 Commits

Author SHA1 Message Date
Timotej Leginus
71a0fc21ab Fixed licensing info 2023-06-11 23:21:49 +02:00
Timotej Leginus
9b2aebee25 Fixed icon being in ea flavor resources 2023-06-11 22:47:33 +02:00
Timotej Leginus
c793a9c95c Fixed formatting 2023-06-11 22:02:11 +02:00
Timotej Leginus
8a1f3b7c41 Added a Wi-Fi (non-metered) only setting 2023-06-11 21:18:25 +02:00
Timotej Leginus
adad1d88d6 Merge branch 'master' of https://github.com/timleg002/yuzu 2023-06-11 20:29:37 +02:00
Timotej Leginus
6be08026c0 Implemented getting network IF info (routes) via Kotlin 2023-06-11 20:29:27 +02:00
Timotej Leginus
aa4003cdc8 Implemented getting network IF info (routes) via Kotlin 2023-06-11 20:26:34 +02:00
Timotej Leginus
ca6e4b8802 Added JNI methods 2023-06-11 20:21:46 +02:00
Timotej Leginus
d5129d9347 Merging 2023-06-11 20:20:17 +02:00
Timotej Leginus
17693651b3 Added multiplayer settings, now can direct connect to a room 2023-06-11 20:12:53 +02:00
Timotej Leginus
c4a0dbfb20 Supports manual network IF loading and network IF can be acquired via Kotlin 2023-06-11 03:04:32 +02:00
bunnei
b6f2490288 Merge pull request #10703 from bunnei/fix-android-layout
android: Fix screen orientation & blurriness.
2023-06-10 16:24:16 -07:00
bunnei
ea716eb5cc android: Fix screen orientation & blurriness. 2023-06-10 15:13:06 -07:00
bunnei
6b898c6d69 Merge pull request #10670 from liamwhite/fxaa2
vk_blit_screen: use higher bit depth for fxaa
2023-06-10 14:35:23 -07:00
Timotej Leginus
bfdc9e312f Fixed network interfaces on Android 2023-06-10 22:52:50 +02:00
Morph
fa5dfcb712 Merge pull request #10685 from liamwhite/serialization-is-hard
qt: persist framerate sync option
2023-06-10 12:28:00 -04:00
Morph
cb1fd1bad8 Merge pull request #10697 from 8bitDream/codespell
Add a codespell exception for kotlin OptIn
2023-06-10 12:27:19 -04:00
Abandoned Cart
d8609eef89 Add a codespell exception for kotlin OptIn 2023-06-10 05:35:57 -04:00
bunnei
f759ff3a5c Merge pull request #10691 from t895/nro-check
android: Add proper homebrew check
2023-06-09 23:59:51 -07:00
Charles Lombardo
72d9dc9a3f android: Add proper homebrew check 2023-06-09 20:17:51 -04:00
bunnei
55b543f466 Merge pull request #10686 from t895/version-check
android: Fix input overlay version check
2023-06-09 15:27:21 -07:00
Charles Lombardo
3cce51d25b android: Fix input overlay version check 2023-06-09 15:15:57 -04:00
liamwhite
4d395b3b72 Merge pull request #10614 from xcfrg/shader-backend-status-bar
yuzu: add opengl shader backend info in status bar
2023-06-09 09:46:11 -04:00
Liam
5a0d4e1d38 qt: persist framerate sync option 2023-06-09 09:40:34 -04:00
liamwhite
b3e2c9f9f1 Merge pull request #10623 from german77/backup
service: nfc: Add backup support
2023-06-08 21:54:12 -04:00
liamwhite
2a1acbfb4d Merge pull request #10666 from liamwhite/my-framerate-is-fine
nvnflinger: allow locking framerate during video playback
2023-06-08 21:53:57 -04:00
liamwhite
a57150afbd Merge pull request #10676 from bunnei/fix-mi-5-android
android: EmulationActivity: Fix orientation on Mi Pad 5.
2023-06-08 21:53:51 -04:00
liamwhite
60cc611f38 Merge pull request #10677 from tokarevart/fix-warning
Fix a potentially uninitialized local variable warning causing a build error
2023-06-08 21:53:40 -04:00
bunnei
064bad6ddf android: EmulationActivity: Fix orientation on Mi Pad 5. 2023-06-08 17:20:13 -07:00
Tokarev Artem
007c3fa7df Fix potentially uninitialized local variable warning 2023-06-09 05:12:22 +05:00
Liam
74671186bf vk_blit_screen: use higher bit depth for fxaa 2023-06-08 11:27:57 -04:00
Liam
6c34adb1de nvnflinger: allow locking framerate during video playback 2023-06-08 01:15:51 -04:00
german77
107aa52cdb service: nfc: Add backup support 2023-06-06 17:06:21 -06:00
xcfrg
a64ad8315f yuzu: add opengl shader backend info in status bar 2023-06-04 17:24:30 -04:00
56 changed files with 858 additions and 192 deletions

View File

@@ -3,4 +3,4 @@
[codespell]
skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES
ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink
ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M0,720L0,667Q0,628.43 41.5,604.22Q83,580 150.38,580Q162.54,580 173.77,580.5Q185,581 196,582.65Q188,600 184,617.82Q180,635.63 180,655L180,720L0,720ZM240,720L240,655Q240,623 257.5,596.5Q275,570 307,550Q339,530 383.5,520Q428,510 480,510Q533,510 577.5,520Q622,530 654,550Q686,570 703,596.5Q720,623 720,655L720,720L240,720ZM780,720L780,655Q780,635.14 776.5,617.57Q773,600 765,582.73Q776,581 787.17,580.5Q798.34,580 810,580Q877.5,580 918.75,603.77Q960,627.54 960,667L960,720L780,720ZM300,660L660,660L660,654Q660,617 609.5,593.5Q559,570 480,570Q401,570 350.5,593.5Q300,617 300,655L300,660ZM149.57,550Q121,550 100.5,529.44Q80,508.88 80,480Q80,451 100.56,430.5Q121.13,410 150,410Q179,410 199.5,430.5Q220,451 220,480.43Q220,509 199.5,529.5Q179,550 149.57,550ZM809.57,550Q781,550 760.5,529.44Q740,508.88 740,480Q740,451 760.56,430.5Q781.13,410 810,410Q839,410 859.5,430.5Q880,451 880,480.43Q880,509 859.5,529.5Q839,550 809.57,550ZM480,480Q430,480 395,445Q360,410 360,360Q360,309 395,274.5Q430,240 480,240Q531,240 565.5,274.5Q600,309 600,360Q600,410 565.5,445Q531,480 480,480ZM480.35,420Q506,420 523,402.65Q540,385.3 540,359.65Q540,334 522.85,317Q505.7,300 480.35,300Q455,300 437.5,317.15Q420,334.3 420,359.65Q420,385 437.35,402.5Q454.7,420 480.35,420ZM480,660L480,660Q480,660 480,660Q480,660 480,660Q480,660 480,660Q480,660 480,660L480,660L480,660ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z"/>
</vector>

View File

@@ -11,6 +11,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.NFC" />

View File

@@ -223,6 +223,8 @@ object NativeLibrary {
external fun getCompany(filename: String): String
external fun isHomebrew(filename: String): Boolean
external fun setAppDirectory(directory: String)
external fun initializeGpuDriver(
@@ -464,6 +466,17 @@ object NativeLibrary {
*/
external fun submitInlineKeyboardInput(key_code: Int)
/**
* Connects to a room (similar with desktop version's "direct connect")
*/
external fun connectToRoom(nickname: String, server_addr: String, server_port: Int, password: String)
/**
* Returns the state of the room member (client)
* @return The state as a string
*/
external fun getRoomMemberState(): String
/**
* Button type for use in onTouchEvent
*/

View File

@@ -10,6 +10,8 @@ import android.content.Context
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MultiplayerHelper
import org.yuzu.yuzu_emu.utils.NetworkHelper
import java.io.File
fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
@@ -46,6 +48,8 @@ class YuzuApplication : Application() {
documentsTree = DocumentsTree()
DirectoryInitialization.start(applicationContext)
GpuDriverHelper.initializeDriverParameters(applicationContext)
NetworkHelper.getRoute(applicationContext)
MultiplayerHelper.initRoom(applicationContext)
NativeLibrary.logDeviceInfo()
createNotificationChannels();

View File

@@ -6,15 +6,12 @@ package org.yuzu.yuzu_emu.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Rect
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.hardware.display.DisplayManager
import android.os.Bundle
import android.view.Display
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
@@ -23,7 +20,6 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
@@ -39,7 +35,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.fragments.EmulationFragment
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
import org.yuzu.yuzu_emu.utils.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.NfcReader
@@ -148,11 +143,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onResume()
nfcReader.startScanning()
startMotionSensorListener()
NativeLibrary.notifyOrientationChange(
EmulationMenuSettings.landscapeScreenLayout,
getAdjustedRotation()
)
}
override fun onPause() {
@@ -258,23 +248,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
private fun getAdjustedRotation():Int {
val rotation = getSystemService<DisplayManager>()!!.getDisplay(Display.DEFAULT_DISPLAY).rotation
val config: Configuration = resources.configuration
if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 ||
(config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0) {
return rotation
}
when (rotation) {
Surface.ROTATION_0 -> return Surface.ROTATION_90
Surface.ROTATION_90 -> return Surface.ROTATION_0
Surface.ROTATION_180 -> return Surface.ROTATION_270
Surface.ROTATION_270 -> return Surface.ROTATION_180
}
return rotation
}
private fun restoreState(savedInstanceState: Bundle) {
game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
}

View File

@@ -107,7 +107,9 @@ class Settings {
const val SECTION_RENDERER = "Renderer"
const val SECTION_AUDIO = "Audio"
const val SECTION_CPU = "Cpu"
const val SECTION_NETWORK = "Network"
const val SECTION_THEME = "Theme"
const val SECTION_MULTIPLAYER = "Multiplayer"
const val SECTION_DEBUG = "Debug"
const val PREF_OVERLAY_INIT = "OverlayInit"
@@ -130,6 +132,14 @@ class Settings {
const val PREF_BUTTON_TOGGLE_13 = "buttonToggle13"
const val PREF_BUTTON_TOGGLE_14 = "buttonToggle14"
const val PREF_FORCE_WIFI = "Network_ForceWifi"
const val PREF_ROOM_ADDRESS = "MultiplayerRoom_ServerAddress"
const val PREF_ROOM_PORT = "MultiplayerRoom_ServerPort"
const val PREF_ROOM_NICKNAME = "MultiplayerRoom_Nickname"
const val PREF_ROOM_PASSWORD = "MultiplayerRoom_Password"
const val PREF_ROOM_CONNECT_ON_START = "MultiplayerRoom_ConnectOnStart"
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
@@ -151,6 +161,7 @@ class Settings {
SECTION_SYSTEM,
SECTION_RENDERER,
SECTION_AUDIO,
SECTION_NETWORK,
SECTION_CPU
)
}

View File

@@ -8,7 +8,8 @@ enum class StringSetting(
override val section: String,
override val defaultValue: String
) : AbstractStringSetting {
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"),
NETWORK_ROUTE("network_route", Settings.SECTION_NETWORK, ";;;;");
override var string: String = defaultValue
@@ -27,7 +28,8 @@ enum class StringSetting(
companion object {
private val NOT_RUNTIME_EDITABLE = listOf(
CUSTOM_RTC
CUSTOM_RTC,
NETWORK_ROUTE
)
fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }

View File

@@ -35,5 +35,6 @@ abstract class SettingsItem(
const val TYPE_STRING_SINGLE_CHOICE = 5
const val TYPE_DATETIME_SETTING = 6
const val TYPE_RUNNABLE = 7
const val TYPE_TEXT_SETTING = 8
}
}

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
class TextSetting(
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int,
val key: String? = null,
private val defaultValue: String? = null
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_TEXT_SETTING
val value: String
get() = if (setting != null) {
val setting = setting as AbstractStringSetting
setting.string
} else {
defaultValue!!
}
fun setSelectedValue(string: String): AbstractStringSetting {
val stringSetting = setting as AbstractStringSetting
stringSetting.string = string
return stringSetting
}
}

View File

@@ -7,13 +7,14 @@ import android.content.Context
import android.content.DialogInterface
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.text.TextWatcher
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.setFragmentResultListener
import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -21,6 +22,7 @@ import com.google.android.material.slider.Slider
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
@@ -83,6 +85,10 @@ class SettingsAdapter(
RunnableViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
SettingsItem.TYPE_TEXT_SETTING -> {
TextSettingViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
else -> {
// TODO: Create an error view since we can't return null now
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
@@ -167,6 +173,7 @@ class SettingsAdapter(
.setSelection(storedTime)
.setTitleText(R.string.select_rtc_date)
.build()
val timePicker: MaterialTimePicker = MaterialTimePicker.Builder()
.setTimeFormat(timeFormat)
.setHour(calendar.get(Calendar.HOUR_OF_DAY))
@@ -199,6 +206,38 @@ class SettingsAdapter(
)
}
fun onTextSettingClick(item: TextSetting, position: Int) {
clickedItem = item
clickedPosition = position
var value = item.value
val inflater = LayoutInflater.from(context)
val editTextBinding = DialogEditTextBinding.inflate(inflater)
editTextBinding.editText.setText(value)
editTextBinding.editText.doAfterTextChanged {
value = it.toString()
}
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
.setView(editTextBinding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (item.value != value) {
fragmentView.onSettingChanged()
}
notifyItemChanged(clickedPosition)
val setting = item.setSelectedValue(value)
fragmentView.putSetting(setting)
clickedItem = null
}
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
.show()
}
fun onSliderClick(item: SliderSetting, position: Int) {
clickedItem = item
clickedPosition = position

View File

@@ -7,12 +7,12 @@ import android.content.SharedPreferences
import android.os.Build
import android.text.TextUtils
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
@@ -67,6 +67,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_SYSTEM -> addSystemSettings(sl)
Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.SECTION_AUDIO -> addAudioSettings(sl)
Settings.SECTION_NETWORK -> addNetworkSettings(sl)
Settings.SECTION_MULTIPLAYER -> addMultiplayerSettings(sl)
Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl)
else -> {
@@ -102,6 +104,13 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_RENDERER
)
)
add(
SubmenuSetting(
R.string.preferences_network,
0,
Settings.SECTION_NETWORK
)
)
add(
SubmenuSetting(
R.string.preferences_audio,
@@ -437,6 +446,179 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
}
private fun addNetworkSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_network))
sl.apply {
val forceWifi: AbstractBooleanSetting = object : AbstractBooleanSetting {
override var boolean: Boolean
get() = preferences.getBoolean(Settings.PREF_FORCE_WIFI, false)
set(value) {
preferences.edit()
.putBoolean(Settings.PREF_FORCE_WIFI, value)
.apply()
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getBoolean(Settings.PREF_FORCE_WIFI, false).toString()
override val defaultValue: Any = false
}
add(
SwitchSetting(
forceWifi,
R.string.set_force_wifi,
R.string.force_wifi_desc,
"",
false
)
)
add(
TextSetting(
StringSetting.NETWORK_ROUTE,
R.string.set_network_route,
R.string.network_route_desc,
StringSetting.NETWORK_ROUTE.key,
StringSetting.NETWORK_ROUTE.defaultValue
)
)
}
}
private fun addMultiplayerSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_multiplayer))
sl.apply {
val serverAddress: AbstractStringSetting = object : AbstractStringSetting {
override var string: String
get() = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: ""
set(value) {
preferences.edit()
.putString(Settings.PREF_ROOM_ADDRESS, value)
.apply()
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: ""
override val defaultValue: Any = -1
}
val serverPort: AbstractStringSetting = object : AbstractStringSetting {
override var string: String
get() = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872"
set(value) {
preferences.edit()
.putString(Settings.PREF_ROOM_PORT, value)
.apply()
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872"
override val defaultValue: Any = "24872"
}
val nickname: AbstractStringSetting = object : AbstractStringSetting {
override var string: String
get() = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: ""
set(value) {
preferences.edit()
.putString(Settings.PREF_ROOM_NICKNAME, value)
.apply()
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: ""
override val defaultValue: Any = -1
}
val password: AbstractStringSetting = object : AbstractStringSetting {
override var string: String
get() = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: ""
set(value) {
preferences.edit()
.putString(Settings.PREF_ROOM_PASSWORD, value)
.apply()
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: ""
override val defaultValue: Any = -1
}
val connectOnStart: AbstractBooleanSetting = object : AbstractBooleanSetting {
override var boolean: Boolean
get() = preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false)
set(value) {
preferences.edit()
.putBoolean(Settings.PREF_ROOM_CONNECT_ON_START, value)
.apply()
}
override val key: String? = null
override val section: String? = null
override val isRuntimeEditable: Boolean = false
override val valueAsString: String
get() = preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false).toString()
override val defaultValue: Any = -1
}
add(
TextSetting(
serverAddress,
R.string.multiplayer_room_server_address,
0,
"",
""
)
)
add(
TextSetting(
serverPort,
R.string.multiplayer_room_server_port,
0,
"",
""
)
)
add(
TextSetting(
nickname,
R.string.multiplayer_room_nickname,
0,
"",
""
)
)
add(
TextSetting(
password,
R.string.multiplayer_room_password,
0,
"",
""
)
)
add(
SwitchSetting(
connectOnStart,
R.string.multiplayer_room_connect_on_start,
0,
"",
false
)
)
}
}
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
sl.apply {

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.TextSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
class TextSettingViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
private lateinit var setting: TextSetting
override fun bind(item: SettingsItem) {
setting = item as TextSetting
binding.textSettingName.setText(item.nameId)
if (item.descriptionId != 0) {
binding.textSettingDescription.setText(item.descriptionId)
binding.textSettingDescription.visibility = View.VISIBLE
}
}
override fun onClick(clicked: View) {
if (setting.isEditable) {
adapter.onTextSettingClick(setting, bindingAdapterPosition)
}
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
}
return false
}
}

View File

@@ -84,6 +84,11 @@ class HomeSettingsFragment : Fragment() {
R.string.theme_and_color_description,
R.drawable.ic_palette
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
HomeSetting(
R.string.preferences_multiplayer,
R.string.multiplayer_description,
R.drawable.ic_multiplayer
) { SettingsActivity.launch(requireContext(), Settings.SECTION_MULTIPLAYER, "") },
HomeSetting(
R.string.install_gpu_driver,
R.string.install_gpu_driver_description,

View File

@@ -127,13 +127,7 @@ class SearchFragment : Fragment() {
}
}
R.id.chip_homebrew -> {
baseList.filter {
Log.error("Guh - ${it.path}")
FileUtil.hasExtension(it.path, "nro")
|| FileUtil.hasExtension(it.path, "nso")
}
}
R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
R.id.chip_retail -> baseList.filter {
FileUtil.hasExtension(it.path, "xci")

View File

@@ -16,7 +16,8 @@ class Game(
val regions: String,
val path: String,
val gameId: String,
val company: String
val company: String,
val isHomebrew: Boolean
) : Parcelable {
val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${gameId}_LastPlayed"
@@ -31,6 +32,7 @@ class Game(
&& path == other.path
&& gameId == other.gameId
&& company == other.company
&& isHomebrew == other.isHomebrew
}
companion object {

View File

@@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
@@ -20,6 +22,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
import java.util.Locale
@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() {
private val _games = MutableLiveData<List<Game>>(emptyList())
val games: LiveData<List<Game>> get() = _games
@@ -49,7 +52,13 @@ class GamesViewModel : ViewModel() {
if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>()
storedGames.forEach {
val game: Game = Json.decodeFromString(it)
val game: Game
try {
game = Json.decodeFromString(it)
} catch (e: MissingFieldException) {
return@forEach
}
val gameExists =
DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
?.exists()

View File

@@ -765,18 +765,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
// If we have API access, calculate the safe area to draw the overlay
var cutoutLeft = 0
var cutoutBottom = 0
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
if (insets != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
insets.boundingRectTop.bottom.toFloat() else maxY
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
insets.boundingRectRight.left.toFloat() else maxX
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
if (insets != null) {
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
insets.boundingRectTop.bottom.toFloat() else maxY
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
insets.boundingRectRight.left.toFloat() else maxX
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
}
}
// This makes sure that if we have an inset on one side of the screen, we mirror it on

View File

@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences
import android.net.Uri
import androidx.preference.PreferenceManager
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
@@ -83,7 +82,8 @@ object GameHelper {
NativeLibrary.getRegions(filePath),
filePath,
gameId,
NativeLibrary.getCompany(filePath)
NativeLibrary.getCompany(filePath),
NativeLibrary.isHomebrew(filePath)
)
val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)

View File

@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.content.Context
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.features.settings.model.Settings
object MultiplayerHelper {
fun initRoom(context: Context) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
if(preferences.getBoolean(Settings.PREF_ROOM_CONNECT_ON_START, false)) {
val addr = preferences.getString(Settings.PREF_ROOM_ADDRESS, "") ?: ""
val port = preferences.getString(Settings.PREF_ROOM_PORT, "24872") ?: "24872"
val nickname = preferences.getString(Settings.PREF_ROOM_NICKNAME, "") ?: ""
val password = preferences.getString(Settings.PREF_ROOM_PASSWORD, "") ?: ""
NativeLibrary.connectToRoom(nickname, addr, port.toIntOrNull() ?: 0, password)
}
}
}

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.content.Context
import android.net.ConnectivityManager
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.features.settings.model.Settings
object NetworkHelper {
/**
* Gets available network interface info/route info - currently the active network info.
* @return The route info separated by semicolons (interface, address, netmask, gateway), or null if no networks are available.
*/
fun getRoute(context: Context): String? {
val connectivity =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
if(connectivity.isActiveNetworkMetered && preferences.getBoolean(Settings.PREF_FORCE_WIFI, false))
return null
val lp = connectivity.getLinkProperties(connectivity.activeNetwork) ?: return null
val ifName = lp.interfaceName
val addr = lp.linkAddresses[0]
val cidr = addr.prefixLength
val bits = 0xffffffff xor ((1 shl 32 - cidr)).toLong() - 1
val mask = String.format(
"%d.%d.%d.%d",
bits and 0x0000000000ff000000L shr 24,
bits and 0x000000000000ff0000L shr 16,
bits and 0x00000000000000ff00L shr 8,
bits and 0x0000000000000000ffL shr 0
)
val gw = lp.routes.last { it.isDefaultRoute }.gateway?.hostAddress
return "$ifName;$addr;$mask;$gw"
}
}

View File

@@ -244,6 +244,10 @@ void Config::ReadValues() {
ReadSetting("Audio", Settings::values.audio_output_device_id);
ReadSetting("Audio", Settings::values.volume);
// Network
Settings::values.network_route = config->GetString("Network", "network_route", "");
// Miscellaneous
// log_filter has a different default here than from common
Settings::values.log_filter = "*:Info";

View File

@@ -13,6 +13,9 @@
#include <android/api-level.h>
#include <android/native_window_jni.h>
#include <core/loader/nro.h>
#include <network/network.h>
#include <network/room_member.h>
#include "common/detached_tasks.h"
#include "common/dynamic_library.h"
@@ -93,14 +96,6 @@ public:
m_native_window = native_window;
}
u32 ScreenRotation() const {
return m_screen_rotation;
}
void SetScreenRotation(u32 screen_rotation) {
m_screen_rotation = screen_rotation;
}
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
const std::string& custom_driver_name,
const std::string& file_redirect_dir) {
@@ -281,6 +276,10 @@ public:
return GetRomMetadata(path).icon;
}
bool GetIsHomebrew(const std::string& path) {
return GetRomMetadata(path).isHomebrew;
}
void ResetRomMetadata() {
m_rom_metadata_cache.clear();
}
@@ -344,10 +343,35 @@ public:
return m_software_keyboard;
}
void DirectConnectToRoom(const std::string& nickname, const char* server_addr = "127.0.0.1",
u16 server_port = Network::DefaultRoomPort,
const std::string& password = "") {
auto room_network = m_system.GetRoomNetwork();
if (const auto member = room_network.GetRoomMember().lock()) {
// Prevent the user from trying to join a room while they are already joining.
if (member->GetState() == Network::RoomMember::State::Joining ||
member->IsConnected()) {
return;
} else {
member->Join(nickname, server_addr, server_port);
}
}
}
Network::RoomMember::State GetRoomMemberState() {
if (const auto member = m_system.GetRoomNetwork().GetRoomMember().lock()) {
return member->GetState();
} else {
return Network::RoomMember::State::Idle;
}
}
private:
struct RomMetadata {
std::string title;
std::vector<u8> icon;
bool isHomebrew;
};
RomMetadata GetRomMetadata(const std::string& path) {
@@ -360,11 +384,17 @@ private:
RomMetadata CacheRomMetadata(const std::string& path) {
const auto file = Core::GetGameFileFromPath(m_vfs, path);
const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
RomMetadata entry;
loader->ReadTitle(entry.title);
loader->ReadIcon(entry.icon);
if (loader->GetFileType() == Loader::FileType::NRO) {
auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
entry.isHomebrew = loader_nro->IsHomebrew();
} else {
entry.isHomebrew = false;
}
m_rom_metadata_cache[path] = entry;
@@ -388,7 +418,6 @@ private:
// Window management
std::unique_ptr<EmuWindow_Android> m_window;
ANativeWindow* m_native_window{};
u32 m_screen_rotation{};
// Core emulation
Core::System m_system;
@@ -414,10 +443,6 @@ private:
} // Anonymous namespace
u32 GetAndroidScreenRotation() {
return EmulationSession::GetInstance().ScreenRotation();
}
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
@@ -461,13 +486,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
EmulationSession::GetInstance().SurfaceChanged();
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_notifyOrientationChange(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jint layout_option,
jint rotation) {
return EmulationSession::GetInstance().SetScreenRotation(static_cast<u32>(rotation));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jstring j_directory) {
@@ -662,6 +680,12 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv
return env->NewStringUTF("");
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz,
[[maybe_unused]] jstring j_filename) {
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
[[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
// Create the default config.ini.
@@ -771,4 +795,40 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_submitInlineKeyboardInput(JNIEnv* env
EmulationSession::GetInstance().SoftwareKeyboard()->SubmitInlineKeyboardInput(j_key_code);
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_connectToRoom(JNIEnv* env, jclass clazz,
jstring nickname, jstring server_addr,
jint server_port, jstring password) {
EmulationSession::GetInstance().DirectConnectToRoom(GetJString(env, nickname),
GetJString(env, server_addr).c_str(),
server_port, GetJString(env, password));
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRoomMemberState(JNIEnv* env, jclass clazz) {
auto state = EmulationSession::GetInstance().GetRoomMemberState();
std::string state_str{};
switch (state) {
using State = Network::RoomMember::State;
case State::Uninitialized:
state_str = "Uninitialized";
break;
case State::Idle:
state_str = "Idle";
break;
case State::Joining:
state_str = "Joining";
break;
case State::Joined:
state_str = "Joined";
break;
case State::Moderator:
state_str = "Moderator";
break;
}
return env->NewStringUTF(state_str.c_str());
}
} // extern "C"

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M0,720L0,667Q0,628.43 41.5,604.22Q83,580 150.38,580Q162.54,580 173.77,580.5Q185,581 196,582.65Q188,600 184,617.82Q180,635.63 180,655L180,720L0,720ZM240,720L240,655Q240,623 257.5,596.5Q275,570 307,550Q339,530 383.5,520Q428,510 480,510Q533,510 577.5,520Q622,530 654,550Q686,570 703,596.5Q720,623 720,655L720,720L240,720ZM780,720L780,655Q780,635.14 776.5,617.57Q773,600 765,582.73Q776,581 787.17,580.5Q798.34,580 810,580Q877.5,580 918.75,603.77Q960,627.54 960,667L960,720L780,720ZM300,660L660,660L660,654Q660,617 609.5,593.5Q559,570 480,570Q401,570 350.5,593.5Q300,617 300,655L300,660ZM149.57,550Q121,550 100.5,529.44Q80,508.88 80,480Q80,451 100.56,430.5Q121.13,410 150,410Q179,410 199.5,430.5Q220,451 220,480.43Q220,509 199.5,529.5Q179,550 149.57,550ZM809.57,550Q781,550 760.5,529.44Q740,508.88 740,480Q740,451 760.56,430.5Q781.13,410 810,410Q839,410 859.5,430.5Q880,451 880,480.43Q880,509 859.5,529.5Q839,550 809.57,550ZM480,480Q430,480 395,445Q360,410 360,360Q360,309 395,274.5Q430,240 480,240Q531,240 565.5,274.5Q600,309 600,360Q600,410 565.5,445Q531,480 480,480ZM480.35,420Q506,420 523,402.65Q540,385.3 540,359.65Q540,334 522.85,317Q505.7,300 480.35,300Q455,300 437.5,317.15Q420,334.3 420,359.65Q420,385 437.35,402.5Q454.7,420 480.35,420ZM480,660L480,660Q480,660 480,660Q480,660 480,660Q480,660 480,660Q480,660 480,660L480,660L480,660ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z"/>
</vector>

View File

@@ -84,6 +84,7 @@
<string name="open_user_folder">Open yuzu folder</string>
<string name="open_user_folder_description">Manage yuzu\'s internal files</string>
<string name="theme_and_color_description">Modify the look of the app</string>
<string name="multiplayer_description">Play with your friends!</string>
<string name="no_file_manager">No file manager found</string>
<string name="notification_no_directory_link">Could not open yuzu directory</string>
<string name="notification_no_directory_link_description">Please locate the user folder with the file manager\'s side panel manually.</string>
@@ -151,6 +152,19 @@
<string name="use_custom_rtc_description">Allows you to set a custom real-time clock separate from your current system time.</string>
<string name="set_custom_rtc">Set custom RTC</string>
<!-- Network settings strings -->
<string name="network_route_desc">Sets the default network route</string>
<string name="set_network_route">Set network route</string>
<string name="set_force_wifi">Force Wi-Fi</string>
<string name="force_wifi_desc">Forces yuzu to use only Wi-Fi (non-metered connections in general)</string>
<!-- Multiplayer settings strings -->
<string name="multiplayer_room_server_address">Server address</string>
<string name="multiplayer_room_server_port">Port</string>
<string name="multiplayer_room_nickname">Nickname</string>
<string name="multiplayer_room_password">Password</string>
<string name="multiplayer_room_connect_on_start">Connect on start</string>
<!-- Graphics settings strings -->
<string name="renderer_api">API</string>
<string name="renderer_accuracy">Accuracy level</string>
@@ -203,9 +217,11 @@
<string name="preferences_settings">Settings</string>
<string name="preferences_general">General</string>
<string name="preferences_system">System</string>
<string name="preferences_network">Network</string>
<string name="preferences_graphics">Graphics</string>
<string name="preferences_audio">Audio</string>
<string name="preferences_theme">Theme and color</string>
<string name="preferences_multiplayer">Multiplayer</string>
<string name="preferences_debug">Debug</string>
<!-- ROM loading errors -->

View File

@@ -47,12 +47,4 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
return *adsp;
}
void AudioCore::SetNVDECActive(bool active) {
nvdec_active = active;
}
bool AudioCore::IsNVDECActive() const {
return nvdec_active;
}
} // namespace AudioCore

View File

@@ -57,18 +57,6 @@ public:
*/
AudioRenderer::ADSP::ADSP& GetADSP();
/**
* Toggle NVDEC state, used to avoid stall in playback.
*
* @param active - Set true if nvdec is active, otherwise false.
*/
void SetNVDECActive(bool active);
/**
* Get NVDEC state.
*/
bool IsNVDECActive() const;
private:
/**
* Create the sinks on startup.
@@ -83,8 +71,6 @@ private:
std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule
std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
/// Is NVDec currently active?
bool nvdec_active{false};
};
} // namespace AudioCore

View File

@@ -10,6 +10,7 @@
// Sub-directories contained within a yuzu data directory
#define AMIIBO_DIR "amiibo"
#define CACHE_DIR "cache"
#define CONFIG_DIR "config"
#define DUMP_DIR "dump"

View File

@@ -114,6 +114,7 @@ public:
#endif
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);

View File

@@ -12,6 +12,7 @@ namespace Common::FS {
enum class YuzuPath {
YuzuDir, // Where yuzu stores its data.
AmiiboDir, // Where Amiibo backups are stored.
CacheDir, // Where cached filesystem data is stored.
ConfigDir, // Where config files are stored.
DumpDir, // Where dumped data is stored.

View File

@@ -235,6 +235,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);
values.enable_compute_pipelines.SetGlobal(true);
values.use_video_framerate.SetGlobal(true);
// System
values.language_index.SetGlobal(true);

View File

@@ -482,6 +482,7 @@ struct Values {
SwitchableSetting<AstcRecompression, true> astc_recompression{
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
"astc_recompression"};
SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
SwitchableSetting<u8> bg_red{0, "bg_red"};
SwitchableSetting<u8> bg_green{0, "bg_green"};
@@ -580,6 +581,7 @@ struct Values {
// Network
Setting<std::string> network_interface{std::string(), "network_interface"};
Setting<std::string> network_route{std::string(), "network_route"};
// WebService
Setting<bool> enable_telemetry{true, "enable_telemetry"};

View File

@@ -48,7 +48,7 @@ std::array<u8, 0x10> ConstructFromRawString(std::string_view raw_string) {
}
std::array<u8, 0x10> ConstructFromFormattedString(std::string_view formatted_string) {
std::array<u8, 0x10> uuid;
std::array<u8, 0x10> uuid{};
size_t i = 0;

View File

@@ -216,6 +216,14 @@ struct System::Impl {
}
}
void SetNVDECActive(bool is_nvdec_active) {
nvdec_active = is_nvdec_active;
}
bool GetNVDECActive() {
return nvdec_active;
}
void InitializeDebugger(System& system, u16 port) {
debugger = std::make_unique<Debugger>(system, port);
}
@@ -485,6 +493,8 @@ struct System::Impl {
std::atomic_bool is_powered_on{};
bool exit_lock = false;
bool nvdec_active{};
Reporter reporter;
std::unique_ptr<Memory::CheatEngine> cheat_engine;
std::unique_ptr<Tools::Freezer> memory_freezer;
@@ -594,6 +604,14 @@ void System::UnstallApplication() {
impl->UnstallApplication();
}
void System::SetNVDECActive(bool is_nvdec_active) {
impl->SetNVDECActive(is_nvdec_active);
}
bool System::GetNVDECActive() {
return impl->GetNVDECActive();
}
void System::InitializeDebugger() {
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
}

View File

@@ -189,6 +189,9 @@ public:
std::unique_lock<std::mutex> StallApplication();
void UnstallApplication();
void SetNVDECActive(bool is_nvdec_active);
[[nodiscard]] bool GetNVDECActive();
/**
* Initialize the debugger.
*/

View File

@@ -12,6 +12,11 @@
#pragma warning(pop)
#endif
#include <fmt/format.h>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/input.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -136,7 +141,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
if (!NFP::AmiiboCrypto::IsKeyAvailable()) {
LOG_INFO(Service_NFC, "Loading amiibo without keys");
memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
BuildAmiiboWithoutKeys();
BuildAmiiboWithoutKeys(tag_data, encrypted_tag_data);
is_plain_amiibo = true;
is_write_protected = true;
return true;
@@ -366,16 +371,25 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
// The loaded amiibo is not encrypted
if (is_plain_amiibo) {
std::vector<u8> data(sizeof(NFP::NTAG215File));
memcpy(data.data(), &tag_data, sizeof(tag_data));
WriteBackupData(tag_data.uid, data);
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
}
if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
return ResultCorruptedData;
bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess();
LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
}
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
WriteBackupData(encrypted_tag_data.uuid.uid, data);
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
@@ -470,6 +484,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
if (is_plain_amiibo) {
memcpy(data.data(), &tag_data, sizeof(tag_data));
WriteBackupData(tag_data.uid, data);
} else {
if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Failed to encode data");
@@ -477,6 +492,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
}
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
WriteBackupData(encrypted_tag_data.uuid.uid, data);
}
if (!npad_device->WriteNfc(data)) {
@@ -488,7 +504,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
}
Result NfcDevice::Restore() {
if (device_state != DeviceState::TagMounted) {
if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ResultTagRemoved;
@@ -496,13 +512,59 @@ Result NfcDevice::Restore() {
return ResultWrongDeviceState;
}
if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
return ResultWrongDeviceState;
NFC::TagInfo tag_info{};
std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
Result result = GetTagInfo(tag_info, false);
if (result.IsError()) {
return result;
}
// TODO: Load amiibo from backup on system
LOG_ERROR(Service_NFP, "Not Implemented");
result = ReadBackupData(tag_info.uuid, data);
if (result.IsError()) {
return result;
}
NFP::NTAG215File temporary_tag_data{};
NFP::EncryptedNTAG215File temporary_encrypted_tag_data{};
// Fallback for encrypted amiibos without keys
if (is_write_protected) {
return ResultWriteAmiiboFailed;
}
// Fallback for plain amiibos
if (is_plain_amiibo) {
LOG_INFO(Service_NFP, "Restoring backup of plain amiibo");
memcpy(&temporary_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
temporary_encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(temporary_tag_data);
}
if (!is_plain_amiibo) {
LOG_INFO(Service_NFP, "Restoring backup of encrypted amiibo");
temporary_tag_data = {};
memcpy(&temporary_encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
}
if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) {
return ResultNotAnAmiibo;
}
if (!is_plain_amiibo) {
if (!NFP::AmiiboCrypto::DecodeAmiibo(temporary_encrypted_tag_data, temporary_tag_data)) {
LOG_ERROR(Service_NFP, "Can't decode amiibo");
return ResultCorruptedData;
}
}
// Overwrite tag contents with backup and mount the tag
tag_data = temporary_tag_data;
encrypted_tag_data = temporary_encrypted_tag_data;
device_state = DeviceState::TagMounted;
mount_target = NFP::MountTarget::All;
is_data_moddified = true;
return ResultSuccess;
}
@@ -1132,13 +1194,69 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) {
return FlushWithBreak(break_type);
}
Result NfcDevice::ReadBackupData(std::span<u8> data) const {
// Not implemented
Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const {
constexpr auto backup_dir = "backup";
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) {
return ResultUnableToAccessBackupFile;
}
return ResultSuccess;
}
Result NfcDevice::WriteBackupData(std::span<const u8> data) {
// Not implemented
Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const {
constexpr auto backup_dir = "backup";
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!keys_file.IsOpen()) {
LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
return ResultUnableToAccessBackupFile;
}
if (keys_file.Read(data) != data.size()) {
LOG_ERROR(Service_NFP, "Failed to read amiibo backup");
return ResultUnableToAccessBackupFile;
}
return ResultSuccess;
}
Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) {
constexpr auto backup_dir = "backup";
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
if (HasBackup(uid).IsError()) {
if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) {
return ResultBackupPathAlreadyExist;
}
if (!Common::FS::NewFile(yuzu_amiibo_dir / backup_dir / file_name)) {
return ResultBackupPathAlreadyExist;
}
}
const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
Common::FS::FileAccessMode::ReadWrite,
Common::FS::FileType::BinaryFile};
if (!keys_file.IsOpen()) {
LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
return ResultUnableToAccessBackupFile;
}
if (keys_file.Write(data) != data.size()) {
LOG_ERROR(Service_NFP, "Failed to write amiibo backup");
return ResultUnableToAccessBackupFile;
}
return ResultSuccess;
}
@@ -1177,7 +1295,8 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
return amiibo_name;
}
void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) {
void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings,
const NFP::AmiiboName& amiibo_name) const {
std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};
// Convert from utf8 to utf16
@@ -1258,22 +1377,23 @@ void NfcDevice::UpdateRegisterInfoCrc() {
tag_data.register_info_crc = crc.checksum();
}
void NfcDevice::BuildAmiiboWithoutKeys() {
void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
const NFP::EncryptedNTAG215File& encrypted_file) const {
Service::Mii::MiiManager manager;
auto& settings = tag_data.settings;
auto& settings = stubbed_tag_data.settings;
tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_tag_data);
stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
// Common info
tag_data.write_counter = 0;
tag_data.amiibo_version = 0;
stubbed_tag_data.write_counter = 0;
stubbed_tag_data.amiibo_version = 0;
settings.write_date = GetAmiiboDate(GetCurrentPosixTime());
// Register info
SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
settings.settings.font_region.Assign(0);
settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
// Admin info
settings.settings.amiibo_initialized.Assign(1);

View File

@@ -86,8 +86,9 @@ public:
Result GetAll(NFP::NfpData& data) const;
Result SetAll(const NFP::NfpData& data);
Result BreakTag(NFP::BreakType break_type);
Result ReadBackupData(std::span<u8> data) const;
Result WriteBackupData(std::span<const u8> data);
Result HasBackup(const NFC::UniqueSerialNumber& uid) const;
Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const;
Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data);
Result WriteNtf(std::span<const u8> data);
u64 GetHandle() const;
@@ -103,14 +104,15 @@ private:
void CloseNfcTag();
NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name);
void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) const;
NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const;
u64 GetCurrentPosixTime() const;
u64 RemoveVersionByte(u64 application_id) const;
void UpdateSettingsCrc();
void UpdateRegisterInfoCrc();
void BuildAmiiboWithoutKeys();
void BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
const NFP::EncryptedNTAG215File& encrypted_file) const;
bool is_controller_set{};
int callback_key;

View File

@@ -543,9 +543,14 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
std::shared_ptr<NfcDevice> device = nullptr;
auto result = GetDeviceHandle(device_handle, device);
NFC::TagInfo tag_info{};
if (result.IsSuccess()) {
result = device->ReadBackupData(data);
result = device->GetTagInfo(tag_info, false);
}
if (result.IsSuccess()) {
result = device->ReadBackupData(tag_info.uuid, data);
result = VerifyDeviceResult(device, result);
}
@@ -557,9 +562,14 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
std::shared_ptr<NfcDevice> device = nullptr;
auto result = GetDeviceHandle(device_handle, device);
NFC::TagInfo tag_info{};
if (result.IsSuccess()) {
result = device->WriteBackupData(data);
result = device->GetTagInfo(tag_info, false);
}
if (result.IsSuccess()) {
result = device->WriteBackupData(tag_info.uuid, data);
result = VerifyDeviceResult(device, result);
}

View File

@@ -302,7 +302,7 @@ Result NfcInterface::TranslateResultToServiceError(Result result) const {
return TranslateResultToNfp(result);
}
default:
if (result != ResultUnknown216) {
if (result != ResultBackupPathAlreadyExist) {
return result;
}
return ResultUnknown74;
@@ -343,6 +343,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
if (result == ResultApplicationAreaIsNotInitialized) {
return NFP::ResultApplicationAreaIsNotInitialized;
}
if (result == ResultCorruptedDataWithBackup) {
return NFP::ResultCorruptedDataWithBackup;
}
if (result == ResultCorruptedData) {
return NFP::ResultCorruptedData;
}
@@ -355,6 +358,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
if (result == ResultNotAnAmiibo) {
return NFP::ResultNotAnAmiibo;
}
if (result == ResultUnableToAccessBackupFile) {
return NFP::ResultUnableToAccessBackupFile;
}
LOG_WARNING(Service_NFC, "Result conversion not handled");
return result;
}

View File

@@ -9,20 +9,22 @@ namespace Service::NFC {
constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64);
constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65);
constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68);
constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFC, 68);
constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73);
constexpr Result ResultUnknown74(ErrorModule::NFC, 74);
constexpr Result ResultUnknown76(ErrorModule::NFC, 76);
constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77);
constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80);
constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFC, 88);
constexpr Result ResultTagRemoved(ErrorModule::NFC, 97);
constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
constexpr Result ResultUnknown216(ErrorModule::NFC, 216);
constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFC, 113);
constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFC, 120);
constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFC, 128);
constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136);
constexpr Result ResultCorruptedData(ErrorModule::NFC, 144);
constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152);
constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168);
constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178);
constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216);
} // namespace Service::NFC

View File

@@ -126,7 +126,7 @@ void Interface::Flush(HLERequestContext& ctx) {
void Interface::Restore(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
auto result = GetManager()->Restore(device_handle);
result = TranslateResultToServiceError(result);
@@ -394,7 +394,7 @@ void Interface::BreakTag(HLERequestContext& ctx) {
void Interface::ReadBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
std::vector<u8> backup_data{};
auto result = GetManager()->ReadBackupData(device_handle, backup_data);
@@ -412,7 +412,7 @@ void Interface::WriteBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto backup_data_buffer{ctx.ReadBuffer()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer);
result = TranslateResultToServiceError(result);

View File

@@ -17,9 +17,11 @@ constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
constexpr Result ResultTagRemoved(ErrorModule::NFP, 97);
constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFP, 136);
constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFP, 200);
} // namespace Service::NFP

View File

@@ -69,7 +69,7 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
void nvhost_nvdec::OnOpen(DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
system.AudioCore().SetNVDECActive(true);
system.SetNVDECActive(true);
}
void nvhost_nvdec::OnClose(DeviceFD fd) {
@@ -79,7 +79,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) {
if (iter != host1x_file.fd_to_id.end()) {
system.GPU().ClearCdmaInstance(iter->second);
}
system.AudioCore().SetNVDECActive(false);
system.SetNVDECActive(false);
}
} // namespace Service::Nvidia::Devices

View File

@@ -324,6 +324,10 @@ s64 Nvnflinger::GetNextTicks() const {
speed_scale = 0.01f;
}
}
if (system.GetNVDECActive() && settings.use_video_framerate.GetValue()) {
// Run at intended presentation rate during video playback.
speed_scale = 1.f;
}
// As an extension, treat nonpositive swap interval as framerate multiplier.
const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval)

View File

@@ -6,6 +6,8 @@
#include <sstream>
#include <vector>
#include <boost/algorithm/string.hpp>
#include "common/bit_cast.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -18,8 +20,10 @@
#include <iphlpapi.h>
#else
#include <cerrno>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/if.h>
#endif
namespace Network {
@@ -91,8 +95,28 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
return result;
}
#else
#elif defined(__ANDROID__)
std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
std::vector<NetworkInterface> result;
std::vector<std::string> route_parts;
boost::split(route_parts, Settings::values.network_route.GetValue(), boost::is_any_of(";"));
struct in_addr ip {
}, sm{}, gw{};
inet_pton(AF_INET, route_parts[1].c_str(), &ip);
inet_pton(AF_INET, route_parts[2].c_str(), &sm);
inet_pton(AF_INET, route_parts[3].c_str(), &gw);
result.emplace_back(
NetworkInterface{.name{route_parts[0]}, .ip_address{ip}, .subnet_mask{sm}, .gateway{gw}});
return result;
}
#else
std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
struct ifaddrs* ifaddr = nullptr;
@@ -187,6 +211,10 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
#endif
std::optional<NetworkInterface> GetSelectedNetworkInterface() {
#ifdef __ANDROID__
Network::SelectFirstNetworkInterface(); // TODO ANDROID
#endif
const auto& selected_network_interface = Settings::values.network_interface.GetValue();
const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
if (network_interfaces.empty()) {

View File

@@ -33,7 +33,8 @@ static_assert(sizeof(NroSegmentHeader) == 0x8, "NroSegmentHeader has incorrect s
struct NroHeader {
INSERT_PADDING_BYTES(0x4);
u32_le module_header_offset;
INSERT_PADDING_BYTES(0x8);
u32 magic_ext1;
u32 magic_ext2;
u32_le magic;
INSERT_PADDING_BYTES(0x4);
u32_le file_size;
@@ -124,6 +125,16 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) {
return FileType::Error;
}
bool AppLoader_NRO::IsHomebrew() {
// Read NSO header
NroHeader nro_header{};
if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
return false;
}
return nro_header.magic_ext1 == Common::MakeMagic('H', 'O', 'M', 'E') &&
nro_header.magic_ext2 == Common::MakeMagic('B', 'R', 'E', 'W');
}
static constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}

View File

@@ -38,6 +38,8 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& nro_file);
bool IsHomebrew();
FileType GetFileType() const override {
return IdentifyType(file);
}

View File

@@ -37,10 +37,6 @@
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#ifdef ANDROID
extern u32 GetAndroidScreenRotation();
#endif
namespace Vulkan {
namespace {
@@ -78,47 +74,6 @@ struct ScreenRectVertex {
}
};
#ifdef ANDROID
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
constexpr u32 ROTATION_0 = 0;
constexpr u32 ROTATION_90 = 1;
constexpr u32 ROTATION_180 = 2;
constexpr u32 ROTATION_270 = 3;
// clang-format off
switch (GetAndroidScreenRotation()) {
case ROTATION_0:
// Desktop
return { 2.f / width, 0.f, 0.f, 0.f,
0.f, 2.f / height, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
-1.f, -1.f, 0.f, 1.f};
case ROTATION_180:
// Reverse desktop
return {-2.f / width, 0.f, 0.f, 0.f,
0.f, -2.f / height, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
1.f, 1.f, 0.f, 1.f};
case ROTATION_270:
// Reverse landscape
return { 0.f, -2.f / width, 0.f, 0.f,
2.f / height, 0.f, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
-1.f, 1.f, 0.f, 1.f};
case ROTATION_90:
default:
// Landscape
return { 0.f, 2.f / width, 0.f, 0.f,
-2.f / height, 0.f, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
1.f, -1.f, 0.f, 1.f};
}
// clang-format on
}
#else
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
// clang-format off
return { 2.f / width, 0.f, 0.f, 0.f,
@@ -128,8 +83,6 @@ std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
// clang-format on
}
#endif
u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
using namespace VideoCore::Surface;
return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
@@ -1159,7 +1112,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = GetFormat(framebuffer),
.format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
.extent =
{
.width = (up_scale * framebuffer.width) >> down_shift,
@@ -1180,14 +1133,14 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
const auto create_commit = [&](vk::Image& image) {
return memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
};
const auto create_image_view = [&](vk::Image& image) {
const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) {
return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = *image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = GetFormat(framebuffer),
.format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
.components =
{
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -1217,7 +1170,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
const u32 down_shift = Settings::values.resolution_info.down_shift;
aa_image = create_image(true, up_scale, down_shift);
aa_commit = create_commit(aa_image);
aa_image_view = create_image_view(aa_image);
aa_image_view = create_image_view(aa_image, true);
VkExtent2D size{
.width = (up_scale * framebuffer.width) >> down_shift,
.height = (up_scale * framebuffer.height) >> down_shift,

View File

@@ -231,7 +231,12 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
#ifdef ANDROID
// On Android, do not allow surface rotation to deviate from the frontend.
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
#else
.preTransform = capabilities.currentTransform,
#endif
.compositeAlpha = alpha_flags,
.presentMode = present_mode,
.clipped = VK_FALSE,

View File

@@ -101,6 +101,12 @@ const std::map<Settings::RendererBackend, QString> Config::renderer_backend_text
{Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
};
const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
{Settings::ShaderBackend::GLSL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
{Settings::ShaderBackend::GLASM, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
{Settings::ShaderBackend::SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
};
// This shouldn't have anything except static initializers (no functions). So
// QKeySequence(...).toString() is NOT ALLOWED HERE.
// This must be in alphabetical order according to action name as it must have the same order as
@@ -754,6 +760,7 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.use_fast_gpu_time);
ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
ReadGlobalSetting(Settings::values.enable_compute_pipelines);
ReadGlobalSetting(Settings::values.use_video_framerate);
ReadGlobalSetting(Settings::values.bg_red);
ReadGlobalSetting(Settings::values.bg_green);
ReadGlobalSetting(Settings::values.bg_blue);
@@ -1409,6 +1416,7 @@ void Config::SaveRendererValues() {
WriteGlobalSetting(Settings::values.use_fast_gpu_time);
WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
WriteGlobalSetting(Settings::values.enable_compute_pipelines);
WriteGlobalSetting(Settings::values.use_video_framerate);
WriteGlobalSetting(Settings::values.bg_red);
WriteGlobalSetting(Settings::values.bg_green);
WriteGlobalSetting(Settings::values.bg_blue);

View File

@@ -54,6 +54,7 @@ public:
static const std::map<bool, QString> use_docked_mode_texts_map;
static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map;
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
static constexpr UISettings::Theme default_theme{
#ifdef _WIN32

View File

@@ -42,6 +42,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
Settings::values.use_vulkan_driver_pipeline_cache.GetValue());
ui->enable_compute_pipelines_checkbox->setChecked(
Settings::values.enable_compute_pipelines.GetValue());
ui->use_video_framerate_checkbox->setChecked(Settings::values.use_video_framerate.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setCurrentIndex(
@@ -91,6 +92,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines,
ui->enable_compute_pipelines_checkbox,
enable_compute_pipelines);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_video_framerate,
ui->use_video_framerate_checkbox, use_video_framerate);
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -125,6 +128,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
Settings::values.max_anisotropy.UsingGlobal());
ui->enable_compute_pipelines_checkbox->setEnabled(
Settings::values.enable_compute_pipelines.UsingGlobal());
ui->use_video_framerate_checkbox->setEnabled(
Settings::values.use_video_framerate.UsingGlobal());
return;
}
@@ -149,6 +154,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox,
Settings::values.enable_compute_pipelines,
enable_compute_pipelines);
ConfigurationShared::SetColoredTristate(ui->use_video_framerate_checkbox,
Settings::values.use_video_framerate,
use_video_framerate);
ConfigurationShared::SetColoredComboBox(
ui->gpu_accuracy, ui->label_gpu_accuracy,
static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));

View File

@@ -47,6 +47,7 @@ private:
ConfigurationShared::CheckState use_fast_gpu_time;
ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache;
ConfigurationShared::CheckState enable_compute_pipelines;
ConfigurationShared::CheckState use_video_framerate;
const Core::System& system;
};

View File

@@ -191,6 +191,16 @@ Compute pipelines are always enabled on all other drivers.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_video_framerate_checkbox">
<property name="toolTip">
<string>Run the game at normal speed during video playback, even when the framerate is unlocked.</string>
</property>
<property name="text">
<string>Sync to framerate of video playback</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="af_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_1">

View File

@@ -4116,7 +4116,13 @@ void GMainWindow::UpdateDockedButton() {
void GMainWindow::UpdateAPIText() {
const auto api = Settings::values.renderer_backend.GetValue();
const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second;
renderer_status_button->setText(renderer_status_text.toUpper());
renderer_status_button->setText(
api == Settings::RendererBackend::OpenGL
? tr("%1 %2").arg(
renderer_status_text.toUpper(),
Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue())
->second)
: renderer_status_text.toUpper());
}
void GMainWindow::UpdateFilterText() {

View File

@@ -81,6 +81,7 @@ void DirectConnectWindow::Connect() {
}
}
}
if (!ui->ip->hasAcceptableInput()) {
NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID);
return;