mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-24 10:36:55 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f307a0331d | |||
| cb85810ffd | |||
| 8ddd0f3cc4 | |||
| c52a7be00f | |||
| 37dc82d6d7 | |||
| b6a7c809eb | |||
| d2b43a2356 | |||
| 6d508f3eaf | |||
| 0871e16706 | |||
| 38816c4f31 | |||
| 251dc590dd | |||
| 3eb20d4d45 | |||
| fdf816c1ee | |||
| 88bfc04887 | |||
| 44e6f76c4c | |||
| b9ab300b97 | |||
| 376dc541ea |
+21
-33
@@ -1,44 +1,32 @@
|
||||
# User Handbook - Command Line
|
||||
|
||||
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept the same command line arguments.
|
||||
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments.
|
||||
|
||||
## eden
|
||||
|
||||
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application.
|
||||
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
|
||||
- `--config/-c`: Specify alternate configuration file.
|
||||
- `--fullscreen/-f`: Use fullscreen.
|
||||
- `--help/-h`: Display help.
|
||||
- `--game/-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
|
||||
- `--multiplayer/-m`: Specify multiplayer options.
|
||||
- `--program/-p`: Specify the program arguments to pass (optional).
|
||||
- `--user/-u <number>`: Specify the user index.
|
||||
- `--version/-v`: Display version and quit.
|
||||
- `--input-profile/-i <name>`: Specifies input profile name to use (for player #0 only).
|
||||
- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
|
||||
- `--filter/-x`: Sets the debug log filter irrespective of settings.
|
||||
- `--singlecore/-s`: Forces single-core regardless of settings.
|
||||
|
||||
Only the Qt frontend supports the following arguments:
|
||||
|
||||
- `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
|
||||
- `-f`: Use fullscreen.
|
||||
- `-u <number>`: Select the index of the user to load as.
|
||||
- `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
|
||||
- `-qlaunch`: Launch QLaunch.
|
||||
- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
|
||||
- Requires a copy of Atmosphere to be extracted onto `sdmc`.
|
||||
- This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
|
||||
- `-setup`: Launch setup applet.
|
||||
|
||||
`eden` (provided with `--room`), and `eden-room` supports the following options as well:
|
||||
## eden-cli
|
||||
|
||||
- `-n/--room-name`: The name of the room.
|
||||
- `-d/--room-description`: The room description.
|
||||
- `-s/--bind-address`: The bind address for the room.
|
||||
- `-p/--port`: The port used for the room.
|
||||
- `-m/--max-members`: The maximum number of players for this room.
|
||||
- `-w/--password`: The password for the room.
|
||||
- `-g/--preferred-game`: The preferred game for this room.
|
||||
- `-i/--preferred-game-id`: The preferred game-id for this room.
|
||||
- `-u/--username`: The username used for announce.
|
||||
- `-t/--token`: The token used for announce.
|
||||
- `-a/--web-api-url`: yuzu Web API url.
|
||||
- `-b/--ban-list-file`: The file for storing the room ban list.
|
||||
- `-l/--log-file`: The file for storing the room log.
|
||||
- `-h/--help`: Display this help and exit.
|
||||
- `-v/--version`: Output version information and exit.
|
||||
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
|
||||
- `--config/-c`: Specify alternate configuration file.
|
||||
- `--fullscreen/-f`: Set fullscreen.
|
||||
- `--help/-h`: Display help.
|
||||
- `--game/-g`: Specify the game to run.
|
||||
- `--multiplayer/-m`: Specify multiplayer options.
|
||||
- `--program/-p`: Specify the program arguments to pass (optional).
|
||||
- `--user/-u`: Specify the user index.
|
||||
- `--version/-v`: Display version and quit.
|
||||
- `--input-profile/-i`: Specifies input profile name to use (for player #0 only).
|
||||
- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
|
||||
- `--filter/-x`: Sets the debug log filter irrespective of settings.
|
||||
- `--singlecore/-s`: Forces single-core regardless of settings.
|
||||
|
||||
@@ -16,12 +16,16 @@ import java.io.FileOutputStream
|
||||
import java.security.KeyStore
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.LocaleList
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
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.Log
|
||||
import org.yuzu.yuzu_emu.utils.PowerStateUpdater
|
||||
import org.yuzu.yuzu_emu.utils.ControllerNavigationGlobalHook
|
||||
import java.util.Locale
|
||||
|
||||
fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
|
||||
|
||||
@@ -80,5 +84,38 @@ class YuzuApplication : Application() {
|
||||
|
||||
val appContext: Context
|
||||
get() = application.applicationContext
|
||||
|
||||
private val LANGUAGE_CODES = arrayOf(
|
||||
"system", "en", "es", "fr", "de", "it", "pt", "pt-BR", "ru", "ja", "ko",
|
||||
"zh-CN", "zh-TW", "pl", "cs", "nb", "hu", "uk", "vi", "id", "ar", "ckb", "fa", "he", "sr"
|
||||
)
|
||||
|
||||
fun applyLanguage(context: Context): Context {
|
||||
val languageIndex = IntSetting.APP_LANGUAGE.getInt()
|
||||
val langCode = if (languageIndex in LANGUAGE_CODES.indices) {
|
||||
LANGUAGE_CODES[languageIndex]
|
||||
} else {
|
||||
"system"
|
||||
}
|
||||
|
||||
if (langCode == "system") {
|
||||
return context
|
||||
}
|
||||
|
||||
val locale = when {
|
||||
langCode.contains("-") -> {
|
||||
val parts = langCode.split("-")
|
||||
Locale.Builder().setLanguage(parts[0]).setRegion(parts[1]).build()
|
||||
}
|
||||
else -> Locale.Builder().setLanguage(langCode).build()
|
||||
}
|
||||
|
||||
Locale.setDefault(locale)
|
||||
|
||||
val config = Configuration(context.resources.configuration)
|
||||
config.setLocales(LocaleList(locale))
|
||||
|
||||
return context.createConfigurationContext(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
mainHandler.postDelayed(romSwapStopTimeoutRunnable, ROM_SWAP_STOP_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(YuzuApplication.applyLanguage(base))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Log.gameLaunched = true
|
||||
ThemeHelper.setTheme(this)
|
||||
|
||||
@@ -38,6 +38,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
THEME("theme"),
|
||||
THEME_MODE("theme_mode"),
|
||||
STATIC_THEME_COLOR("static_theme_color"),
|
||||
APP_LANGUAGE("app_language"),
|
||||
OVERLAY_SCALE("control_scale"),
|
||||
OVERLAY_OPACITY("control_opacity"),
|
||||
LOCK_DRAWER("lock_drawer"),
|
||||
|
||||
+9
@@ -997,6 +997,15 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.enable_qlaunch_button_description,
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.APP_LANGUAGE,
|
||||
titleId = R.string.app_language,
|
||||
descriptionId = R.string.app_language_description,
|
||||
choicesId = R.array.appLanguageNames,
|
||||
valuesId = R.array.appLanguageValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_DEBUG,
|
||||
|
||||
+17
@@ -6,6 +6,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.content.Context
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
@@ -27,6 +29,7 @@ import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
@@ -35,6 +38,10 @@ class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(YuzuApplication.applyLanguage(base))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
ThemeHelper.setTheme(this)
|
||||
|
||||
@@ -141,6 +148,16 @@ class SettingsActivity : AppCompatActivity() {
|
||||
NativeConfig.savePerGameConfig()
|
||||
NativeConfig.unloadPerGameConfig()
|
||||
}
|
||||
|
||||
if (settingsViewModel.shouldRecreateForLanguageChange.value) {
|
||||
settingsViewModel.setShouldRecreateForLanguageChange(false)
|
||||
val relaunchIntent = packageManager?.getLaunchIntentForPackage(packageName)
|
||||
if (relaunchIntent != null) {
|
||||
relaunchIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK or android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(relaunchIntent)
|
||||
android.os.Process.killProcess(android.os.Process.myPid())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -482,6 +482,14 @@ class SettingsAdapter(
|
||||
position
|
||||
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||
|
||||
// reset language if detected
|
||||
if (item.setting.key == "app_language") {
|
||||
// recreate page apply language change instantly
|
||||
fragment.requireActivity().recreate()
|
||||
|
||||
settingsViewModel.setShouldRecreateForLanguageChange(true)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -387,6 +387,12 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
) {
|
||||
settingsViewModel.setShouldReloadSettingsList(true)
|
||||
}
|
||||
|
||||
if (scSetting.setting.key == "app_language") {
|
||||
settingsViewModel.setShouldRecreateForLanguageChange(true)
|
||||
// recreate page apply language change instantly
|
||||
requireActivity().recreate()
|
||||
}
|
||||
}
|
||||
|
||||
is StringSingleChoiceSetting -> {
|
||||
|
||||
+3
-1
@@ -1345,8 +1345,10 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.app_settings))
|
||||
add(IntSetting.APP_LANGUAGE.key)
|
||||
|
||||
if (NativeLibrary.isUpdateCheckerEnabled()) {
|
||||
add(HeaderSetting(R.string.app_settings))
|
||||
add(BooleanSetting.ENABLE_UPDATE_CHECKS.key)
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -57,6 +57,9 @@ class SettingsViewModel : ViewModel() {
|
||||
private val _shouldShowResetInputDialog = MutableStateFlow(false)
|
||||
val shouldShowResetInputDialog = _shouldShowResetInputDialog.asStateFlow()
|
||||
|
||||
private val _shouldRecreateForLanguageChange = MutableStateFlow(false)
|
||||
val shouldRecreateForLanguageChange = _shouldRecreateForLanguageChange.asStateFlow()
|
||||
|
||||
private val _shouldShowPathPicker = MutableStateFlow(false)
|
||||
val shouldShowPathPicker = _shouldShowPathPicker.asStateFlow()
|
||||
|
||||
@@ -115,6 +118,10 @@ class SettingsViewModel : ViewModel() {
|
||||
_shouldShowResetInputDialog.value = value
|
||||
}
|
||||
|
||||
fun setShouldRecreateForLanguageChange(value: Boolean) {
|
||||
_shouldRecreateForLanguageChange.value = value
|
||||
}
|
||||
|
||||
fun setShouldShowPathPicker(value: Boolean) {
|
||||
_shouldShowPathPicker.value = value
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.ui.main
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
@@ -70,6 +71,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
private val CHECKED_DECRYPTION = "CheckedDecryption"
|
||||
private var checkedDecryption = false
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(YuzuApplication.applyLanguage(base))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val splashScreen = installSplashScreen()
|
||||
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace AndroidSettings {
|
||||
Settings::Setting<s32> static_theme_color{linkage, 0, "static_theme_color", Settings::Category::Android};
|
||||
Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
|
||||
Settings::Category::Android};
|
||||
Settings::Setting<s32> app_language{linkage, 0, "app_language", Settings::Category::Android};
|
||||
Settings::Setting<bool> enable_update_checks{linkage, true, "enable_update_checks",
|
||||
Settings::Category::Android};
|
||||
Settings::Setting<bool> enable_folder_button{linkage, true, "enable_folder_button",
|
||||
|
||||
@@ -409,6 +409,62 @@
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="appLanguageNames">
|
||||
<item>@string/app_language_system</item>
|
||||
<item>@string/app_language_english</item>
|
||||
<item>@string/app_language_spanish</item>
|
||||
<item>@string/app_language_french</item>
|
||||
<item>@string/app_language_german</item>
|
||||
<item>@string/app_language_italian</item>
|
||||
<item>@string/app_language_portuguese</item>
|
||||
<item>@string/app_language_brazilian_portuguese</item>
|
||||
<item>@string/app_language_russian</item>
|
||||
<item>@string/app_language_japanese</item>
|
||||
<item>@string/app_language_korean</item>
|
||||
<item>@string/app_language_simplified_chinese</item>
|
||||
<item>@string/app_language_traditional_chinese</item>
|
||||
<item>@string/app_language_polish</item>
|
||||
<item>@string/app_language_czech</item>
|
||||
<item>@string/app_language_norwegian</item>
|
||||
<item>@string/app_language_hungarian</item>
|
||||
<item>@string/app_language_ukrainian</item>
|
||||
<item>@string/app_language_vietnamese</item>
|
||||
<item>@string/app_language_indonesian</item>
|
||||
<item>@string/app_language_arabic</item>
|
||||
<item>@string/app_language_central_kurdish</item>
|
||||
<item>@string/app_language_persian</item>
|
||||
<item>@string/app_language_hebrew</item>
|
||||
<item>@string/app_language_serbian</item>
|
||||
<item>@string/app_language_thai</item>
|
||||
</string-array>
|
||||
<integer-array name="appLanguageValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
<item>8</item>
|
||||
<item>9</item>
|
||||
<item>10</item>
|
||||
<item>11</item>
|
||||
<item>12</item>
|
||||
<item>13</item>
|
||||
<item>14</item>
|
||||
<item>15</item>
|
||||
<item>16</item>
|
||||
<item>17</item>
|
||||
<item>18</item>
|
||||
<item>19</item>
|
||||
<item>20</item>
|
||||
<item>21</item>
|
||||
<item>22</item>
|
||||
<item>23</item>
|
||||
<item>24</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="outputEngineEntries">
|
||||
<item>@string/auto</item>
|
||||
<item>@string/sdl3</item>
|
||||
|
||||
@@ -1275,6 +1275,42 @@
|
||||
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||
<string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
|
||||
|
||||
<!-- Buttons -->
|
||||
<string name="enable_folder_button">Folder</string>
|
||||
<string name="enable_folder_button_description">Show the button to add game folders</string>
|
||||
<string name="enable_qlaunch_button">QLaunch</string>
|
||||
<string name="enable_qlaunch_button_description">Show the button to launch QLaunch</string>
|
||||
|
||||
<!-- App Language -->
|
||||
<string name="app_language">App Language</string>
|
||||
<string name="app_language_description">Change the language of the app interface</string>
|
||||
<string name="app_language_system">Follow System</string>
|
||||
<string name="app_language_english" translatable="false">English</string>
|
||||
<string name="app_language_spanish" translatable="false">Español</string>
|
||||
<string name="app_language_french" translatable="false">Français</string>
|
||||
<string name="app_language_german" translatable="false">Deutsch</string>
|
||||
<string name="app_language_italian" translatable="false">Italiano</string>
|
||||
<string name="app_language_portuguese" translatable="false">Português</string>
|
||||
<string name="app_language_brazilian_portuguese" translatable="false">Português do Brasil</string>
|
||||
<string name="app_language_russian" translatable="false">Русский</string>
|
||||
<string name="app_language_japanese" translatable="false">日本語</string>
|
||||
<string name="app_language_korean" translatable="false">한국어</string>
|
||||
<string name="app_language_simplified_chinese" translatable="false">简体中文</string>
|
||||
<string name="app_language_traditional_chinese" translatable="false">繁體中文</string>
|
||||
<string name="app_language_polish" translatable="false">Polski</string>
|
||||
<string name="app_language_czech" translatable="false">Čeština</string>
|
||||
<string name="app_language_norwegian" translatable="false">Norsk bokmål</string>
|
||||
<string name="app_language_hungarian" translatable="false">Magyar</string>
|
||||
<string name="app_language_ukrainian" translatable="false">Українська</string>
|
||||
<string name="app_language_vietnamese" translatable="false">Tiếng Việt</string>
|
||||
<string name="app_language_indonesian" translatable="false">Bahasa Indonesia</string>
|
||||
<string name="app_language_arabic" translatable="false">العربية</string>
|
||||
<string name="app_language_central_kurdish" translatable="false">کوردیی ناوەندی</string>
|
||||
<string name="app_language_persian" translatable="false">فارسی</string>
|
||||
<string name="app_language_hebrew" translatable="false">עברית</string>
|
||||
<string name="app_language_serbian" translatable="false">Српски</string>
|
||||
<string name="app_language_thai" translatable="false">แบบไทย</string>
|
||||
|
||||
<!-- Static Themes -->
|
||||
<string name="static_theme_color">Theme Color</string>
|
||||
<string name="eden_theme">Eden</string>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -19,6 +19,11 @@ ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
|
||||
}
|
||||
}
|
||||
|
||||
void ADSP::Shutdown() {
|
||||
audio_renderer->Stop();
|
||||
opus_decoder->Shutdown();
|
||||
}
|
||||
|
||||
AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
|
||||
return *audio_renderer;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -45,6 +45,7 @@ public:
|
||||
explicit ADSP(Core::System& system, Sink::Sink& sink);
|
||||
~ADSP() = default;
|
||||
|
||||
void Shutdown();
|
||||
AudioRenderer::AudioRenderer& AudioRenderer();
|
||||
OpusDecoder::OpusDecoder& OpusDecoder();
|
||||
|
||||
|
||||
@@ -34,26 +34,19 @@ void AudioRenderer::Start() {
|
||||
|
||||
mailbox.Send(Direction::DSP, Message::InitializeOK);
|
||||
if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
|
||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
|
||||
"message response from ADSP!");
|
||||
return;
|
||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
|
||||
}
|
||||
running = true;
|
||||
}
|
||||
|
||||
void AudioRenderer::Stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
if (main_thread.joinable()) {
|
||||
main_thread.request_stop();
|
||||
mailbox.Send(Direction::DSP, Message::Shutdown);
|
||||
if (mailbox.Receive(Direction::Host, main_thread.get_stop_token()) != Message::Shutdown) {
|
||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
|
||||
}
|
||||
main_thread.join();
|
||||
}
|
||||
|
||||
mailbox.Send(Direction::DSP, Message::Shutdown);
|
||||
if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
|
||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
|
||||
"message response from ADSP!");
|
||||
}
|
||||
main_thread.request_stop();
|
||||
main_thread.join();
|
||||
|
||||
for (auto& stream : streams) {
|
||||
if (stream) {
|
||||
stream->Stop();
|
||||
@@ -61,7 +54,6 @@ void AudioRenderer::Stop() {
|
||||
stream = nullptr;
|
||||
}
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
|
||||
void AudioRenderer::Signal() {
|
||||
@@ -69,13 +61,12 @@ void AudioRenderer::Signal() {
|
||||
Send(Direction::DSP, Message::Render);
|
||||
}
|
||||
|
||||
void AudioRenderer::Wait() {
|
||||
auto msg = Receive(Direction::Host);
|
||||
void AudioRenderer::Wait(std::stop_token stop_token) {
|
||||
auto msg = mailbox.Receive(Direction::Host, stop_token);
|
||||
if (msg != Message::RenderResponse) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"Did not receive the expected render response from the AudioRenderer! Expected "
|
||||
"{}, got {}",
|
||||
Message::RenderResponse, msg);
|
||||
"Did not receive the expected render response from the AudioRenderer! Expected "
|
||||
"{}, got {}", Message::RenderResponse, msg);
|
||||
}
|
||||
PostDSPClearCommandBuffer();
|
||||
}
|
||||
@@ -147,7 +138,9 @@ void AudioRenderer::Main(std::stop_token stop_token) {
|
||||
constexpr u64 max_process_time{2'304'000ULL};
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
auto msg{mailbox.Receive(Direction::DSP)};
|
||||
auto msg = mailbox.Receive(Direction::DSP, stop_token);
|
||||
if (stop_token.stop_requested())
|
||||
break;
|
||||
switch (msg) {
|
||||
case Message::Shutdown:
|
||||
mailbox.Send(Direction::Host, Message::Shutdown);
|
||||
|
||||
@@ -63,13 +63,15 @@ public:
|
||||
*/
|
||||
void Start();
|
||||
|
||||
void NotifyShutdown();
|
||||
|
||||
/**
|
||||
* Stop the AudioRenderer.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
void Signal();
|
||||
void Wait();
|
||||
void Wait(std::stop_token stop_token);
|
||||
|
||||
void Send(Direction dir, u32 message);
|
||||
u32 Receive(Direction dir);
|
||||
@@ -102,8 +104,6 @@ private:
|
||||
Mailbox mailbox;
|
||||
/// Main thread
|
||||
std::jthread main_thread{};
|
||||
/// The current state
|
||||
std::atomic<bool> running{};
|
||||
/// Shared memory of input command buffers, set by host, read by DSP
|
||||
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
|
||||
/// The command lists to process
|
||||
|
||||
@@ -41,19 +41,16 @@ OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
|
||||
}
|
||||
|
||||
OpusDecoder::~OpusDecoder() {
|
||||
if (!running) {
|
||||
if (main_thread.joinable()) {
|
||||
// Shutdown the thread
|
||||
Send(Direction::DSP, Message::Shutdown);
|
||||
main_thread.request_stop();
|
||||
auto msg = Receive(Direction::Host, main_thread.get_stop_token());
|
||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
|
||||
main_thread.join();
|
||||
} else {
|
||||
init_thread.request_stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Shutdown the thread
|
||||
Send(Direction::DSP, Message::Shutdown);
|
||||
auto msg = Receive(Direction::Host);
|
||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
|
||||
Message::ShutdownOK, msg);
|
||||
main_thread.request_stop();
|
||||
main_thread.join();
|
||||
running = false;
|
||||
}
|
||||
|
||||
void OpusDecoder::Send(Direction dir, u32 message) {
|
||||
@@ -73,7 +70,6 @@ void OpusDecoder::Init(std::stop_token stop_token) {
|
||||
return;
|
||||
}
|
||||
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
|
||||
running = true;
|
||||
Send(Direction::Host, Message::StartOK);
|
||||
}
|
||||
|
||||
@@ -82,12 +78,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
auto msg = Receive(Direction::DSP, stop_token);
|
||||
if (stop_token.stop_requested())
|
||||
break;
|
||||
|
||||
switch (msg) {
|
||||
case Shutdown:
|
||||
case Message::Shutdown:
|
||||
Send(Direction::Host, Message::ShutdownOK);
|
||||
return;
|
||||
|
||||
case GetWorkBufferSize: {
|
||||
case Message::GetWorkBufferSize: {
|
||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
||||
|
||||
ASSERT(IsValidChannelCount(channel_count));
|
||||
@@ -96,7 +95,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
||||
} break;
|
||||
|
||||
case InitializeDecodeObject: {
|
||||
case Message::InitializeDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto buffer_size = shared_memory->host_send_data[1];
|
||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
||||
@@ -113,7 +112,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case ShutdownDecodeObject: {
|
||||
case Message::ShutdownDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
|
||||
@@ -123,7 +122,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case DecodeInterleaved: {
|
||||
case Message::DecodeInterleaved: {
|
||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
@@ -161,19 +160,19 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Send(Direction::Host, Message::DecodeInterleavedOK);
|
||||
} break;
|
||||
|
||||
case MapMemory: {
|
||||
case Message::MapMemory: {
|
||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
Send(Direction::Host, Message::MapMemoryOK);
|
||||
} break;
|
||||
|
||||
case UnmapMemory: {
|
||||
case Message::UnmapMemory: {
|
||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
Send(Direction::Host, Message::UnmapMemoryOK);
|
||||
} break;
|
||||
|
||||
case GetWorkBufferSizeForMultiStream: {
|
||||
case Message::GetWorkBufferSizeForMultiStream: {
|
||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
|
||||
|
||||
@@ -184,7 +183,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
||||
} break;
|
||||
|
||||
case InitializeMultiStreamDecodeObject: {
|
||||
case Message::InitializeMultiStreamDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto buffer_size = shared_memory->host_send_data[1];
|
||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
||||
@@ -211,7 +210,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case ShutdownMultiStreamDecodeObject: {
|
||||
case Message::ShutdownMultiStreamDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
|
||||
@@ -221,7 +220,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case DecodeInterleavedForMultiStream: {
|
||||
case Message::DecodeInterleavedForMultiStream: {
|
||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
@@ -266,4 +265,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
}
|
||||
}
|
||||
|
||||
void OpusDecoder::Shutdown() {
|
||||
if (init_thread.joinable()) {
|
||||
init_thread.request_stop();
|
||||
init_thread.join(); //must explicitly wait for init to join
|
||||
}
|
||||
if (main_thread.joinable()) {
|
||||
main_thread.request_stop();
|
||||
Send(Direction::DSP, Message::Shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
~OpusDecoder();
|
||||
|
||||
bool IsRunning() const noexcept {
|
||||
return running;
|
||||
return main_thread.joinable();
|
||||
}
|
||||
|
||||
void Send(Direction dir, u32 message);
|
||||
@@ -67,6 +67,8 @@ public:
|
||||
shared_memory = &shared_memory_;
|
||||
}
|
||||
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
||||
@@ -85,8 +87,6 @@ private:
|
||||
std::jthread init_thread{};
|
||||
/// Main thread
|
||||
std::jthread main_thread{};
|
||||
/// The current state
|
||||
bool running{};
|
||||
/// Structure shared with the host, input data set by the host before sending a mailbox message,
|
||||
/// and the responses are written back by the OpusDecoder.
|
||||
SharedMemory* shared_memory{};
|
||||
|
||||
@@ -33,6 +33,7 @@ void AudioCore::CreateSinks() {
|
||||
|
||||
void AudioCore::Shutdown() {
|
||||
audio_manager->Shutdown();
|
||||
adsp->Shutdown();
|
||||
}
|
||||
|
||||
AudioManager& AudioCore::GetAudioManager() {
|
||||
|
||||
@@ -17,7 +17,7 @@ AudioManager::AudioManager() {
|
||||
std::unique_lock l{events.GetAudioEventLock()};
|
||||
events.ClearEvents();
|
||||
while (!stop_token.stop_requested()) {
|
||||
const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
|
||||
const auto timed_out = events.Wait(l, std::chrono::seconds{2});
|
||||
if (events.CheckAudioEventSet(Event::Type::Max)) {
|
||||
break;
|
||||
}
|
||||
@@ -35,9 +35,9 @@ AudioManager::AudioManager() {
|
||||
}
|
||||
|
||||
void AudioManager::Shutdown() {
|
||||
events.SetAudioEvent(Event::Type::Max, true);
|
||||
if (thread.joinable()) {
|
||||
thread.request_stop();
|
||||
events.SetAudioEvent(Event::Type::Max, true);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,7 @@ class AudioManager {
|
||||
public:
|
||||
explicit AudioManager();
|
||||
|
||||
/**
|
||||
* Shutdown the audio manager.
|
||||
*/
|
||||
/// @brief Shutdown the audio manager.
|
||||
void Shutdown();
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -35,7 +38,7 @@ void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStat
|
||||
auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
|
||||
auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
|
||||
|
||||
if (in_use == buffer_params->in_use && !buffer_unmapped) {
|
||||
if (in_use == bool(buffer_params->in_use) && !buffer_unmapped) {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
out_status.writeOffset = current_state->last_pos2;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
@@ -31,10 +31,10 @@ public:
|
||||
};
|
||||
|
||||
struct DeviceInParameter {
|
||||
/* 0x000 */ char name[0x100];
|
||||
/* 0x000 */ u8 name[0x100];
|
||||
/* 0x100 */ u32 input_count;
|
||||
/* 0x104 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x10A */ char unk10A[0x1];
|
||||
/* 0x10A */ u8 unk10A[0x1];
|
||||
/* 0x10B */ bool downmix_enabled;
|
||||
/* 0x10C */ std::array<f32, 4> downmix_coeff;
|
||||
};
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
struct DeviceState {
|
||||
/* 0x00 */ UpsamplerInfo* upsampler_info;
|
||||
/* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
|
||||
/* 0x18 */ char unk18[0x18];
|
||||
/* 0x18 */ u8 unk18[0x18];
|
||||
};
|
||||
static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
|
||||
|
||||
@@ -55,8 +55,8 @@ public:
|
||||
/* 0x14 */ u32 previous_pos;
|
||||
/* 0x18 */ SampleFormat format;
|
||||
/* 0x1C */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x22 */ bool in_use;
|
||||
/* 0x23 */ char unk23[0x5];
|
||||
/* 0x22 */ u8 in_use;
|
||||
/* 0x23 */ u8 unk23[0x5];
|
||||
};
|
||||
static_assert(sizeof(CircularBufferInParameter) == 0x28,
|
||||
"CircularBufferInParameter has the wrong size!");
|
||||
@@ -65,16 +65,16 @@ public:
|
||||
/* 0x00 */ u32 last_pos2;
|
||||
/* 0x04 */ s32 current_pos;
|
||||
/* 0x08 */ u32 last_pos;
|
||||
/* 0x0C */ char unk0C[0x4];
|
||||
/* 0x0C */ u8 unk0C[0x4];
|
||||
/* 0x10 */ AddressInfo address_info;
|
||||
};
|
||||
static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
|
||||
|
||||
struct InParameter {
|
||||
/* 0x000 */ Type type;
|
||||
/* 0x001 */ bool in_use;
|
||||
/* 0x001 */ u8 in_use;
|
||||
/* 0x004 */ u32 node_id;
|
||||
/* 0x008 */ char unk08[0x18];
|
||||
/* 0x008 */ u8 unk08[0x18];
|
||||
union {
|
||||
/* 0x020 */ DeviceInParameter device;
|
||||
/* 0x020 */ CircularBufferInParameter circular_buffer;
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
|
||||
struct OutStatus {
|
||||
/* 0x00 */ u32 writeOffset;
|
||||
/* 0x04 */ char unk04[0x1C];
|
||||
/* 0x04 */ u8 unk04[0x1C];
|
||||
}; // size == 0x20
|
||||
static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
|
||||
|
||||
@@ -162,19 +162,18 @@ public:
|
||||
u8* GetParameter();
|
||||
|
||||
protected:
|
||||
/// Type of this sink
|
||||
Type type{Type::Invalid};
|
||||
/// Is this sink in use?
|
||||
bool in_use{};
|
||||
/// Is this sink's buffer unmapped? Circular only
|
||||
bool buffer_unmapped{};
|
||||
/// Node id for this sink
|
||||
u32 node_id{};
|
||||
/// State buffer for this sink
|
||||
std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
|
||||
/// Parameter buffer for this sink
|
||||
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
|
||||
parameter{};
|
||||
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> parameter{};
|
||||
/// Type of this sink
|
||||
Type type{Type::Invalid};
|
||||
/// Node id for this sink
|
||||
u32 node_id{};
|
||||
/// Is this sink in use?
|
||||
bool in_use : 1 = false;
|
||||
/// Is this sink's buffer unmapped? Circular only
|
||||
bool buffer_unmapped : 1 = false;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
|
||||
@@ -100,8 +100,9 @@ u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
|
||||
}
|
||||
|
||||
System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
|
||||
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()},
|
||||
adsp_rendered_event{adsp_rendered_event_} {}
|
||||
: core{core_}
|
||||
, adsp_rendered_event{adsp_rendered_event_}
|
||||
{}
|
||||
|
||||
Result System::Initialize(const AudioRendererParameterInternal& params,
|
||||
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
|
||||
@@ -406,7 +407,7 @@ void System::Finalize() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
if (IsActive()) {
|
||||
Stop();
|
||||
}
|
||||
|
||||
@@ -433,14 +434,12 @@ void System::Start() {
|
||||
std::scoped_lock l{lock};
|
||||
frames_elapsed = 0;
|
||||
state = State::Started;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void System::Stop() {
|
||||
{
|
||||
std::scoped_lock l{lock};
|
||||
state = State::Stopped;
|
||||
active = false;
|
||||
}
|
||||
|
||||
if (execution_mode == ExecutionMode::Auto) {
|
||||
@@ -480,7 +479,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
|
||||
return result;
|
||||
}
|
||||
|
||||
result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer,
|
||||
result = info_updater.UpdateEffects(effect_context, IsActive(), memory_pool_workbuffer,
|
||||
memory_pool_count);
|
||||
if (result.IsError()) {
|
||||
LOG_ERROR(Service_Audio, "Failed to update Effects!");
|
||||
@@ -582,16 +581,16 @@ u32 System::GetRenderingDevice() const {
|
||||
}
|
||||
|
||||
bool System::IsActive() const {
|
||||
return active;
|
||||
return state == State::Started;
|
||||
}
|
||||
|
||||
void System::SendCommandToDsp() {
|
||||
std::scoped_lock l{lock};
|
||||
|
||||
if (initialized) {
|
||||
if (active) {
|
||||
if (IsActive()) {
|
||||
terminate_event.Reset();
|
||||
const auto remaining_command_count{audio_renderer.GetRemainCommandCount(session_id)};
|
||||
const auto remaining_command_count = core.AudioCore().ADSP().AudioRenderer().GetRemainCommandCount(session_id);
|
||||
u64 command_size{0};
|
||||
|
||||
if (remaining_command_count) {
|
||||
@@ -618,16 +617,17 @@ void System::SendCommandToDsp() {
|
||||
auto time_limit{
|
||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
|
||||
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
|
||||
applet_resource_user_id, process_handle,
|
||||
reset_command_buffers);
|
||||
core.AudioCore().ADSP().AudioRenderer().SetCommandBuffer(session_id,
|
||||
translated_addr, command_size,
|
||||
time_limit, applet_resource_user_id,
|
||||
process_handle, reset_command_buffers);
|
||||
reset_command_buffers = false;
|
||||
command_buffer_size = command_size;
|
||||
if (remaining_command_count == 0) {
|
||||
adsp_rendered_event->Signal(core.Kernel());
|
||||
}
|
||||
} else {
|
||||
audio_renderer.ClearRemainCommandCount(session_id);
|
||||
core.AudioCore().ADSP().AudioRenderer().ClearRemainCommandCount(session_id);
|
||||
terminate_event.Set();
|
||||
}
|
||||
}
|
||||
@@ -738,7 +738,7 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
|
||||
const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
|
||||
total_ticks_elapsed += end_time - start_time;
|
||||
num_command_lists_generated++;
|
||||
render_start_tick = audio_renderer.GetRenderingStartTick(session_id);
|
||||
render_start_tick = core.AudioCore().ADSP().AudioRenderer().GetRenderingStartTick(session_id);
|
||||
frames_elapsed++;
|
||||
|
||||
return command_buffer.size;
|
||||
|
||||
@@ -219,8 +219,6 @@ public:
|
||||
private:
|
||||
/// Core system
|
||||
Core::System& core;
|
||||
/// Reference to the ADSP's AudioRenderer for communication
|
||||
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
|
||||
/// Is this system initialized?
|
||||
bool initialized{};
|
||||
/// Is this system currently active?
|
||||
|
||||
@@ -11,73 +11,70 @@
|
||||
#include "audio_core/renderer/system_manager.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
SystemManager::SystemManager(Core::System& core_)
|
||||
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()} {}
|
||||
: core{core_}
|
||||
{
|
||||
}
|
||||
|
||||
SystemManager::~SystemManager() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void SystemManager::InitializeUnsafe() {
|
||||
if (!active) {
|
||||
active = true;
|
||||
audio_renderer.Start();
|
||||
thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("AudioRenderSystemManager");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
while (active && !stop_token.stop_requested()) {
|
||||
if (!thread.joinable()) {
|
||||
core.AudioCore().ADSP().AudioRenderer().Start();
|
||||
thread = core.Kernel().RunOnHostCoreThread("AudioRenderSystemManager", [this]() {
|
||||
auto& audio_renderer = core.AudioCore().ADSP().AudioRenderer();
|
||||
auto const stop_token = thread.get_stop_token();
|
||||
while (!stop_token.stop_requested()) {
|
||||
{
|
||||
std::scoped_lock l{mutex1};
|
||||
std::scoped_lock lk{mutex};
|
||||
for (auto system : systems)
|
||||
system->SendCommandToDsp();
|
||||
}
|
||||
audio_renderer.Signal();
|
||||
audio_renderer.Wait();
|
||||
audio_renderer.Wait(stop_token);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void SystemManager::Stop() {
|
||||
if (active) {
|
||||
active = false;
|
||||
core.AudioCore().ADSP().AudioRenderer().Stop();
|
||||
if (thread.joinable()) {
|
||||
thread.request_stop();
|
||||
thread.join();
|
||||
audio_renderer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool SystemManager::Add(System& system_) {
|
||||
std::scoped_lock l2{mutex2};
|
||||
if (systems.size() + 1 > MaxRendererSessions) {
|
||||
std::scoped_lock lk{mutex};
|
||||
if (systems.size() >= MaxRendererSessions) {
|
||||
LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
|
||||
return false;
|
||||
}
|
||||
{
|
||||
std::scoped_lock l{mutex1};
|
||||
if (systems.empty())
|
||||
InitializeUnsafe();
|
||||
}
|
||||
systems.push_back(&system_);
|
||||
if (!systems.empty()) {
|
||||
InitializeUnsafe();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemManager::Remove(System& system_) {
|
||||
std::scoped_lock l2{mutex2};
|
||||
{
|
||||
std::scoped_lock l{mutex1};
|
||||
if (systems.remove(&system_) == 0) {
|
||||
LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lk{mutex};
|
||||
if (systems.remove(&system_) == 0) {
|
||||
LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (systems.empty())
|
||||
if (systems.empty()) {
|
||||
Stop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,14 +75,8 @@ private:
|
||||
std::list<System*> systems{};
|
||||
/// Main worker thread for generating command lists
|
||||
std::jthread thread;
|
||||
/// Mutex for the systems
|
||||
std::mutex mutex1{};
|
||||
/// Mutex for adding/removing systems
|
||||
std::mutex mutex2{};
|
||||
/// Is the system manager thread active?
|
||||
std::atomic<bool> active{};
|
||||
/// Reference to the ADSP's AudioRenderer for communication
|
||||
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
|
||||
/// Mutex for the reading and adding/removing systems
|
||||
std::mutex mutex{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
|
||||
@@ -109,12 +109,6 @@ private:
|
||||
if (read_index == producer.index.load(std::memory_order::acquire)) {
|
||||
return false;
|
||||
}
|
||||
} else if constexpr (Mode == PopMode::Wait) {
|
||||
// Wait until the queue is not empty.
|
||||
std::unique_lock lock{consumer.cv_mutex};
|
||||
consumer.cv.wait(lock, [this, read_index] {
|
||||
return read_index != producer.index.load(std::memory_order::acquire);
|
||||
});
|
||||
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
|
||||
// Wait until the queue is not empty.
|
||||
std::unique_lock lock{consumer.cv_mutex};
|
||||
@@ -124,6 +118,12 @@ private:
|
||||
if (stop_token.stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
} else if constexpr (Mode == PopMode::Wait) {
|
||||
// Wait until the queue is not empty.
|
||||
std::unique_lock lock{consumer.cv_mutex};
|
||||
consumer.cv.wait(lock, [this, read_index] {
|
||||
return read_index != producer.index.load(std::memory_order::acquire);
|
||||
});
|
||||
} else {
|
||||
static_assert(Mode < PopMode::Count, "Invalid PopMode.");
|
||||
}
|
||||
|
||||
@@ -96,21 +96,6 @@ bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
|
||||
#ifndef MAP_NOCORE
|
||||
#define MAP_NOCORE 0
|
||||
#endif
|
||||
#ifndef MADV_FREE
|
||||
#define MADV_FREE MADV_DONTNEED
|
||||
#endif
|
||||
|
||||
void DecommitVectorPage(uintptr_t base) noexcept {
|
||||
#if defined(_WIN32)
|
||||
VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT);
|
||||
#elif defined(__linux__)
|
||||
// Linux's MADV_DONTNEED zeros out pages for us
|
||||
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED);
|
||||
#else
|
||||
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE);
|
||||
std::memset(reinterpret_cast<void*>(base), 0, HostPageSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
void* AllocateMemoryPages(std::size_t size) noexcept {
|
||||
if (auto page = HostPageSize; size % page != 0) {
|
||||
|
||||
@@ -35,7 +35,6 @@ const u64 HostPageMask = ~(HostPageSize - 1);
|
||||
|
||||
void* AllocateMemoryPages(std::size_t size) noexcept;
|
||||
void FreeMemoryPages(void* base, std::size_t size) noexcept;
|
||||
void DecommitVectorPage(uintptr_t base) noexcept;
|
||||
|
||||
/// A large page-aligned buffer that has optimized memory usage for zero-writes.
|
||||
template <typename T>
|
||||
@@ -115,7 +114,7 @@ public:
|
||||
const u64 end_page = AlignUp(base, HostPageSize);
|
||||
const u64 first_size = (std::min)(end_page, end) - base;
|
||||
|
||||
if (IsCommittedPage(start)) {
|
||||
if (IsCommittedPage(start / sizeof(T))) {
|
||||
std::memset(reinterpret_cast<void*>(base), 0, first_size);
|
||||
}
|
||||
|
||||
@@ -125,16 +124,11 @@ public:
|
||||
base = end_page;
|
||||
|
||||
for (u64 page = base; page < end; page += HostPageSize) {
|
||||
auto index = (page - reinterpret_cast<u64>(base_ptr)) / sizeof(T);
|
||||
if (!IsCommittedPage(index)) {
|
||||
if (!IsCommittedPage((page - reinterpret_cast<u64>(base_ptr)) / sizeof(T))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (end - page >= HostPageSize) {
|
||||
DecommitPage(index);
|
||||
} else {
|
||||
std::memset(reinterpret_cast<void*>(page), 0, end - page);
|
||||
}
|
||||
std::memset(reinterpret_cast<void*>(page), 0, (std::min)( HostPageSize, end - page));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,14 +183,6 @@ private:
|
||||
committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release);
|
||||
}
|
||||
|
||||
constexpr void DecommitPage(std::size_t index) noexcept {
|
||||
auto page_index = (index * sizeof(T)) >> HostPageBits;
|
||||
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
|
||||
|
||||
committed_pages[page_index >> 6].fetch_and(~(1ULL << (page_index & 63)), std::memory_order_release);
|
||||
DecommitVectorPage(page);
|
||||
}
|
||||
|
||||
std::size_t alloc_size{};
|
||||
T* base_ptr{};
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ add_library(core STATIC
|
||||
core_timing.h
|
||||
cpu_manager.cpp
|
||||
cpu_manager.h
|
||||
launch_params.cpp
|
||||
launch_params.h
|
||||
crypto/aes_util.cpp
|
||||
crypto/aes_util.h
|
||||
crypto/ctr_encryption_layer.cpp
|
||||
@@ -1205,9 +1203,6 @@ endif()
|
||||
|
||||
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||
if (MSVC)
|
||||
target_link_libraries(core PRIVATE getopt)
|
||||
endif()
|
||||
|
||||
if (BOOST_NO_HEADERS)
|
||||
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
|
||||
|
||||
+45
-23
@@ -110,8 +110,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
|
||||
struct System::Impl {
|
||||
explicit Impl(System& system)
|
||||
: kernel{system}, fs_controller{system}, hid_core{kernel}, cpu_manager{system},
|
||||
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
|
||||
: kernel{system}
|
||||
, fs_controller{system}
|
||||
, hid_core{kernel}
|
||||
, cpu_manager{system}
|
||||
, reporter{system}
|
||||
, profile_manager{}
|
||||
{}
|
||||
|
||||
u64 program_id;
|
||||
|
||||
@@ -124,6 +129,12 @@ struct System::Impl {
|
||||
core_timing.SetMulticore(is_multicore);
|
||||
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
||||
|
||||
applet_manager.emplace(system);
|
||||
frontend_applets.emplace(system);
|
||||
apm_controller.emplace(core_timing);
|
||||
arp_manager.emplace();
|
||||
profile_manager.emplace();
|
||||
|
||||
// Create a default fs if one doesn't already exist.
|
||||
if (virtual_filesystem == nullptr) {
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
@@ -133,7 +144,7 @@ struct System::Impl {
|
||||
}
|
||||
|
||||
// Create default implementations of applets if one is not provided.
|
||||
frontend_applets.SetDefaultAppletsIfMissing();
|
||||
frontend_applets->SetDefaultAppletsIfMissing();
|
||||
|
||||
auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
|
||||
|
||||
@@ -376,7 +387,7 @@ struct System::Impl {
|
||||
|
||||
// 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);
|
||||
applet_manager->CreateAndInsertByFrontendAppletParameters(std::move(process), params);
|
||||
|
||||
if (Settings::values.gamecard_inserted) {
|
||||
if (Settings::values.gamecard_current_game) {
|
||||
@@ -428,13 +439,27 @@ struct System::Impl {
|
||||
core_timing.SyncPause(false);
|
||||
Network::CancelPendingSocketOperations();
|
||||
kernel.SuspendEmulation(true);
|
||||
kernel.CloseServices();
|
||||
kernel.ShutdownCores();
|
||||
|
||||
// Notify services helpers of shutdown
|
||||
audio_core->Shutdown();
|
||||
|
||||
// Wait for threads/services to join
|
||||
kernel.CloseServices();
|
||||
|
||||
// service shutdown
|
||||
services.reset();
|
||||
service_manager.reset();
|
||||
frontend_applets.reset();
|
||||
applet_manager.reset();
|
||||
apm_controller.reset();
|
||||
arp_manager.reset();
|
||||
profile_manager.reset();
|
||||
|
||||
fs_controller.Reset();
|
||||
cheat_engine.reset();
|
||||
core_timing.ClearPendingEvents();
|
||||
core_timing.Reset();
|
||||
app_loader.reset();
|
||||
audio_core.reset();
|
||||
gpu_core.reset();
|
||||
@@ -451,9 +476,6 @@ struct System::Impl {
|
||||
room_member->SendGameInfo(game_info);
|
||||
}
|
||||
|
||||
// Reset all glue registrations
|
||||
arp_manager.ResetAll();
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
@@ -482,13 +504,13 @@ struct System::Impl {
|
||||
CpuManager cpu_manager;
|
||||
Reporter reporter;
|
||||
/// Applets
|
||||
Service::AM::AppletManager applet_manager;
|
||||
Service::AM::Frontend::FrontendAppletHolder frontend_applets;
|
||||
std::optional<Service::AM::AppletManager> applet_manager;
|
||||
std::optional<Service::AM::Frontend::FrontendAppletHolder> frontend_applets;
|
||||
/// APM (Performance) services
|
||||
Service::APM::Controller apm_controller{core_timing};
|
||||
std::optional<Service::APM::Controller> apm_controller;
|
||||
/// Service State
|
||||
Service::Glue::ARPManager arp_manager;
|
||||
Service::Account::ProfileManager profile_manager;
|
||||
std::optional<Service::Glue::ARPManager> arp_manager;
|
||||
std::optional<Service::Account::ProfileManager> profile_manager;
|
||||
/// Network instance
|
||||
Network::NetworkInstance network_instance;
|
||||
Core::SpeedLimiter speed_limiter;
|
||||
@@ -815,19 +837,19 @@ void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
}
|
||||
|
||||
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
|
||||
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
|
||||
impl->frontend_applets->SetFrontendAppletSet(std::move(set));
|
||||
}
|
||||
|
||||
Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() {
|
||||
return impl->frontend_applets;
|
||||
return *impl->frontend_applets;
|
||||
}
|
||||
|
||||
const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const {
|
||||
return impl->frontend_applets;
|
||||
return *impl->frontend_applets;
|
||||
}
|
||||
|
||||
Service::AM::AppletManager& System::GetAppletManager() {
|
||||
return impl->applet_manager;
|
||||
return *impl->applet_manager;
|
||||
}
|
||||
|
||||
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
|
||||
@@ -868,27 +890,27 @@ const Reporter& System::GetReporter() const {
|
||||
}
|
||||
|
||||
Service::Glue::ARPManager& System::GetARPManager() {
|
||||
return impl->arp_manager;
|
||||
return *impl->arp_manager;
|
||||
}
|
||||
|
||||
const Service::Glue::ARPManager& System::GetARPManager() const {
|
||||
return impl->arp_manager;
|
||||
return *impl->arp_manager;
|
||||
}
|
||||
|
||||
Service::APM::Controller& System::GetAPMController() {
|
||||
return impl->apm_controller;
|
||||
return *impl->apm_controller;
|
||||
}
|
||||
|
||||
const Service::APM::Controller& System::GetAPMController() const {
|
||||
return impl->apm_controller;
|
||||
return *impl->apm_controller;
|
||||
}
|
||||
|
||||
Service::Account::ProfileManager& System::GetProfileManager() {
|
||||
return impl->profile_manager;
|
||||
return *impl->profile_manager;
|
||||
}
|
||||
|
||||
const Service::Account::ProfileManager& System::GetProfileManager() const {
|
||||
return impl->profile_manager;
|
||||
return *impl->profile_manager;
|
||||
}
|
||||
|
||||
void System::SetExitLocked(bool locked) {
|
||||
|
||||
@@ -304,10 +304,10 @@ std::optional<s64> CoreTiming::Advance() {
|
||||
|
||||
void CoreTiming::Reset() {
|
||||
paused = true;
|
||||
pause_event.Set();
|
||||
event.Set();
|
||||
if (timer_thread.joinable()) {
|
||||
timer_thread.request_stop();
|
||||
pause_event.Set();
|
||||
event.Set();
|
||||
timer_thread.join();
|
||||
}
|
||||
has_started = false;
|
||||
|
||||
@@ -72,11 +72,9 @@ void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noe
|
||||
|
||||
void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept {
|
||||
ASSERT(this->IsLocked());
|
||||
if (m_woken_dummy_threads.size() > 0) {
|
||||
for (auto* thread : m_woken_dummy_threads)
|
||||
thread->DummyThreadEndWait(kernel);
|
||||
m_woken_dummy_threads.clear();
|
||||
}
|
||||
for (auto* thread : m_woken_dummy_threads)
|
||||
thread->DummyThreadEndWait(kernel);
|
||||
m_woken_dummy_threads.clear();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -59,7 +59,6 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, const KThread* t
|
||||
KThread* cur_child = nullptr;
|
||||
{
|
||||
KScopedLightLock proc_lk(process->GetListLock());
|
||||
|
||||
auto& thread_list = process->GetThreadList();
|
||||
for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
|
||||
if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
|
||||
|
||||
@@ -1236,18 +1236,13 @@ namespace Kernel {
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
// Determine if this is the first termination request.
|
||||
const bool first_request = [&]() -> bool {
|
||||
// Perform an atomic compare-and-swap from false to true.
|
||||
bool expected = false;
|
||||
return m_termination_requested.compare_exchange_strong(expected, true);
|
||||
}();
|
||||
|
||||
// Perform an atomic compare-and-swap from false to true.
|
||||
bool expected = false;
|
||||
// If this is the first request, start termination procedure.
|
||||
if (first_request) {
|
||||
if (m_termination_requested.compare_exchange_strong(expected, true)) {
|
||||
// If the thread is in initialized state, just change state to terminated.
|
||||
if (this->GetState() == ThreadState::Initialized) {
|
||||
m_thread_state = ThreadState::Terminated;
|
||||
return ThreadState::Terminated;
|
||||
return m_thread_state = ThreadState::Terminated;
|
||||
}
|
||||
|
||||
// Register the terminating dpc.
|
||||
@@ -1281,7 +1276,6 @@ namespace Kernel {
|
||||
m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true);
|
||||
}
|
||||
}
|
||||
|
||||
return this->GetState();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
#include "common/assert.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/k_worker_task.h"
|
||||
@@ -26,7 +28,33 @@ void KWorkerTask::DoWorkerTask(KernelCore& kernel) {
|
||||
}
|
||||
}
|
||||
|
||||
KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "KWorkerTaskManager") {}
|
||||
KWorkerTaskManager::KWorkerTaskManager(KernelCore& kernel) {
|
||||
m_waiting_thread = std::jthread([&kernel, this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("KWorkerManager");
|
||||
while (!stop_token.stop_requested()) {
|
||||
KWorkerTask* t;
|
||||
{
|
||||
std::unique_lock lk{m_task_mutex};
|
||||
m_task_cv.wait(lk, m_waiting_thread.get_stop_token(), [&]() {
|
||||
return !m_task_queue.empty();
|
||||
});
|
||||
if (stop_token.stop_requested())
|
||||
break;
|
||||
t = m_task_queue.back();
|
||||
m_task_queue.pop_back();
|
||||
}
|
||||
t->DoWorkerTask(kernel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
KWorkerTaskManager::~KWorkerTaskManager() {
|
||||
if (m_waiting_thread.joinable()) {
|
||||
m_waiting_thread.request_stop();
|
||||
m_task_cv.notify_one();
|
||||
m_waiting_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) {
|
||||
ASSERT(type <= WorkerType::Count);
|
||||
@@ -35,10 +63,11 @@ void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTas
|
||||
|
||||
void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) {
|
||||
KScopedSchedulerLock sl(kernel);
|
||||
m_waiting_thread.QueueWork([&kernel, task]() {
|
||||
// Do the task.
|
||||
task->DoWorkerTask(kernel);
|
||||
});
|
||||
{
|
||||
std::scoped_lock lk{m_task_mutex};
|
||||
m_task_queue.emplace_back(task);
|
||||
}
|
||||
m_task_cv.notify_one();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread_worker.h"
|
||||
|
||||
@@ -18,15 +23,17 @@ public:
|
||||
Count,
|
||||
};
|
||||
|
||||
KWorkerTaskManager();
|
||||
KWorkerTaskManager(KernelCore& kernel);
|
||||
~KWorkerTaskManager();
|
||||
|
||||
static void AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task);
|
||||
|
||||
private:
|
||||
void AddTask(KernelCore& kernel, KWorkerTask* task);
|
||||
std::jthread m_waiting_thread;
|
||||
|
||||
private:
|
||||
Common::ThreadWorker m_waiting_thread;
|
||||
std::mutex m_task_mutex;
|
||||
std::condition_variable_any m_task_cv;
|
||||
std::vector<KWorkerTask*> m_task_queue;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -80,7 +80,10 @@ struct KernelCore::Impl {
|
||||
// so it will be statically given a TLS slot anyways.
|
||||
static inline thread_local ThreadLocalData tls_data = {};
|
||||
|
||||
explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} {
|
||||
explicit Impl(Core::System& system_, KernelCore& kernel_)
|
||||
: worker_task_manager{kernel_}
|
||||
, system{system_}
|
||||
{
|
||||
tls_data.lock = true;
|
||||
}
|
||||
|
||||
@@ -967,12 +970,16 @@ Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
|
||||
return *impl->hardware_timer;
|
||||
}
|
||||
|
||||
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() {
|
||||
return *impl->global_object_list_container;
|
||||
KAutoObjectWithListContainer* KernelCore::ObjectListContainer() {
|
||||
if (!impl->global_object_list_container)
|
||||
return nullptr;
|
||||
return std::addressof(*impl->global_object_list_container);
|
||||
}
|
||||
|
||||
const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const {
|
||||
return *impl->global_object_list_container;
|
||||
const KAutoObjectWithListContainer* KernelCore::ObjectListContainer() const {
|
||||
if (!impl->global_object_list_container)
|
||||
return nullptr;
|
||||
return std::addressof(*impl->global_object_list_container);
|
||||
}
|
||||
|
||||
void KernelCore::PrepareReschedule(std::size_t id) {
|
||||
@@ -1001,16 +1008,10 @@ void KernelCore::UnregisterInUseObject(KAutoObject* object) {
|
||||
|
||||
void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
||||
auto* manager = server_manager.get();
|
||||
|
||||
{
|
||||
if (!impl->is_shutting_down) {
|
||||
std::scoped_lock lk{impl->server_lock};
|
||||
if (impl->is_shutting_down) {
|
||||
return;
|
||||
}
|
||||
|
||||
impl->server_managers.emplace_back(std::move(server_manager));
|
||||
}
|
||||
|
||||
manager->LoopProcess();
|
||||
}
|
||||
|
||||
@@ -1255,6 +1256,10 @@ void KernelCore::SuspendEmulation(bool suspended) {
|
||||
}
|
||||
|
||||
void KernelCore::ShutdownCores() {
|
||||
// Notify shutdown (pre-emptively)
|
||||
for (auto& sm : impl->server_managers) {
|
||||
sm->NotifyShutdown();
|
||||
}
|
||||
impl->TerminateAllProcesses();
|
||||
KScopedSchedulerLock lk{*this};
|
||||
for (auto* thread : impl->shutdown_threads)
|
||||
|
||||
@@ -175,9 +175,8 @@ public:
|
||||
/// Stops execution of 'id' core, in order to reschedule a new thread.
|
||||
void PrepareReschedule(std::size_t id);
|
||||
|
||||
KAutoObjectWithListContainer& ObjectListContainer();
|
||||
|
||||
const KAutoObjectWithListContainer& ObjectListContainer() const;
|
||||
KAutoObjectWithListContainer* ObjectListContainer();
|
||||
const KAutoObjectWithListContainer* ObjectListContainer() const;
|
||||
|
||||
/// Registers all kernel objects with the global emulation state, this is purely for tracking
|
||||
/// leaks after emulation has been shutdown.
|
||||
|
||||
@@ -151,7 +151,8 @@ public:
|
||||
const bool is_initialized = this->IsInitialized();
|
||||
uintptr_t arg = 0;
|
||||
if (is_initialized) {
|
||||
kernel.ObjectListContainer().Unregister(this);
|
||||
if (auto const olc = kernel.ObjectListContainer(); olc)
|
||||
olc->Unregister(this);
|
||||
arg = this->GetPostDestroyArgument();
|
||||
this->Finalize(kernel);
|
||||
}
|
||||
@@ -175,7 +176,7 @@ public:
|
||||
public:
|
||||
static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
|
||||
kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
|
||||
kernel.ObjectListContainer().Initialize();
|
||||
kernel.ObjectListContainer()->Initialize();
|
||||
}
|
||||
|
||||
static Derived* Create(KernelCore& kernel) {
|
||||
@@ -186,7 +187,7 @@ public:
|
||||
}
|
||||
|
||||
static void Register(KernelCore& kernel, Derived* obj) {
|
||||
return kernel.ObjectListContainer().Register(obj);
|
||||
return kernel.ObjectListContainer()->Register(obj);
|
||||
}
|
||||
|
||||
static size_t GetObjectSize(KernelCore& kernel) {
|
||||
|
||||
@@ -15,13 +15,9 @@ void LoopProcess(Core::System& system) {
|
||||
WindowSystem window_system(system);
|
||||
ButtonPoller button_poller(system, window_system);
|
||||
EventObserver event_observer(system, window_system);
|
||||
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService(
|
||||
"appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
|
||||
server_manager->RegisterNamedService(
|
||||
"appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
|
||||
server_manager->RegisterNamedService("appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
|
||||
server_manager->RegisterNamedService("appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,24 +26,25 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
|
||||
m_window_system.SetEventObserver(this);
|
||||
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
|
||||
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
|
||||
m_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("am:EventObserver");
|
||||
system.Kernel().RunOnGuestCoreProcess("am:EventObserver", [this]() {
|
||||
auto const stop_token = m_stop_source.get_token();
|
||||
while (!stop_token.stop_requested()) {
|
||||
auto* signaled_holder = this->WaitSignaled(stop_token);
|
||||
if (!signaled_holder)
|
||||
if (stop_token.stop_requested() || !signaled_holder)
|
||||
break;
|
||||
this->Process(signaled_holder);
|
||||
}
|
||||
m_process_cv.notify_one();
|
||||
});
|
||||
}
|
||||
|
||||
EventObserver::~EventObserver() {
|
||||
// Signal thread and wait for processing to finish.
|
||||
if (m_thread.joinable()) {
|
||||
// Signal thread and wait for processing to finish.
|
||||
m_thread.request_stop();
|
||||
m_wakeup_event.Signal(m_system.Kernel());
|
||||
m_thread.join();
|
||||
m_stop_source.request_stop();
|
||||
m_wakeup_event.Signal(m_system.Kernel());
|
||||
{
|
||||
std::unique_lock lk{m_process_mutex};
|
||||
m_process_cv.wait(lk);
|
||||
}
|
||||
|
||||
// Free remaining owned sessions.
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <stop_token>
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
@@ -62,7 +64,9 @@ private:
|
||||
MultiWait m_deferred_wait_list;
|
||||
|
||||
// Processing thread.
|
||||
std::jthread m_thread{};
|
||||
std::stop_source m_stop_source{};
|
||||
std::mutex m_process_mutex;
|
||||
std::condition_variable m_process_cv{};
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -90,10 +90,9 @@ static Service::PSC::Time::LocationName GetTimeZoneString(
|
||||
}
|
||||
|
||||
TimeManager::TimeManager(Core::System& system)
|
||||
: m_steady_clock_resource{system}, m_time_zone_binary{system}, m_worker{
|
||||
system,
|
||||
m_steady_clock_resource,
|
||||
m_file_timestamp_worker} {
|
||||
: m_steady_clock_resource{system}, m_time_zone_binary{system}
|
||||
, m_worker{system, m_steady_clock_resource, m_file_timestamp_worker}
|
||||
{
|
||||
m_time_m =
|
||||
system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
|
||||
|
||||
|
||||
@@ -20,15 +20,17 @@
|
||||
|
||||
namespace Service::Glue::Time {
|
||||
|
||||
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource,
|
||||
FileTimestampWorker& file_timestamp_worker)
|
||||
: m_system{system}, m_ctx{m_system, "Glue:TimeWorker"}, m_event{m_ctx.CreateEvent(
|
||||
"Glue:TimeWorker:Event")},
|
||||
m_steady_clock_resource{steady_clock_resource},
|
||||
m_file_timestamp_worker{file_timestamp_worker}, m_timer_steady_clock{m_ctx.CreateEvent(
|
||||
"Glue:TimeWorker:SteadyClockTimerEvent")},
|
||||
m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")},
|
||||
m_alarm_worker{m_system, m_steady_clock_resource}, m_pm_state_change_handler{m_alarm_worker} {
|
||||
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource, FileTimestampWorker& file_timestamp_worker)
|
||||
: m_system{system}
|
||||
, m_ctx{m_system, "Glue:TimeWorker"}
|
||||
, m_event{m_ctx.CreateEvent("Glue:TimeWorker:Event")}
|
||||
, m_steady_clock_resource{steady_clock_resource}
|
||||
, m_file_timestamp_worker{file_timestamp_worker}
|
||||
, m_timer_steady_clock{m_ctx.CreateEvent("Glue:TimeWorker:SteadyClockTimerEvent")}
|
||||
, m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")}
|
||||
, m_alarm_worker{m_system, m_steady_clock_resource}
|
||||
, m_pm_state_change_handler{m_alarm_worker}
|
||||
{
|
||||
m_timer_steady_clock_timing_event = Core::Timing::CreateEvent(
|
||||
"Time::SteadyClockEvent",
|
||||
[this](s64 time,
|
||||
@@ -47,14 +49,16 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady
|
||||
}
|
||||
|
||||
TimeWorker::~TimeWorker() {
|
||||
// Wait for processing to stop
|
||||
m_stop_source.request_stop();
|
||||
m_local_clock_event->Signal(m_system.Kernel());
|
||||
m_network_clock_event->Signal(m_system.Kernel());
|
||||
m_ephemeral_clock_event->Signal(m_system.Kernel());
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
|
||||
m_thread.request_stop();
|
||||
m_event->Signal(m_system.Kernel());
|
||||
m_thread.join();
|
||||
{
|
||||
std::unique_lock lk{m_process_mutex};
|
||||
m_process_cv.wait(lk);
|
||||
}
|
||||
|
||||
m_ctx.CloseEvent(m_event);
|
||||
m_system.CoreTiming().UnscheduleEvent(m_timer_steady_clock_timing_event);
|
||||
@@ -122,166 +126,162 @@ void TimeWorker::Initialize(std::shared_ptr<Service::PSC::Time::StaticService> t
|
||||
}
|
||||
|
||||
void TimeWorker::StartThread() {
|
||||
m_thread = std::jthread(std::bind_front(&TimeWorker::ThreadFunc, this));
|
||||
}
|
||||
|
||||
void TimeWorker::ThreadFunc(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("TimeWorker");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Low);
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
enum class EventType : s32 {
|
||||
Exit = 0,
|
||||
PowerStateChange = 1,
|
||||
SignalAlarms = 2,
|
||||
UpdateLocalSystemClock = 3,
|
||||
UpdateNetworkSystemClock = 4,
|
||||
UpdateEphemeralSystemClock = 5,
|
||||
UpdateSteadyClock = 6,
|
||||
UpdateFileTimestamp = 7,
|
||||
AutoCorrect = 8,
|
||||
};
|
||||
|
||||
s32 index{};
|
||||
|
||||
if (m_pm_state_change_handler.m_priority != 0) {
|
||||
// TODO: gIPmModuleService::GetEvent() 1
|
||||
index = WaitAny(m_system.Kernel(),
|
||||
&m_event->GetReadableEvent(), // 0
|
||||
&m_alarm_worker.GetEvent() // 1
|
||||
);
|
||||
} else {
|
||||
// TODO: gIPmModuleService::GetEvent() 1
|
||||
index = WaitAny(m_system.Kernel(),
|
||||
&m_event->GetReadableEvent(), // 0
|
||||
&m_alarm_worker.GetEvent(), // 1
|
||||
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
|
||||
m_local_clock_event, // 3
|
||||
m_network_clock_event, // 4
|
||||
m_ephemeral_clock_event, // 5
|
||||
&m_timer_steady_clock->GetReadableEvent(), // 6
|
||||
&m_timer_file_system->GetReadableEvent(), // 7
|
||||
m_standard_user_auto_correct_clock_event // 8
|
||||
);
|
||||
}
|
||||
|
||||
switch (static_cast<EventType>(index)) {
|
||||
case EventType::Exit:
|
||||
return;
|
||||
|
||||
case EventType::PowerStateChange:
|
||||
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
|
||||
if (m_pm_state_change_handler.m_priority <= 1) {
|
||||
m_alarm_worker.OnPowerStateChanged();
|
||||
m_system.Kernel().RunOnGuestCoreProcess("TimeWorker", [this]() {
|
||||
auto const stop_token = m_stop_source.get_token();
|
||||
while (!stop_token.stop_requested()) {
|
||||
enum class EventType : s32 {
|
||||
Exit = 0,
|
||||
PowerStateChange = 1,
|
||||
SignalAlarms = 2,
|
||||
UpdateLocalSystemClock = 3,
|
||||
UpdateNetworkSystemClock = 4,
|
||||
UpdateEphemeralSystemClock = 5,
|
||||
UpdateSteadyClock = 6,
|
||||
UpdateFileTimestamp = 7,
|
||||
AutoCorrect = 8,
|
||||
};
|
||||
s32 index{};
|
||||
if (m_pm_state_change_handler.m_priority != 0) {
|
||||
// TODO: gIPmModuleService::GetEvent() 1
|
||||
index = WaitAny(
|
||||
m_system.Kernel(),
|
||||
&m_event->GetReadableEvent(), // 0
|
||||
&m_alarm_worker.GetEvent() // 1
|
||||
);
|
||||
} else {
|
||||
// TODO: gIPmModuleService::GetEvent() 1
|
||||
index = WaitAny(
|
||||
m_system.Kernel(),
|
||||
&m_event->GetReadableEvent(), // 0
|
||||
&m_alarm_worker.GetEvent(), // 1
|
||||
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
|
||||
m_local_clock_event, // 3
|
||||
m_network_clock_event, // 4
|
||||
m_ephemeral_clock_event, // 5
|
||||
&m_timer_steady_clock->GetReadableEvent(), // 6
|
||||
&m_timer_file_system->GetReadableEvent(), // 7
|
||||
m_standard_user_auto_correct_clock_event // 8
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType::SignalAlarms:
|
||||
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
|
||||
m_time_m->CheckAndSignalAlarms();
|
||||
break;
|
||||
if (stop_token.stop_requested())
|
||||
break;
|
||||
|
||||
case EventType::UpdateLocalSystemClock: {
|
||||
m_local_clock_event->Clear(m_system.Kernel());
|
||||
switch (EventType(index)) {
|
||||
case EventType::Exit:
|
||||
return;
|
||||
|
||||
Service::PSC::Time::SystemClockContext context{};
|
||||
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
|
||||
case EventType::PowerStateChange:
|
||||
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
|
||||
if (m_pm_state_change_handler.m_priority <= 1) {
|
||||
m_alarm_worker.OnPowerStateChanged();
|
||||
}
|
||||
break;
|
||||
|
||||
m_set_sys->SetUserSystemClockContext(context);
|
||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||
break;
|
||||
}
|
||||
case EventType::SignalAlarms:
|
||||
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
|
||||
m_time_m->CheckAndSignalAlarms();
|
||||
break;
|
||||
|
||||
case EventType::UpdateNetworkSystemClock: {
|
||||
m_network_clock_event->Clear(m_system.Kernel());
|
||||
case EventType::UpdateLocalSystemClock: {
|
||||
m_local_clock_event->Clear(m_system.Kernel());
|
||||
|
||||
Service::PSC::Time::SystemClockContext context{};
|
||||
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
|
||||
Service::PSC::Time::SystemClockContext context{};
|
||||
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
|
||||
|
||||
m_set_sys->SetNetworkSystemClockContext(context);
|
||||
|
||||
s64 time{};
|
||||
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
|
||||
m_set_sys->SetUserSystemClockContext(context);
|
||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||
break;
|
||||
}
|
||||
|
||||
[[maybe_unused]] auto offset_before{
|
||||
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
|
||||
// TODO system report "standard_netclock_operation"
|
||||
// "clock_time" = time
|
||||
// "context_offset_before" = offset_before
|
||||
// "context_offset_after" = context.offset
|
||||
m_report_network_clock_context = context;
|
||||
if (!m_ig_report_network_clock_context_set) {
|
||||
m_ig_report_network_clock_context_set = true;
|
||||
}
|
||||
case EventType::UpdateNetworkSystemClock: {
|
||||
m_network_clock_event->Clear(m_system.Kernel());
|
||||
|
||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||
break;
|
||||
}
|
||||
Service::PSC::Time::SystemClockContext context{};
|
||||
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
|
||||
|
||||
case EventType::UpdateEphemeralSystemClock: {
|
||||
m_ephemeral_clock_event->Clear(m_system.Kernel());
|
||||
m_set_sys->SetNetworkSystemClockContext(context);
|
||||
|
||||
Service::PSC::Time::SystemClockContext context{};
|
||||
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
|
||||
if (res != ResultSuccess) {
|
||||
s64 time{};
|
||||
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
|
||||
break;
|
||||
}
|
||||
|
||||
[[maybe_unused]] auto offset_before{
|
||||
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
|
||||
// TODO system report "standard_netclock_operation"
|
||||
// "clock_time" = time
|
||||
// "context_offset_before" = offset_before
|
||||
// "context_offset_after" = context.offset
|
||||
m_report_network_clock_context = context;
|
||||
if (!m_ig_report_network_clock_context_set) {
|
||||
m_ig_report_network_clock_context_set = true;
|
||||
}
|
||||
|
||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||
break;
|
||||
}
|
||||
|
||||
s64 time{};
|
||||
res = m_ephemeral_clock->GetCurrentTime(&time);
|
||||
if (res != ResultSuccess) {
|
||||
case EventType::UpdateEphemeralSystemClock: {
|
||||
m_ephemeral_clock_event->Clear(m_system.Kernel());
|
||||
|
||||
Service::PSC::Time::SystemClockContext context{};
|
||||
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
|
||||
if (res != ResultSuccess) {
|
||||
break;
|
||||
}
|
||||
|
||||
s64 time{};
|
||||
res = m_ephemeral_clock->GetCurrentTime(&time);
|
||||
if (res != ResultSuccess) {
|
||||
break;
|
||||
}
|
||||
|
||||
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
|
||||
? m_report_ephemeral_clock_context.offset
|
||||
: 0};
|
||||
// TODO system report "ephemeral_netclock_operation"
|
||||
// "clock_time" = time
|
||||
// "context_offset_before" = offset_before
|
||||
// "context_offset_after" = context.offset
|
||||
m_report_ephemeral_clock_context = context;
|
||||
if (!m_ig_report_ephemeral_clock_context_set) {
|
||||
m_ig_report_ephemeral_clock_context_set = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
|
||||
? m_report_ephemeral_clock_context.offset
|
||||
: 0};
|
||||
// TODO system report "ephemeral_netclock_operation"
|
||||
// "clock_time" = time
|
||||
// "context_offset_before" = offset_before
|
||||
// "context_offset_after" = context.offset
|
||||
m_report_ephemeral_clock_context = context;
|
||||
if (!m_ig_report_ephemeral_clock_context_set) {
|
||||
m_ig_report_ephemeral_clock_context_set = true;
|
||||
case EventType::UpdateSteadyClock:
|
||||
m_timer_steady_clock->Clear(m_system.Kernel());
|
||||
|
||||
m_steady_clock_resource.UpdateTime();
|
||||
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
|
||||
break;
|
||||
|
||||
case EventType::UpdateFileTimestamp:
|
||||
m_timer_file_system->Clear(m_system.Kernel());
|
||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||
break;
|
||||
|
||||
case EventType::AutoCorrect: {
|
||||
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
|
||||
|
||||
bool automatic_correction{};
|
||||
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(&automatic_correction));
|
||||
|
||||
Service::PSC::Time::SteadyClockTimePoint time_point{};
|
||||
R_ASSERT(m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
|
||||
|
||||
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
|
||||
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EventType::UpdateSteadyClock:
|
||||
m_timer_steady_clock->Clear(m_system.Kernel());
|
||||
|
||||
m_steady_clock_resource.UpdateTime();
|
||||
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
|
||||
break;
|
||||
|
||||
case EventType::UpdateFileTimestamp:
|
||||
m_timer_file_system->Clear(m_system.Kernel());
|
||||
|
||||
m_file_timestamp_worker.SetFilesystemPosixTime();
|
||||
break;
|
||||
|
||||
case EventType::AutoCorrect: {
|
||||
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
|
||||
|
||||
bool automatic_correction{};
|
||||
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
|
||||
&automatic_correction));
|
||||
|
||||
Service::PSC::Time::SteadyClockTimePoint time_point{};
|
||||
R_ASSERT(
|
||||
m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
|
||||
|
||||
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
|
||||
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
m_process_cv.notify_one();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Service::Glue::Time
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/glue/time/alarm_worker.h"
|
||||
@@ -33,17 +37,17 @@ public:
|
||||
|
||||
void StartThread();
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
T GetSettingsItemValue(const std::string& category, const std::string& name);
|
||||
|
||||
void ThreadFunc(std::stop_token stop_token);
|
||||
|
||||
Core::System& m_system;
|
||||
KernelHelpers::ServiceContext m_ctx;
|
||||
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
|
||||
|
||||
std::jthread m_thread;
|
||||
std::mutex m_process_mutex;
|
||||
std::condition_variable m_process_cv;
|
||||
std::stop_source m_stop_source;
|
||||
|
||||
Kernel::KEvent* m_event{};
|
||||
std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
|
||||
std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
|
||||
|
||||
@@ -98,8 +98,10 @@ ServerManager::~ServerManager() {
|
||||
// Signal stop.
|
||||
m_stop_source.request_stop();
|
||||
m_wakeup_event->Signal(m_system.Kernel());
|
||||
if (m_deferral_event) m_deferral_event->Signal(m_system.Kernel()); //for sm: and ports
|
||||
|
||||
// Wait for processing to stop.
|
||||
|
||||
// Wait for processing to stop. Schedule
|
||||
m_stopped.Wait();
|
||||
m_threads.clear();
|
||||
|
||||
@@ -253,6 +255,14 @@ void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_thre
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Notifies that the system is shutting down (pre-emptively terminate threads)
|
||||
void ServerManager::NotifyShutdown() {
|
||||
m_stop_source.request_stop();
|
||||
// Wake them up regardless
|
||||
m_wakeup_event->Signal(m_system.Kernel());
|
||||
if (m_deferral_event) m_deferral_event->Signal(m_system.Kernel()); //for sm: and ports
|
||||
}
|
||||
|
||||
Result ServerManager::LoopProcess() {
|
||||
SCOPE_EXIT {
|
||||
m_stopped.Set();
|
||||
@@ -285,9 +295,8 @@ MultiWaitHolder* ServerManager::WaitSignaled() {
|
||||
this->LinkDeferred();
|
||||
|
||||
// If we're done, return before we start waiting.
|
||||
if (m_stop_source.stop_requested()) {
|
||||
if (m_stop_source.stop_requested())
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
|
||||
if (selected == std::addressof(*m_wakeup_holder)) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 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
|
||||
|
||||
@@ -49,6 +52,7 @@ public:
|
||||
|
||||
Result LoopProcess();
|
||||
void StartAdditionalHostThreads(const char* name, size_t num_threads);
|
||||
void NotifyShutdown();
|
||||
|
||||
static void RunServer(std::unique_ptr<ServerManager>&& server);
|
||||
|
||||
|
||||
@@ -117,7 +117,6 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
{"friends", &Friend::LoopProcess},
|
||||
{"settings", &Set::LoopProcess},
|
||||
{"psc", &PSC::LoopProcess},
|
||||
{"glue", &Glue::LoopProcess},
|
||||
{"grc", &GRC::LoopProcess},
|
||||
{"hid", &HID::LoopProcess},
|
||||
{"jit", &JIT::LoopProcess},
|
||||
@@ -153,6 +152,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
{"usb", &USB::LoopProcess},
|
||||
{"i2c", &I2C::LoopProcess},
|
||||
{"gpio", &GPIO::LoopProcess},
|
||||
{"glue", &Glue::LoopProcess},
|
||||
})
|
||||
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
|
||||
}
|
||||
|
||||
@@ -305,16 +305,14 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_)
|
||||
SM::~SM() = default;
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto& service_manager = system.ServiceManager();
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
Kernel::KEvent* deferral_event{};
|
||||
server_manager->ManageDeferral(&deferral_event);
|
||||
service_manager.SetDeferralEvent(deferral_event);
|
||||
|
||||
system.ServiceManager().SetDeferralEvent(deferral_event);
|
||||
auto sm_service = std::make_shared<SM>(system.ServiceManager(), system);
|
||||
server_manager->ManageNamedPort("sm:", [sm_service] { return sm_service; });
|
||||
|
||||
server_manager->ManageNamedPort("sm:", [sm_service] {
|
||||
return sm_service;
|
||||
});
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/service/vi/conductor.h"
|
||||
#include "core/hle/service/vi/container.h"
|
||||
#include "core/hle/service/vi/display_list.h"
|
||||
@@ -29,24 +30,29 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
|
||||
});
|
||||
|
||||
if (system.IsMulticore()) {
|
||||
m_event = Core::Timing::CreateEvent(
|
||||
"ScreenComposition",
|
||||
[this](s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
m_signal.Set();
|
||||
return std::chrono::nanoseconds(this->GetNextTicks());
|
||||
});
|
||||
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
m_signal.Set();
|
||||
return std::chrono::nanoseconds(this->GetNextTicks());
|
||||
});
|
||||
|
||||
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
|
||||
m_thread = std::jthread([this](std::stop_token token) { this->VsyncThread(token); });
|
||||
} else {
|
||||
m_event = Core::Timing::CreateEvent(
|
||||
"ScreenComposition",
|
||||
[this](s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
m_thread = system.Kernel().RunOnHostCoreThread("VSyncThread", [this]() {
|
||||
auto const stop_token = m_thread.get_stop_token();
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
while (!stop_token.stop_requested()) {
|
||||
m_signal.Wait();
|
||||
if (stop_token.stop_requested() || m_system.IsShuttingDown()) {
|
||||
break;
|
||||
}
|
||||
this->ProcessVsync();
|
||||
return std::chrono::nanoseconds(this->GetNextTicks());
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
this->ProcessVsync();
|
||||
return std::chrono::nanoseconds(this->GetNextTicks());
|
||||
});
|
||||
|
||||
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
|
||||
}
|
||||
@@ -54,10 +60,10 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
|
||||
|
||||
Conductor::~Conductor() {
|
||||
m_system.CoreTiming().UnscheduleEvent(m_event);
|
||||
|
||||
if (m_system.IsMulticore()) {
|
||||
if (m_thread.joinable()) {
|
||||
m_thread.request_stop();
|
||||
m_signal.Set();
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,22 +89,6 @@ void Conductor::ProcessVsync() {
|
||||
}
|
||||
}
|
||||
|
||||
void Conductor::VsyncThread(std::stop_token token) {
|
||||
Common::SetCurrentThreadName("VSyncThread");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
|
||||
while (!token.stop_requested()) {
|
||||
m_signal.Wait();
|
||||
|
||||
if (m_system.IsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->ProcessVsync();
|
||||
}
|
||||
}
|
||||
|
||||
s64 Conductor::GetNextTicks() const {
|
||||
const auto& settings = Settings::values;
|
||||
auto speed_scale = 1.f;
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <regex>
|
||||
#include <cctype>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/launch_params.h"
|
||||
|
||||
#undef _UNICODE
|
||||
#include <getopt.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept {
|
||||
LaunchParams p{};
|
||||
|
||||
int option_index = 0;
|
||||
static struct option long_options[] = {
|
||||
// clang-format off
|
||||
{"debug", no_argument, 0, 'd'},
|
||||
{"config", required_argument, 0, 'c'},
|
||||
{"fullscreen", no_argument, 0, 'f'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"game", required_argument, 0, 'g'},
|
||||
{"multiplayer", required_argument, 0, 'm'},
|
||||
{"program", optional_argument, 0, 'p'},
|
||||
{"user", required_argument, 0, 'u'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"input-profile", no_argument, 0, 'i'},
|
||||
{"null-render", no_argument, 0, 'n'},
|
||||
{"singlecore", no_argument, 0, 's'},
|
||||
{"filter", no_argument, 0, 'x'},
|
||||
{0, 0, 0, 0},
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
while (optind < argc) {
|
||||
if (int arg = getopt_long(argc, argv, "dc:fhg:m:p:u:vinsx", long_options, &option_index); arg != -1) {
|
||||
switch (char(arg)) {
|
||||
case 'd':
|
||||
p.override_gdb_port = uint16_t(atoi(optarg));
|
||||
break;
|
||||
case 'c':
|
||||
p.config_path = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
p.fullscreen = true;
|
||||
LOG_INFO(Frontend, "Starting in fullscreen mode...");
|
||||
break;
|
||||
case 'h':
|
||||
p.print_help = true;
|
||||
break;
|
||||
case 'g':
|
||||
p.filepath = std::string(optarg);
|
||||
break;
|
||||
case 'i': {
|
||||
p.input_profile = std::string(optarg);
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
p.use_multiplayer = true;
|
||||
const std::string str_arg(optarg);
|
||||
// regex to check if the format is nickname:password@ip:port
|
||||
// with optional :password
|
||||
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
|
||||
if (std::regex_match(str_arg, re)) {
|
||||
std::smatch match;
|
||||
std::regex_search(str_arg, match, re);
|
||||
ASSERT(match.size() == 5);
|
||||
p.nickname = match[1];
|
||||
p.password = match[2];
|
||||
p.address = match[3];
|
||||
if (!match[4].str().empty()) {
|
||||
p.port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
|
||||
}
|
||||
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
||||
ASSERT(std::regex_match(p.nickname, nickname_re) && "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
|
||||
ASSERT(!p.address.empty() && "Address is empty");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'p':
|
||||
p.program_args = argv[optind];
|
||||
++optind;
|
||||
break;
|
||||
case 'u': {
|
||||
// Launch game with a specific user
|
||||
bool argument_ok = isdigit(optarg[0]);
|
||||
p.selected_user = atoi(optarg);
|
||||
if (!argument_ok) {
|
||||
// try to look it up by username, only finds the first username that matches.
|
||||
auto const user_idx = system.GetProfileManager().GetUserIndex(optarg);
|
||||
if (user_idx != std::nullopt) {
|
||||
p.selected_user = user_idx.value();
|
||||
} else {
|
||||
LOG_ERROR(Frontend, "Invalid user argument '{}'", optarg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (system.GetProfileManager().UserExistsIndex(*p.selected_user)) {
|
||||
Settings::values.current_user = s32(*p.selected_user);
|
||||
} else {
|
||||
LOG_ERROR(Frontend, "Selected user {} doesn't exist", *p.selected_user);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'v':
|
||||
p.print_version = true;
|
||||
break;
|
||||
case 'n':
|
||||
p.force_null_render = true;
|
||||
break;
|
||||
case 's':
|
||||
p.force_single_core = true;
|
||||
break;
|
||||
case 'x':
|
||||
p.log_filter = argv[optind];
|
||||
++optind;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// only kept due to shortcuts made by Qt frontend which use "-qlaunch"
|
||||
if (strcmp(argv[optind], "-hlaunch") == 0) {
|
||||
p.launch_hlaunch = true;
|
||||
} else if (strcmp(argv[optind], "-qlaunch") == 0) {
|
||||
p.launch_qlaunch = true;
|
||||
} else if (strcmp(argv[optind], "-setup") == 0) {
|
||||
p.launch_setup = true;
|
||||
} else {
|
||||
// qt feeds utf8 data, sdl frontend feeds raw utf16 data
|
||||
#ifdef _WIN32
|
||||
p.filepath = argv_w != nullptr
|
||||
? Common::UTF16ToUTF8(argv_w[optind])
|
||||
: argv[optind];
|
||||
#else
|
||||
p.filepath = argv[optind];
|
||||
#endif
|
||||
}
|
||||
optind++;
|
||||
}
|
||||
}
|
||||
p.argv0 = argv[0];
|
||||
return p;
|
||||
}
|
||||
|
||||
void ApplyLaunchParams(LaunchParams const& lp) noexcept {
|
||||
// apply the log_filter setting
|
||||
// the logger was initialized before and doesn't pick up the filter on its own
|
||||
Common::Log::Filter filter{};
|
||||
filter.ParseFilterString(lp.log_filter.value_or(Settings::values.log_filter.GetValue()));
|
||||
Common::Log::SetGlobalFilter(filter);
|
||||
|
||||
if (!lp.program_args.empty()) {
|
||||
Settings::values.program_args = lp.program_args;
|
||||
}
|
||||
if (!lp.input_profile.empty()) {
|
||||
auto& players = Settings::values.players.GetValue();
|
||||
players[0].profile_name = lp.input_profile;
|
||||
}
|
||||
if (lp.selected_user.has_value()) {
|
||||
Settings::values.current_user = std::clamp(*lp.selected_user, 0, 7);
|
||||
}
|
||||
if (lp.override_gdb_port.has_value()) {
|
||||
Settings::values.use_gdbstub = true;
|
||||
Settings::values.gdbstub_port = *lp.override_gdb_port;
|
||||
}
|
||||
if (lp.force_single_core) {
|
||||
Settings::values.use_multi_core = false;
|
||||
}
|
||||
if (lp.force_null_render) {
|
||||
Settings::values.renderer_backend = Settings::RendererBackend::Null;
|
||||
}
|
||||
|
||||
if (lp.print_version) {
|
||||
LOG_INFO(Frontend, "Eden {} {}", Common::g_scm_branch, Common::g_scm_desc);
|
||||
}
|
||||
if (lp.print_help) {
|
||||
LOG_INFO(Frontend,
|
||||
"Usage: {}"
|
||||
" [options] <filename>\n"
|
||||
"-c, --config Load the specified configuration file\n"
|
||||
"-f, --fullscreen Start in fullscreen mode\n"
|
||||
"-g, --game File path of the game to load\n"
|
||||
"-h, --help Display this help and exit\n"
|
||||
"-m, --multiplayer=nick:password@address:port"
|
||||
" Nickname, password, address and port for multiplayer\n"
|
||||
"-p, --program Pass following string as arguments to executable\n"
|
||||
"-u, --user Select a specific user profile from 0 to 7\n"
|
||||
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
|
||||
"-v, --version Output version information and exit\n",
|
||||
lp.argv0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include "common/common_types.h"
|
||||
#include "network/room.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class System;
|
||||
struct LaunchParams {
|
||||
std::string argv0{};
|
||||
std::string filepath{};
|
||||
std::string nickname{};
|
||||
std::string password{};
|
||||
std::string address{};
|
||||
std::string input_profile{};
|
||||
std::string program_args{};
|
||||
std::optional<std::string> config_path{};
|
||||
std::optional<int> selected_user{};
|
||||
std::optional<u16> override_gdb_port{};
|
||||
std::optional<std::string> log_filter{};
|
||||
u16 port = Network::DefaultRoomPort;
|
||||
bool use_multiplayer = false;
|
||||
bool fullscreen = false;
|
||||
bool force_null_render = false;
|
||||
bool force_single_core = false;
|
||||
// print
|
||||
bool print_version = false;
|
||||
bool print_help = false;
|
||||
// qt
|
||||
bool launch_hlaunch = false;
|
||||
bool launch_qlaunch = false;
|
||||
bool launch_setup = false;
|
||||
};
|
||||
|
||||
LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept;
|
||||
void ApplyLaunchParams(LaunchParams const& lp) noexcept;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,15 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 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
|
||||
|
||||
// gcc14 bug
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
||||
#endif
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
@@ -1266,3 +1275,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
}
|
||||
|
||||
} // namespace Core::Memory
|
||||
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -254,14 +254,16 @@ struct UnrecognizedInstruction {
|
||||
|
||||
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{};
|
||||
std::variant<
|
||||
std::monostate,
|
||||
StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction
|
||||
> opcode{};
|
||||
};
|
||||
|
||||
class DmntCheatVm {
|
||||
|
||||
@@ -170,13 +170,19 @@ template<>
|
||||
|
||||
// check for marked bit, use as unmapped if marked
|
||||
if (ctx.conf.page_table_marked_bit) {
|
||||
code.bt(page, *ctx.conf.page_table_marked_bit);
|
||||
code.jc(abort, code.T_NEAR);
|
||||
auto const marked_bit = *ctx.conf.page_table_marked_bit;
|
||||
if (s64(s32(1 << marked_bit)) == s64(1 << marked_bit)) {
|
||||
code.test(page, s32(1 << marked_bit));
|
||||
code.jnz(abort, code.T_NEAR);
|
||||
} else {
|
||||
code.bt(page, marked_bit);
|
||||
code.jc(abort, code.T_NEAR);
|
||||
}
|
||||
}
|
||||
// mask away attributes
|
||||
if (ctx.conf.page_table_pointer_mask == 0) {
|
||||
code.test(page, page);
|
||||
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) {
|
||||
} else if (s64(s32(ctx.conf.page_table_pointer_mask)) == s64(ctx.conf.page_table_pointer_mask)) {
|
||||
code.and_(page, ctx.conf.page_table_pointer_mask);
|
||||
} else {
|
||||
code.mov(tmp, ctx.conf.page_table_pointer_mask);
|
||||
|
||||
@@ -79,8 +79,10 @@ void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
|
||||
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
||||
for (std::size_t i = 0; i < AruidIndexMax; i++) {
|
||||
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
|
||||
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||
UpdateButtonLifo(npad_entry, data->aruid);
|
||||
if (auto const shfmt = data->shared_memory_format; shfmt) {
|
||||
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||
UpdateButtonLifo(npad_entry, data->aruid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,19 +90,19 @@ void NpadAbstractButtonHandler::UpdateCoreBatteryState() {
|
||||
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
||||
for (std::size_t i = 0; i < AruidIndexMax; i++) {
|
||||
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
|
||||
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||
UpdateButtonLifo(npad_entry, data->aruid);
|
||||
if (auto const shfmt = data->shared_memory_format; shfmt) {
|
||||
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||
UpdateButtonLifo(npad_entry, data->aruid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
|
||||
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
|
||||
auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
if (auto data = applet_resource_holder->applet_resource->GetAruidData(aruid); data) {
|
||||
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||
UpdateButtonLifo(npad_entry, aruid);
|
||||
}
|
||||
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
|
||||
UpdateButtonLifo(npad_entry, aruid);
|
||||
}
|
||||
|
||||
Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
|
||||
|
||||
@@ -66,14 +66,17 @@ void EmuThread::run() {
|
||||
} else {
|
||||
QtCommon::system->Pause();
|
||||
m_stopped.Set();
|
||||
|
||||
EmulationPaused(lk);
|
||||
m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; });
|
||||
m_should_run_cv.wait(lk, stop_token, [&] {
|
||||
return m_should_run;
|
||||
});
|
||||
EmulationResumed(lk);
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown the main emulated process
|
||||
// Needs to pause first, we may not have paused before shutting down.
|
||||
QtCommon::system->Pause();
|
||||
QtCommon::system->DetachDebugger();
|
||||
QtCommon::system->ShutdownMainProcess();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -31,6 +28,11 @@ struct FuncTraits<ReturnType_ (*)(Args...)> {
|
||||
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
|
||||
};
|
||||
|
||||
template <auto func, typename... Args>
|
||||
void SetDefinition(EmitContext& ctx, IR::Inst* inst, Args... args) {
|
||||
inst->SetDefinition<Id>(func(ctx, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename ArgType>
|
||||
auto Arg(EmitContext& ctx, const IR::Value& arg) {
|
||||
if constexpr (std::is_same_v<ArgType, std::string_view>) {
|
||||
@@ -51,10 +53,21 @@ auto Arg(EmitContext& ctx, const IR::Value& arg) {
|
||||
template <auto func, bool is_first_arg_inst, size_t... I>
|
||||
void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
|
||||
using Traits = FuncTraits<decltype(func)>;
|
||||
if constexpr (is_first_arg_inst) {
|
||||
func(ctx, *inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||
if constexpr (std::is_same_v<typename Traits::ReturnType, Id>) {
|
||||
if constexpr (is_first_arg_inst) {
|
||||
SetDefinition<func>(
|
||||
ctx, inst, *inst,
|
||||
Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||
} else {
|
||||
SetDefinition<func>(
|
||||
ctx, inst, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||
}
|
||||
} else {
|
||||
func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||
if constexpr (is_first_arg_inst) {
|
||||
func(ctx, *inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||
} else {
|
||||
func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -496,31 +496,30 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
|
||||
}
|
||||
|
||||
void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
|
||||
// Flatten all leading PHIs from each block into a vector
|
||||
std::vector<IR::Inst*> phi_instructions;
|
||||
for (IR::Block* block : program.blocks) {
|
||||
for (auto it = block->begin(); it != block->end(); ++it) {
|
||||
if (it->GetOpcode() != IR::Opcode::Phi)
|
||||
break;
|
||||
phi_instructions.push_back(&*it);
|
||||
}
|
||||
}
|
||||
|
||||
if (phi_instructions.empty()) {
|
||||
return; // nothing to patch
|
||||
}
|
||||
|
||||
// Start "before" first PHI; advance on phi_arg == 0
|
||||
size_t phi_index = static_cast<size_t>(-1);
|
||||
|
||||
ctx.PatchDeferredPhi([&](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
|
||||
if (phi_arg == 0) {
|
||||
++phi_index;
|
||||
}
|
||||
IR::Inst* phi = phi_instructions[phi_index];
|
||||
return { ctx.Def(phi->Arg(phi_arg)), parent };
|
||||
});
|
||||
// Flatten all leading PHIs from each block into a vector
|
||||
std::vector<IR::Inst*> phi_instructions;
|
||||
for (IR::Block* block : program.blocks) {
|
||||
for (auto it = block->begin(); it != block->end(); ++it) {
|
||||
if (it->GetOpcode() != IR::Opcode::Phi)
|
||||
break;
|
||||
phi_instructions.push_back(&*it);
|
||||
}
|
||||
}
|
||||
|
||||
if (phi_instructions.empty()) {
|
||||
return; // nothing to patch
|
||||
}
|
||||
|
||||
// Start "before" first PHI; advance on phi_arg == 0
|
||||
size_t phi_index = size_t(-1);
|
||||
ctx.PatchDeferredPhi([&ctx, &phi_index, phi_insts = std::move(phi_instructions)](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
|
||||
if (phi_arg == 0) {
|
||||
++phi_index;
|
||||
}
|
||||
IR::Inst* phi = phi_insts[phi_index];
|
||||
return { ctx.Def(phi->Arg(phi_arg)), parent };
|
||||
});
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings) {
|
||||
|
||||
@@ -98,10 +98,7 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
|
||||
RefreshResources(device, framebuffer);
|
||||
SetAntiAliasPass(device);
|
||||
#ifdef HAS_RESHADE
|
||||
const bool is_applet =
|
||||
(framebuffer.layer_stack_mask & Service::Nvnflinger::LayerStackBit(
|
||||
Service::Nvnflinger::LayerStackId::Recording)) == 0;
|
||||
SetPostProcessPass(device, is_applet);
|
||||
SetPostProcessPass(device);
|
||||
#endif
|
||||
|
||||
// Finish any pending renderpass
|
||||
@@ -231,11 +228,7 @@ void Layer::SetAntiAliasPass(const Device& device) {
|
||||
}
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
void Layer::SetPostProcessPass(const Device& device, bool is_applet) {
|
||||
if (is_applet) {
|
||||
post_process.reset();
|
||||
return;
|
||||
}
|
||||
void Layer::SetPostProcessPass(const Device& device) {
|
||||
const VkExtent2D render_area{
|
||||
.width = Settings::values.resolution_info.ScaleUp(raw_width),
|
||||
.height = Settings::values.resolution_info.ScaleUp(raw_height),
|
||||
|
||||
@@ -70,7 +70,7 @@ private:
|
||||
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
||||
void SetAntiAliasPass(const Device& device);
|
||||
#ifdef HAS_RESHADE
|
||||
void SetPostProcessPass(const Device& device, bool is_applet);
|
||||
void SetPostProcessPass(const Device& device);
|
||||
#endif
|
||||
void ReleaseRawImages();
|
||||
|
||||
|
||||
+68
-18
@@ -3,7 +3,6 @@
|
||||
|
||||
// Static Qt on macOS doesn't use Vulkan
|
||||
// Other platforms do, and thus have conflicting VulkanMemoryAllocator symbols
|
||||
#include "core/launch_params.h"
|
||||
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
||||
#undef VMA_IMPLEMENTATION
|
||||
#endif
|
||||
@@ -516,28 +515,79 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
||||
// to prevent the UI from blowing up.
|
||||
UpdateUITheme();
|
||||
|
||||
if (QStringList args = QApplication::arguments(); args.size() >= 2) {
|
||||
QList<QByteArray> qba{};
|
||||
for (auto const& e : args)
|
||||
qba.push_back(e.toUtf8());
|
||||
std::vector<char*> args_cstr{};
|
||||
for (auto& e : qba)
|
||||
args_cstr.push_back(e.data());
|
||||
QStringList args = QApplication::arguments();
|
||||
|
||||
auto const lp = Core::ParseLaunchParams(*QtCommon::system, args_cstr.size(), args_cstr.data(), nullptr);
|
||||
if (args.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Override fullscreen setting if gamepath or argument is provided
|
||||
if (!lp.filepath.empty() || lp.fullscreen) {
|
||||
ui->action_Fullscreen->setChecked(lp.fullscreen);
|
||||
QString game_path;
|
||||
bool should_launch_qlaunch = false;
|
||||
bool should_launch_hlaunch = false;
|
||||
bool should_launch_setup = false;
|
||||
bool has_gamepath = false;
|
||||
bool is_fullscreen = false;
|
||||
|
||||
// Preserves drag/drop functionality
|
||||
for (int i = 1; i < args.size(); ++i) {
|
||||
if (args[i] == QStringLiteral("-f")) {
|
||||
// Launch game in fullscreen mode
|
||||
is_fullscreen = true;
|
||||
} else if (args[i] == QStringLiteral("-u") && i < args.size() - 1) {
|
||||
// Launch game with a specific user
|
||||
int user_arg_idx = ++i;
|
||||
bool argument_ok;
|
||||
std::size_t selected_user = args[user_arg_idx].toUInt(&argument_ok);
|
||||
if (!argument_ok) {
|
||||
// try to look it up by username, only finds the first username that matches.
|
||||
std::string const user_arg_str = args[user_arg_idx].toStdString();
|
||||
auto const user_idx =
|
||||
QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str);
|
||||
if (user_idx != std::nullopt) {
|
||||
selected_user = user_idx.value();
|
||||
} else {
|
||||
LOG_ERROR(Frontend, "Invalid user argument '{}'", user_arg_str);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (QtCommon::system->GetProfileManager().UserExistsIndex(selected_user)) {
|
||||
Settings::values.current_user = s32(selected_user);
|
||||
user_flag_cmd_line = true;
|
||||
} else {
|
||||
LOG_ERROR(Frontend, "Selected user {} doesn't exist", selected_user);
|
||||
}
|
||||
} else if (args[i] == QStringLiteral("-g") && i < args.size() - 1) {
|
||||
// Launch game at path
|
||||
game_path = args[++i];
|
||||
has_gamepath = true;
|
||||
} else if (args[i] == QStringLiteral("-input-profile") && i < args.size() - 1) {
|
||||
auto& players = Settings::values.players.GetValue();
|
||||
players[0].profile_name = args[++i].toStdString();
|
||||
} else if (args[i] == QStringLiteral("-qlaunch")) {
|
||||
should_launch_qlaunch = true;
|
||||
} else if (args[i] == QStringLiteral("-hlaunch")) {
|
||||
should_launch_hlaunch = true;
|
||||
} else if (args[i] == QStringLiteral("-setup")) {
|
||||
should_launch_setup = true;
|
||||
} else {
|
||||
game_path = args[i];
|
||||
has_gamepath = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lp.filepath.empty()) {
|
||||
BootGame(QString::fromStdString(lp.filepath), ApplicationAppletParameters());
|
||||
} else if (lp.launch_setup) {
|
||||
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
|
||||
} else if (lp.launch_qlaunch) {
|
||||
// Override fullscreen setting if gamepath or argument is provided
|
||||
if (has_gamepath || is_fullscreen) {
|
||||
ui->action_Fullscreen->setChecked(is_fullscreen);
|
||||
}
|
||||
|
||||
if (should_launch_setup) {
|
||||
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
|
||||
} else {
|
||||
if (!game_path.isEmpty()) {
|
||||
BootGame(game_path, ApplicationAppletParameters());
|
||||
} else if (should_launch_qlaunch) {
|
||||
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
|
||||
} else if (lp.launch_hlaunch) {
|
||||
} else if (should_launch_hlaunch) {
|
||||
std::filesystem::path const sd_dir =
|
||||
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
|
||||
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
|
||||
|
||||
@@ -42,6 +42,9 @@ target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common
|
||||
if (ENABLE_OPENGL)
|
||||
target_link_libraries(yuzu-cmd PRIVATE glad)
|
||||
endif()
|
||||
if (MSVC)
|
||||
target_link_libraries(yuzu-cmd PRIVATE getopt)
|
||||
endif()
|
||||
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
|
||||
|
||||
+190
-16
@@ -10,7 +10,6 @@
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include "common/settings_enums.h"
|
||||
#include "core/launch_params.h"
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
@@ -53,6 +52,12 @@
|
||||
#include "common/windows/timer_resolution.h"
|
||||
#endif
|
||||
|
||||
#undef _UNICODE
|
||||
#include <getopt.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
extern "C" {
|
||||
// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
|
||||
@@ -62,6 +67,25 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void PrintHelp(const char* argv0) {
|
||||
std::cout << "Usage: " << argv0
|
||||
<< " [options] <filename>\n"
|
||||
"-c, --config Load the specified configuration file\n"
|
||||
"-f, --fullscreen Start in fullscreen mode\n"
|
||||
"-g, --game File path of the game to load\n"
|
||||
"-h, --help Display this help and exit\n"
|
||||
"-m, --multiplayer=nick:password@address:port"
|
||||
" Nickname, password, address and port for multiplayer\n"
|
||||
"-p, --program Pass following string as arguments to executable\n"
|
||||
"-u, --user Select a specific user profile from 0 to 7\n"
|
||||
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
|
||||
"-v, --version Output version information and exit\n";
|
||||
}
|
||||
|
||||
static void PrintVersion() {
|
||||
std::cout << "Eden " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
|
||||
}
|
||||
|
||||
static void OnStateChanged(const Network::RoomMember::State& state) {
|
||||
switch (state) {
|
||||
case Network::RoomMember::State::Idle:
|
||||
@@ -184,18 +208,168 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
||||
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
#else
|
||||
wchar_t **argv_w = nullptr;
|
||||
#endif
|
||||
Core::LaunchParams lp = Core::ParseLaunchParams(state->system, argc, argv, argv_w);
|
||||
std::string filepath;
|
||||
std::optional<std::string> config_path{};
|
||||
std::string program_args;
|
||||
std::optional<int> selected_user{};
|
||||
std::optional<u16> override_gdb_port{};
|
||||
bool use_multiplayer = false;
|
||||
bool fullscreen = false;
|
||||
bool force_null_render = false;
|
||||
bool force_single_core = false;
|
||||
std::string nickname{};
|
||||
std::string password{};
|
||||
std::string address{};
|
||||
std::string input_profile{};
|
||||
std::optional<std::string> log_filter{};
|
||||
u16 port = Network::DefaultRoomPort;
|
||||
|
||||
static struct option long_options[] = {
|
||||
// clang-format off
|
||||
{"debug", no_argument, 0, 'd'},
|
||||
{"config", required_argument, 0, 'c'},
|
||||
{"fullscreen", no_argument, 0, 'f'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"game", required_argument, 0, 'g'},
|
||||
{"multiplayer", required_argument, 0, 'm'},
|
||||
{"program", optional_argument, 0, 'p'},
|
||||
{"user", required_argument, 0, 'u'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"input-profile", no_argument, 0, 'i'},
|
||||
{"null-render", no_argument, 0, 'n'},
|
||||
{"singlecore", no_argument, 0, 's'},
|
||||
{"filter", no_argument, 0, 'x'},
|
||||
{0, 0, 0, 0},
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
while (optind < argc) {
|
||||
int arg = getopt_long(argc, argv, "g:fhvcip::c:u:d:", long_options, &option_index);
|
||||
if (arg != -1) {
|
||||
switch (char(arg)) {
|
||||
case 'd':
|
||||
override_gdb_port = uint16_t(atoi(optarg));
|
||||
break;
|
||||
case 'c':
|
||||
config_path = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
fullscreen = true;
|
||||
LOG_INFO(Frontend, "Starting in fullscreen mode...");
|
||||
break;
|
||||
case 'h':
|
||||
PrintHelp(argv[0]);
|
||||
return SDL_APP_FAILURE;
|
||||
case 'g':
|
||||
filepath = std::string(optarg);
|
||||
break;
|
||||
case 'i': {
|
||||
input_profile = std::string(optarg);
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
use_multiplayer = true;
|
||||
const std::string str_arg(optarg);
|
||||
// regex to check if the format is nickname:password@ip:port
|
||||
// with optional :password
|
||||
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
|
||||
if (!std::regex_match(str_arg, re)) {
|
||||
std::cout << "Wrong format for option --multiplayer\n";
|
||||
PrintHelp(argv[0]);
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
std::smatch match;
|
||||
std::regex_search(str_arg, match, re);
|
||||
ASSERT(match.size() == 5);
|
||||
nickname = match[1];
|
||||
password = match[2];
|
||||
address = match[3];
|
||||
if (!match[4].str().empty()) {
|
||||
port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
|
||||
}
|
||||
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
||||
if (!std::regex_match(nickname, nickname_re)) {
|
||||
LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
if (address.empty()) {
|
||||
LOG_ERROR(Frontend, "Address to room must not be empty");
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'p':
|
||||
program_args = argv[optind];
|
||||
++optind;
|
||||
break;
|
||||
case 'u':
|
||||
selected_user = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
PrintVersion();
|
||||
return SDL_APP_FAILURE;
|
||||
case 'n':
|
||||
force_null_render = true;
|
||||
break;
|
||||
case 's':
|
||||
force_single_core = true;
|
||||
break;
|
||||
case 'x':
|
||||
log_filter = argv[optind];
|
||||
++optind;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
filepath = Common::UTF16ToUTF8(argv_w[optind]);
|
||||
#else
|
||||
filepath = argv[optind];
|
||||
#endif
|
||||
optind++;
|
||||
}
|
||||
}
|
||||
|
||||
SdlConfig config{config_path};
|
||||
|
||||
// apply the log_filter setting
|
||||
// the logger was initialized before and doesn't pick up the filter on its own
|
||||
Common::Log::Filter filter;
|
||||
filter.ParseFilterString(log_filter.value_or(Settings::values.log_filter.GetValue()));
|
||||
Common::Log::SetGlobalFilter(filter);
|
||||
|
||||
if (!program_args.empty()) {
|
||||
Settings::values.program_args = program_args;
|
||||
}
|
||||
|
||||
if (!input_profile.empty()) {
|
||||
auto& players = Settings::values.players.GetValue();
|
||||
players[0].profile_name = input_profile;
|
||||
}
|
||||
|
||||
if (selected_user.has_value()) {
|
||||
Settings::values.current_user = std::clamp(*selected_user, 0, 7);
|
||||
}
|
||||
|
||||
if (override_gdb_port.has_value()) {
|
||||
Settings::values.use_gdbstub = true;
|
||||
Settings::values.gdbstub_port = *override_gdb_port;
|
||||
}
|
||||
|
||||
if (force_single_core) {
|
||||
Settings::values.use_multi_core = false;
|
||||
}
|
||||
|
||||
if (force_null_render) {
|
||||
Settings::values.renderer_backend = Settings::RendererBackend::Null;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
LocalFree(argv_w);
|
||||
#endif
|
||||
SdlConfig config{lp.config_path};
|
||||
|
||||
Core::ApplyLaunchParams(lp);
|
||||
|
||||
if (lp.filepath.empty()) {
|
||||
if (filepath.empty()) {
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
@@ -212,14 +386,14 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
||||
case Settings::RendererBackend::OpenGL_GLSL:
|
||||
case Settings::RendererBackend::OpenGL_GLASM:
|
||||
case Settings::RendererBackend::OpenGL_SPIRV:
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, lp.fullscreen);
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, fullscreen);
|
||||
break;
|
||||
#endif
|
||||
case Settings::RendererBackend::Vulkan:
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, lp.fullscreen);
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, fullscreen);
|
||||
break;
|
||||
case Settings::RendererBackend::Null:
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, lp.fullscreen);
|
||||
state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, fullscreen);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
||||
@@ -239,12 +413,12 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
||||
Service::AM::FrontendAppletParameters load_parameters{
|
||||
.applet_id = Service::AM::AppletId::Application,
|
||||
};
|
||||
const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, lp.filepath, load_parameters);
|
||||
const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, filepath, load_parameters);
|
||||
switch (load_result) {
|
||||
case Core::SystemResultStatus::Success:
|
||||
break; // Expected case
|
||||
case Core::SystemResultStatus::ErrorGetLoader:
|
||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", lp.filepath);
|
||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
|
||||
return SDL_APP_FAILURE;
|
||||
case Core::SystemResultStatus::ErrorLoader:
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||
@@ -266,14 +440,14 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
if (lp.use_multiplayer) {
|
||||
if (use_multiplayer) {
|
||||
if (auto member = Network::GetRoomMember().lock()) {
|
||||
member->BindOnChatMessageReceived(OnMessageReceived);
|
||||
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
||||
member->BindOnStateChanged(OnStateChanged);
|
||||
member->BindOnError(OnNetworkError);
|
||||
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", lp.address, lp.port, lp.nickname);
|
||||
member->Join(lp.nickname, lp.address.c_str(), lp.port, 0, Network::NoPreferredIP, lp.password);
|
||||
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname);
|
||||
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
|
||||
} else {
|
||||
LOG_ERROR(Network, "Could not access RoomMember");
|
||||
return SDL_APP_FAILURE;
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs/vfs_real.h"
|
||||
#include "core/hle/service/am/applet_manager.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "input_common/main.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
|
||||
|
||||
class EmuWindow_Headless : public Core::Frontend::EmuWindow {
|
||||
public:
|
||||
explicit EmuWindow_Headless() = default;
|
||||
~EmuWindow_Headless() = default;
|
||||
|
||||
bool IsShown() const override {
|
||||
return true;
|
||||
}
|
||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override {
|
||||
|
||||
}
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
|
||||
return std::make_unique<DummyContext>();
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct {
|
||||
Core::System system{};
|
||||
EmuWindow_Headless emu_window{};
|
||||
} state = {};
|
||||
|
||||
Common::Log::Initialize();
|
||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||
Common::Log::Start();
|
||||
|
||||
if (argc < 2) {
|
||||
LOG_CRITICAL(Frontend, "Usage: {} [ms-to-run] [file]", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::chrono::milliseconds time_quanta = std::chrono::milliseconds{atoi(argv[1])};
|
||||
auto const time_end = std::chrono::steady_clock::now() + time_quanta;
|
||||
std::string filepath = argv[2];
|
||||
|
||||
// apply the log_filter setting
|
||||
// the logger was initialized before and doesn't pick up the filter on its own
|
||||
Common::Log::Filter filter;
|
||||
filter.ParseFilterString("*:Info");
|
||||
Common::Log::SetGlobalFilter(filter);
|
||||
|
||||
if (filepath.empty()) {
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
state.system.Initialize();
|
||||
|
||||
InputCommon::InputSubsystem input_subsystem{};
|
||||
|
||||
// Apply the command line arguments
|
||||
state.system.ApplySettings();
|
||||
Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Null);
|
||||
|
||||
state.system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
state.system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||
state.system.GetFileSystemController().CreateFactories(*state.system.GetFilesystem());
|
||||
state.system.GetUserChannel().clear();
|
||||
|
||||
Service::AM::FrontendAppletParameters load_parameters{
|
||||
.applet_id = Service::AM::AppletId::Application,
|
||||
};
|
||||
if (auto const load_result = state.system.Load(state.emu_window, filepath, load_parameters); load_result != Core::SystemResultStatus::Success) {
|
||||
LOG_CRITICAL(Frontend, "load result = {}", u32(load_result));
|
||||
// shutdown
|
||||
void(state.system.Pause());
|
||||
state.system.DetachDebugger();
|
||||
state.system.ShutdownMainProcess();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
|
||||
state.system.GPU().Start();
|
||||
state.system.GetCpuManager().OnGpuReady();
|
||||
// don't do anything, SDL3 already exists for us :D
|
||||
state.system.RegisterExitCallback([] {});
|
||||
void(state.system.Run());
|
||||
if (state.system.DebuggerEnabled())
|
||||
state.system.InitializeDebugger();
|
||||
while (state.system.IsPoweredOn() && !state.system.GetExitRequested()) {
|
||||
auto const time_now = std::chrono::steady_clock::now();
|
||||
if (time_now > time_end)
|
||||
break;
|
||||
}
|
||||
// shutdown
|
||||
void(state.system.Pause());
|
||||
state.system.DetachDebugger();
|
||||
state.system.ShutdownMainProcess();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include "video_core/vulkan_common/vma.h"
|
||||
Reference in New Issue
Block a user