mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-27 17:31:31 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e20cd98a7 | |||
| 6a3adefd46 | |||
| 0fc813332f | |||
| 95c78a206c | |||
| 24cb761852 | |||
| 23d0ad417d | |||
| 2fa88c4868 | |||
| a9461e23b3 | |||
| 8476fbfcfe | |||
| ad854018df | |||
| b748f0ad5f | |||
| b2c9ccfc7e | |||
| 13f48a56ad | |||
| 63e33f37f6 | |||
| 41cd784835 | |||
| c41cadf3f4 | |||
| a43664c0fd | |||
| a0f1cd1baf | |||
| 49a0ca6d5d | |||
| 5e1d5e82dc | |||
| ba9130fbf9 | |||
| 7d5f390ffb | |||
| 8fe1e6efa2 | |||
| ee197e6222 |
@@ -188,6 +188,7 @@ android {
|
||||
create("mainline") {
|
||||
dimension = "version"
|
||||
isDefault = true
|
||||
minSdk = 33
|
||||
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden")
|
||||
resValue("string", "app_name_suffixed", "Eden")
|
||||
@@ -199,6 +200,7 @@ android {
|
||||
|
||||
create("genshinSpoof") {
|
||||
dimension = "version"
|
||||
minSdk = 35
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden Optimized")
|
||||
resValue("string", "app_name_suffixed", "Eden Optimized")
|
||||
applicationId = "com.miHoYo.Yuanshen"
|
||||
@@ -216,6 +218,7 @@ android {
|
||||
|
||||
create("legacy") {
|
||||
dimension = "version"
|
||||
minSdk = 29
|
||||
manifestPlaceholders += mapOf("appNameBase" to "Eden Legacy")
|
||||
resValue("string", "app_name_suffixed", "Eden Legacy")
|
||||
applicationId = "dev.legacy.eden_emulator"
|
||||
|
||||
@@ -218,6 +218,8 @@ object NativeLibrary {
|
||||
|
||||
external fun logSettings()
|
||||
|
||||
external fun refreshThreadPolicies()
|
||||
|
||||
external fun getDebugKnobAt(index: Int): Boolean
|
||||
|
||||
/**
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
|
||||
RENDERER_ASYNC_PRESENTATION("async_presentation"),
|
||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
|
||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
|
||||
|
||||
@@ -50,7 +50,6 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
GPU_UNSWIZZLE_TEXTURE_SIZE("gpu_unswizzle_texture_size"),
|
||||
GPU_UNSWIZZLE_STREAM_SIZE("gpu_unswizzle_stream_size"),
|
||||
GPU_UNSWIZZLE_CHUNK_SIZE("gpu_unswizzle_chunk_size"),
|
||||
HOMEBREW_NXLINK_SERVER_MODE("homebrew_nxlink_server_mode"),
|
||||
BAT_TEMPERATURE_UNIT("bat_temperature_unit"),
|
||||
CABINET_APPLET("cabinet_applet_mode"),
|
||||
CONTROLLER_APPLET("controller_applet_mode"),
|
||||
|
||||
+8
-10
@@ -132,15 +132,6 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.program_args_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.HOMEBREW_NXLINK_SERVER_MODE,
|
||||
titleId = R.string.nxlink_server_mode,
|
||||
descriptionId = R.string.nxlink_server_mode_description,
|
||||
choicesId = R.array.nxlinkServerModeEntries,
|
||||
valuesId = R.array.nxlinkServerModeValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||
@@ -603,7 +594,7 @@ abstract class SettingsItem(
|
||||
IntSetting.ANDROID_PIPELINE_WORKERS,
|
||||
titleId = R.string.pipeline_worker_cores,
|
||||
descriptionId = R.string.pipeline_worker_cores_description,
|
||||
min = 4,
|
||||
min = 2,
|
||||
max = 8,
|
||||
units = "cores"
|
||||
)
|
||||
@@ -694,6 +685,13 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.renderer_asynchronous_shaders_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_UNIFIED_MEMORY,
|
||||
titleId = R.string.renderer_unified_memory,
|
||||
descriptionId = R.string.renderer_unified_memory_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.FAST_GPU_TIME,
|
||||
|
||||
+3
-3
@@ -250,8 +250,9 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.USE_CUSTOM_RTC.key)
|
||||
add(LongSetting.CUSTOM_RTC.key)
|
||||
|
||||
add(HeaderSetting(R.string.cpu))
|
||||
add(HeaderSetting(R.string.clocks))
|
||||
add(IntSetting.FAST_CPU_TIME.key)
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
|
||||
|
||||
add(IntSetting.MEMORY_LAYOUT.key)
|
||||
@@ -298,12 +299,12 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(HeaderSetting(R.string.hacks))
|
||||
|
||||
add(IntSetting.FAST_GPU_TIME.key)
|
||||
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
|
||||
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
|
||||
add(BooleanSetting.EMULATE_BGR565.key)
|
||||
add(BooleanSetting.RESCALE_HACK.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
|
||||
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
|
||||
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
|
||||
@@ -1288,7 +1289,6 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(ShortSetting.DEBUG_KNOBS.key)
|
||||
add(StringSetting.PROGRAM_ARGS.key)
|
||||
add(IntSetting.HOMEBREW_NXLINK_SERVER_MODE.key)
|
||||
|
||||
if (!NativeConfig.isPerGameConfigLoaded()) {
|
||||
add(HeaderSetting(R.string.gpu_logging_header))
|
||||
|
||||
@@ -1451,6 +1451,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
NativeLibrary.refreshThreadPolicies()
|
||||
val b = _binding ?: return
|
||||
updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt())
|
||||
updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt())
|
||||
|
||||
@@ -20,132 +20,96 @@ struct RomMetadata {
|
||||
std::vector<u8> icon;
|
||||
bool isHomebrew;
|
||||
};
|
||||
static ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
|
||||
|
||||
ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
|
||||
static RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
auto& instance = EmulationSession::GetInstance();
|
||||
const auto file = Core::GetGameFileFromPath(instance.System().GetFilesystem(), path);
|
||||
if (auto loader = Loader::GetLoader(instance.System(), file, 0, 0); loader) {
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadProgramId(entry.programId);
|
||||
loader->ReadIcon(entry.icon);
|
||||
|
||||
RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
const auto file =
|
||||
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
const FileSys::PatchManager pm{
|
||||
entry.programId,
|
||||
instance.System().GetFileSystemController(),
|
||||
instance.System().GetContentProvider()
|
||||
};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadProgramId(entry.programId);
|
||||
loader->ReadIcon(entry.icon);
|
||||
|
||||
const FileSys::PatchManager pm{
|
||||
entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
EmulationSession::GetInstance().System().GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
if (control.first != nullptr) {
|
||||
entry.developer = control.first->GetDeveloperName();
|
||||
entry.version = control.first->GetVersionString();
|
||||
} else {
|
||||
FileSys::NACP nacp;
|
||||
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
|
||||
entry.developer = nacp.GetDeveloperName();
|
||||
if (control.first != nullptr) {
|
||||
entry.developer = control.first->GetDeveloperName();
|
||||
entry.version = control.first->GetVersionString();
|
||||
} else {
|
||||
entry.developer = "";
|
||||
FileSys::NACP nacp{};
|
||||
entry.developer = loader->ReadControlData(nacp) == Loader::ResultStatus::Success
|
||||
? nacp.GetDeveloperName()
|
||||
: "";
|
||||
entry.version = "1.0.0";
|
||||
}
|
||||
|
||||
entry.version = "1.0.0";
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
}
|
||||
m_rom_metadata_cache[path] = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
}
|
||||
|
||||
m_rom_metadata_cache[path] = entry;
|
||||
|
||||
return entry;
|
||||
return {};
|
||||
}
|
||||
|
||||
RomMetadata GetRomMetadata(const std::string& path, bool reload = false) {
|
||||
if (reload) {
|
||||
static RomMetadata GetRomMetadata(const std::string& path, bool reload = false) {
|
||||
if (reload)
|
||||
return CacheRomMetadata(path);
|
||||
}
|
||||
|
||||
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
if (auto it = m_rom_metadata_cache.find(path); it != m_rom_metadata_cache.end())
|
||||
return it->second;
|
||||
return CacheRomMetadata(path);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
|
||||
Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read);
|
||||
if (!file) {
|
||||
return false;
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
if (auto const file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read); file) {
|
||||
if (auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file); loader) {
|
||||
auto const file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error)
|
||||
return false;
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) && !Loader::IsBootableGameContainer(file, file_type))
|
||||
return false;
|
||||
u64 program_id = 0;
|
||||
return loader->ReadProgramId(program_id) == Loader::ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file);
|
||||
if (!loader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
|
||||
!Loader::IsBootableGameContainer(file, file_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
Loader::ResultStatus res = loader->ReadProgramId(program_id);
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId));
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId));
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer);
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
|
||||
jstring jpath, jboolean jreload) {
|
||||
return Common::Android::ToJString(
|
||||
env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version);
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, jstring jpath, jboolean jreload) {
|
||||
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version);
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
auto icon_data = GetRomMetadata(Common::Android::GetJString(env, jpath)).icon;
|
||||
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
|
||||
reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
jbyteArray icon = env->NewByteArray(jsize(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
return icon;
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
|
||||
jstring jpath) {
|
||||
return static_cast<jboolean>(
|
||||
GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew);
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj, jstring jpath) {
|
||||
return jboolean(GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
|
||||
|
||||
@@ -50,6 +50,7 @@ extern "C" {
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/thread.h"
|
||||
#include "frontend_common/play_time_manager.h"
|
||||
#include "core/constants.h"
|
||||
#include "core/core.h"
|
||||
@@ -1182,6 +1183,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
|
||||
Settings::LogSettings();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) {
|
||||
Common::RefreshThreadPolicies();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
|
||||
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
|
||||
}
|
||||
|
||||
@@ -958,7 +958,6 @@
|
||||
<string name="clock_fast">سريع (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">تعطيل</string>
|
||||
<string name="fast_gpu_medium">متوسط (256)</string>
|
||||
<string name="fast_gpu_high">مرتفع (512)</string>
|
||||
|
||||
|
||||
@@ -884,7 +884,6 @@ Wirklich fortfahren?</string>
|
||||
<string name="clock_fast">Schnell (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Aus</string>
|
||||
<string name="fast_gpu_medium">Mittel (256)</string>
|
||||
<string name="fast_gpu_high">Hoch (512)</string>
|
||||
|
||||
|
||||
@@ -950,7 +950,6 @@
|
||||
<string name="clock_fast">Rápido (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Desactivado</string>
|
||||
<string name="fast_gpu_medium">Medio (256)</string>
|
||||
<string name="fast_gpu_high">Alto (512)</string>
|
||||
|
||||
|
||||
@@ -897,7 +897,6 @@
|
||||
<string name="clock_fast">Rapide (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Désactivé</string>
|
||||
<string name="fast_gpu_medium">Moyen (256)</string>
|
||||
<string name="fast_gpu_high">Élevé (512)</string>
|
||||
|
||||
|
||||
@@ -871,7 +871,6 @@
|
||||
<string name="clock_fast">Szybkie (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Wyłączone</string>
|
||||
<string name="fast_gpu_medium">Średnie (256)</string>
|
||||
<string name="fast_gpu_high">Wysokie (512)</string>
|
||||
|
||||
|
||||
@@ -954,7 +954,6 @@
|
||||
<string name="clock_fast">Быстрая (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Выкл.</string>
|
||||
<string name="fast_gpu_medium">Среднее (256)</string>
|
||||
<string name="fast_gpu_high">Высокое (512)</string>
|
||||
|
||||
|
||||
@@ -943,7 +943,6 @@
|
||||
<string name="clock_fast">Швидко (2000 МГц)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Вимкнено</string>
|
||||
<string name="fast_gpu_medium">Середньо (256)</string>
|
||||
<string name="fast_gpu_high">Високо (512)</string>
|
||||
|
||||
|
||||
@@ -948,7 +948,6 @@
|
||||
<string name="clock_fast">快速 (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">关闭</string>
|
||||
<string name="fast_gpu_medium">中 (256)</string>
|
||||
<string name="fast_gpu_high">高 (512)</string>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</integer-array>
|
||||
|
||||
<string-array name="clockNames">
|
||||
<item>@string/off</item>
|
||||
<item>@string/clock_normal</item>
|
||||
<item>@string/clock_boost</item>
|
||||
<item>@string/clock_fast</item>
|
||||
</string-array>
|
||||
@@ -547,7 +547,7 @@
|
||||
</integer-array>
|
||||
|
||||
<string-array name="gpuEntries">
|
||||
<item>@string/off</item>
|
||||
<item>@string/fast_gpu_normal</item>
|
||||
<item>@string/fast_gpu_medium</item>
|
||||
<item>@string/fast_gpu_high</item>
|
||||
</string-array>
|
||||
@@ -630,16 +630,6 @@
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="nxlinkServerModeEntries">
|
||||
<item>Disabled</item>
|
||||
<item>Eden Log</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="nxlinkServerModeValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="installKeysResults">
|
||||
<item>""</item>
|
||||
<item>""</item>
|
||||
|
||||
@@ -436,8 +436,6 @@
|
||||
|
||||
<string name="program_args">Homebrew Args</string>
|
||||
<string name="program_args_description">Command-line arguments passed to homebrew at launch (e.g. -noglsl).</string>
|
||||
<string name="nxlink_server_mode">nxlink Server</string>
|
||||
<string name="nxlink_server_mode_description">Starts a local nxlink server for homebrew stdout/stderr streams.</string>
|
||||
|
||||
<!-- System settings strings -->
|
||||
<string name="device_name">Device name</string>
|
||||
@@ -452,8 +450,8 @@
|
||||
<string name="set_custom_rtc">Set custom RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">CPU Overclock</string>
|
||||
<string name="fast_cpu_time_description">Forces the emulated CPU to run at a higher clock, reducing certain FPS limiters. Use Boost (1700MHz) to run at the Switch\'s highest native clock, or Fast (2000MHz) to run at 2x clock.</string>
|
||||
<string name="fast_cpu_time">CPU Clocks</string>
|
||||
<string name="fast_cpu_time_description">Raises the clock the emulated CPU reports, which removes some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly.</string>
|
||||
<string name="custom_cpu_ticks">Custom CPU Ticks</string>
|
||||
<string name="custom_cpu_ticks_description">Set a custom value of CPU ticks. Higher values can increase performance, but may also cause the game to freeze. A range of 77–21000 is recommended.</string>
|
||||
<string name="cpu_ticks">Ticks</string>
|
||||
@@ -514,8 +512,8 @@
|
||||
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
<string name="fast_gpu_time">Fast GPU Time</string>
|
||||
<string name="fast_gpu_time_description">Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity.</string>
|
||||
<string name="fast_gpu_time">GPU Clocks</string>
|
||||
<string name="fast_gpu_time_description">Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch\'s clocks.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
|
||||
<string name="fix_bloom_effects">Fix Bloom Effects</string>
|
||||
@@ -526,6 +524,8 @@
|
||||
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
|
||||
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
|
||||
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
|
||||
<string name="renderer_unified_memory">Unified memory access</string>
|
||||
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
|
||||
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
|
||||
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
|
||||
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
|
||||
@@ -562,6 +562,7 @@
|
||||
|
||||
<!-- Debug settings strings -->
|
||||
<string name="cpu">CPU</string>
|
||||
<string name="clocks">Clocks</string>
|
||||
<string name="use_auto_stub">Use Auto Stub</string>
|
||||
<string name="use_auto_stub_description">Automatically stub missing services and functions. This may improve compatibility but can cause crashes and stability issues.</string>
|
||||
|
||||
@@ -970,14 +971,15 @@
|
||||
<string name="memory_6gb">6GB (Unsafe)</string>
|
||||
<string name="memory_8gb">8GB (Unsafe)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Fast (2000MHz)</string>
|
||||
<!-- CPU clock levels -->
|
||||
<string name="clock_normal">Normal</string>
|
||||
<string name="clock_boost">Boost</string>
|
||||
<string name="clock_fast">Overclock</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="off">Off</string>
|
||||
<string name="fast_gpu_medium">Medium (256)</string>
|
||||
<string name="fast_gpu_high">High (512)</string>
|
||||
<!-- GPU clock levels -->
|
||||
<string name="fast_gpu_normal">Normal</string>
|
||||
<string name="fast_gpu_medium">Boost</string>
|
||||
<string name="fast_gpu_high">Overclock</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">Very Small (16 MB)</string>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<game-mode-config
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:supportsBatteryGameMode="true"
|
||||
android:supportsBatteryGameMode="false"
|
||||
android:supportsPerformanceGameMode="true"
|
||||
android:allowGameDownscaling="false"
|
||||
android:allowGameFpsOverride="false"/>
|
||||
@@ -18,20 +18,15 @@
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
constexpr u32 TempBufferSize = 0x3F00;
|
||||
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
|
||||
|
||||
/**
|
||||
* Decode PCM data. Only s16 or f32 is supported.
|
||||
*
|
||||
* @tparam T - Type to decode. Only s16 and f32 are supported.
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
/// @brief Decode PCM data. Only s16 or f32 is supported.
|
||||
/// @tparam T - Type to decode. Only s16 and f32 are supported.
|
||||
/// @param memory - Core memory for reading samples.
|
||||
/// @param out_buffer - Output mix buffer to receive the samples.
|
||||
/// @param req - Information for how to decode.
|
||||
/// @return Number of samples decoded.
|
||||
template <typename T>
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, const DecodeArg& req) {
|
||||
constexpr s32 min{(std::numeric_limits<s16>::min)()};
|
||||
constexpr s32 max{(std::numeric_limits<s16>::max)()};
|
||||
|
||||
@@ -94,16 +89,12 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return samples_to_decode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode ADPCM data.
|
||||
*
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
/// @brief Decode ADPCM data.
|
||||
/// @param memory - Core memory for reading samples.
|
||||
/// @param out_buffer - Output mix buffer to receive the samples.
|
||||
/// @param req - Information for how to decode.
|
||||
/// @return Number of samples decoded.
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, const DecodeArg& req) {
|
||||
constexpr u32 SamplesPerFrame{14};
|
||||
constexpr u32 NibblesPerFrame{16};
|
||||
|
||||
@@ -115,8 +106,7 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto end{(req.end_offset % SamplesPerFrame) +
|
||||
NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
|
||||
auto end{(req.end_offset % SamplesPerFrame) + NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
|
||||
if (req.end_offset % SamplesPerFrame) {
|
||||
end += 3;
|
||||
} else {
|
||||
@@ -133,52 +123,49 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_read{samples_to_process};
|
||||
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
|
||||
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
|
||||
samples_remaining_in_frame};
|
||||
|
||||
auto samples_to_read = samples_to_process;
|
||||
auto samples_remaining_in_frame = start_pos % SamplesPerFrame;
|
||||
auto position_in_frame = (start_pos / SamplesPerFrame) * NibblesPerFrame + samples_remaining_in_frame;
|
||||
if (samples_remaining_in_frame) {
|
||||
position_in_frame += 2;
|
||||
}
|
||||
|
||||
const auto size{(std::max)((samples_to_process / 8U) * SamplesPerFrame, 8U)};
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(
|
||||
memory, req.buffer + position_in_frame / 2, size);
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(memory, req.buffer + position_in_frame / 2, size);
|
||||
|
||||
auto context{req.adpcm_context};
|
||||
auto header{context->header};
|
||||
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
|
||||
u8 scale{static_cast<u8>(header & 0xFU)};
|
||||
s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
|
||||
s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
|
||||
auto context = req.adpcm_context;
|
||||
auto header = context->header;
|
||||
u8 scale = u8(header & 0xfU);
|
||||
u8 coeff_index = u8((header >> 4U) & 0x7u);
|
||||
s32 coeff0 = req.coefficients[coeff_index * 2 + 0];
|
||||
s32 coeff1 = req.coefficients[coeff_index * 2 + 1];
|
||||
|
||||
auto yn0{context->yn0};
|
||||
auto yn1{context->yn1};
|
||||
|
||||
static constexpr std::array<s32, 16> Steps{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
|
||||
auto yn0 = context->yn0;
|
||||
auto yn1 = context->yn1;
|
||||
auto const get_step = [](u32 index) {
|
||||
// Emulates the following table
|
||||
// 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
|
||||
constexpr u64 steps_table = 0x1234567876543210ull;
|
||||
constexpr u64 steps_sign = 0b1111111100000000ull;
|
||||
auto const r = s32((steps_table >> (index * 4)) & 0xf);
|
||||
return ((steps_sign >> index) & 1) == 0 ? -r : r;
|
||||
};
|
||||
|
||||
const auto decode_sample = [&](const s32 code) -> s16 {
|
||||
auto const decode_sample = [&](const s32 code) -> s16 {
|
||||
const auto xn = code * (1 << scale);
|
||||
const auto prediction = coeff0 * yn0 + coeff1 * yn1;
|
||||
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
|
||||
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
|
||||
yn1 = yn0;
|
||||
yn0 = static_cast<s16>(saturated);
|
||||
return yn0;
|
||||
return yn0 = s16(saturated);
|
||||
};
|
||||
|
||||
u32 read_index{0};
|
||||
u32 write_index{0};
|
||||
|
||||
while (samples_to_read > 0) {
|
||||
u32 read_index = 0;
|
||||
for (u32 write_index = 0; samples_to_read > 0 && write_index < out_buffer.size(); ) {
|
||||
// Are we at a new frame?
|
||||
if ((position_in_frame % NibblesPerFrame) == 0) {
|
||||
header = wavebuffer[read_index++];
|
||||
coeff_index = (header >> 4) & 0xF;
|
||||
scale = header & 0xF;
|
||||
scale = header & 0xFu;
|
||||
coeff_index = (header >> 4) & 0x7u;
|
||||
coeff0 = req.coefficients[coeff_index * 2 + 0];
|
||||
coeff1 = req.coefficients[coeff_index * 2 + 1];
|
||||
position_in_frame += 2;
|
||||
@@ -187,14 +174,12 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
if (samples_to_read >= SamplesPerFrame) {
|
||||
// Can grab all samples until the next header
|
||||
for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
|
||||
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
|
||||
auto code1{Steps[wavebuffer[read_index] & 0xF]};
|
||||
read_index++;
|
||||
|
||||
auto code0 = get_step((wavebuffer[read_index + i] >> 4) & 0xF);
|
||||
auto code1 = get_step(wavebuffer[read_index + i] & 0xF);
|
||||
out_buffer[write_index++] = decode_sample(code0);
|
||||
out_buffer[write_index++] = decode_sample(code1);
|
||||
}
|
||||
|
||||
read_index += SamplesPerFrame / 2;
|
||||
position_in_frame += SamplesPerFrame;
|
||||
samples_to_read -= SamplesPerFrame;
|
||||
continue;
|
||||
@@ -202,15 +187,14 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
}
|
||||
|
||||
// Decode a single sample
|
||||
auto code{wavebuffer[read_index]};
|
||||
auto code = wavebuffer[read_index];
|
||||
if (position_in_frame & 1) {
|
||||
code &= 0xF;
|
||||
read_index++;
|
||||
} else {
|
||||
code >>= 4;
|
||||
}
|
||||
|
||||
out_buffer[write_index++] = decode_sample(Steps[code]);
|
||||
out_buffer[write_index++] = decode_sample(get_step(code));
|
||||
|
||||
position_in_frame++;
|
||||
samples_to_read--;
|
||||
@@ -219,27 +203,21 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
context->header = header;
|
||||
context->yn0 = yn0;
|
||||
context->yn1 = yn1;
|
||||
|
||||
return samples_to_process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode implementation.
|
||||
* Decode wavebuffers according to the given args.
|
||||
*
|
||||
* @param memory - Core memory to read data from.
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
/// @brief Decode implementation.
|
||||
/// Decode wavebuffers according to the given args.
|
||||
///
|
||||
/// @param memory - Core memory to read data from.
|
||||
/// @param args - The wavebuffer data, and information for how to decode it.
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
|
||||
static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
|
||||
auto& played_samples, auto& consumed) -> void {
|
||||
constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, auto& played_samples, auto& consumed) -> void {
|
||||
voice_state.wave_buffer_valid[index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_samples = 0;
|
||||
}
|
||||
|
||||
index = (index + 1) % MaxWaveBuffers;
|
||||
consumed++;
|
||||
};
|
||||
@@ -255,8 +233,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
return;
|
||||
}
|
||||
|
||||
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
|
||||
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
|
||||
// 0 -> 4, 1 -> 8, 2 -> 4
|
||||
auto pitch = u32((0x040804ul >> (u32(args.src_quality) * 8)) & 0xfful);
|
||||
if (u32(pitch + size_required.to_int_floor()) > TempBufferSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,7 +251,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
bool is_buffer_starved{false};
|
||||
u32 offset{voice_state.offset};
|
||||
|
||||
auto output_buffer{args.output};
|
||||
auto output_buffer = args.output;
|
||||
std::array<s16, TempBufferSize> temp_buffer{};
|
||||
|
||||
while (remaining_sample_count > 0) {
|
||||
@@ -294,8 +273,8 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
if (wavebuffer_index >= MaxWaveBuffers) {
|
||||
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
|
||||
wavebuffer_index = 0;
|
||||
voice_state.wave_buffer_valid.fill(false);
|
||||
wavebuffers_consumed = MaxWaveBuffers;
|
||||
voice_state.wave_buffer_valid.fill(false);
|
||||
}
|
||||
|
||||
if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
|
||||
@@ -303,12 +282,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
break;
|
||||
}
|
||||
|
||||
auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
|
||||
|
||||
if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
|
||||
wavebuffer.context != 0) {
|
||||
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
|
||||
wavebuffer.context_size);
|
||||
auto& wavebuffer = args.wave_buffers[wavebuffer_index];
|
||||
if (offset == 0 && args.sample_format == SampleFormat::Adpcm && wavebuffer.context != 0) {
|
||||
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, wavebuffer.context_size);
|
||||
}
|
||||
|
||||
auto start_offset{wavebuffer.start_offset};
|
||||
@@ -351,9 +327,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
case SampleFormat::Adpcm: {
|
||||
decode_arg.adpcm_context = &voice_state.adpcm_context;
|
||||
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
|
||||
samples_decoded = DecodeAdpcm(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
samples_decoded = DecodeAdpcm( memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, decode_arg);
|
||||
} break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,21 +8,13 @@
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
|
||||
const Common::FixedPoint<49, 15>& sample_rate_ratio,
|
||||
Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
|
||||
if (sample_rate_ratio == 1.0f) {
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[i];
|
||||
}
|
||||
} else {
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[read_index + (fraction >= 0.5f)];
|
||||
fraction += sample_rate_ratio;
|
||||
read_index += static_cast<u32>(fraction.to_int_floor());
|
||||
fraction.clear_int();
|
||||
}
|
||||
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, const Common::FixedPoint<49, 15>& sample_rate_ratio, Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
output[i] = input[read_index + (fraction >= 0.5f)];
|
||||
fraction += sample_rate_ratio;
|
||||
read_index += u32(fraction.to_int_floor());
|
||||
fraction.clear_int();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +290,7 @@ static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> in
|
||||
auto lut{get_lut()};
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
const auto lut_index{(fraction.get_frac() >> 8) * 4};
|
||||
const auto lut_index = ((fraction.get_frac() >> 8) << 2) & 511;
|
||||
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
|
||||
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
|
||||
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
|
||||
@@ -845,7 +840,7 @@ static void ResampleHighQuality(std::span<s32> output, std::span<const s16> inpu
|
||||
auto lut{get_lut()};
|
||||
u32 read_index{0};
|
||||
for (u32 i = 0; i < samples_to_write; i++) {
|
||||
const auto lut_index{(fraction.get_frac() >> 8) * 8};
|
||||
const auto lut_index = ((fraction.get_frac() >> 8) << 3) & 1023;
|
||||
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
|
||||
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
|
||||
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
|
||||
|
||||
@@ -121,6 +121,8 @@ add_library(
|
||||
swap.h
|
||||
thread.cpp
|
||||
thread.h
|
||||
adpf.cpp
|
||||
adpf.h
|
||||
thread_queue_list.h
|
||||
thread_worker.h
|
||||
threadsafe_queue.h
|
||||
|
||||
@@ -336,22 +336,22 @@ ALLOC_MEMBER(VaType)::Allocate(VaType size) {
|
||||
current_linear_alloc_end = alloc_start + size;
|
||||
} else { // If linear allocation overflows the AS then find a gap
|
||||
if (this->blocks.size() <= 2) {
|
||||
alloc_start = virt_start;
|
||||
ASSERT_MSG(false, "Unexpected allocator state!");
|
||||
}
|
||||
|
||||
auto search_predecessor{std::next(this->blocks.begin())};
|
||||
auto search_successor{std::next(search_predecessor)};
|
||||
|
||||
while (search_successor != this->blocks.end() &&
|
||||
(search_successor->virt - search_predecessor->virt < size ||
|
||||
search_predecessor->Mapped())) {
|
||||
search_predecessor = search_successor++;
|
||||
}
|
||||
|
||||
if (search_successor != this->blocks.end()) {
|
||||
alloc_start = search_predecessor->virt;
|
||||
} else {
|
||||
auto search_predecessor{std::next(this->blocks.begin())};
|
||||
auto search_successor{std::next(search_predecessor)};
|
||||
|
||||
while (search_successor != this->blocks.end() &&
|
||||
(search_successor->virt - search_predecessor->virt < size ||
|
||||
search_predecessor->Mapped())) {
|
||||
search_predecessor = search_successor++;
|
||||
}
|
||||
|
||||
if (search_successor != this->blocks.end()) {
|
||||
alloc_start = search_predecessor->virt;
|
||||
} else {
|
||||
return {}; // AS is full
|
||||
}
|
||||
return {}; // AS is full
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,10 +364,6 @@ ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
|
||||
}
|
||||
|
||||
ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
|
||||
const VaType virt_end = virt + size;
|
||||
this->Unmap(virt, size);
|
||||
if (virt_end >= virt && virt_end == current_linear_alloc_end) {
|
||||
current_linear_alloc_end = virt < virt_start ? virt_start : virt;
|
||||
}
|
||||
}
|
||||
} // namespace Common
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/adpf.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::chrono::nanoseconds DEFAULT_TARGET = std::chrono::nanoseconds{16'666'667};
|
||||
|
||||
struct AHintManager;
|
||||
struct AHintSession;
|
||||
|
||||
using PFN_GetManager = AHintManager* (*)();
|
||||
using PFN_CreateSession = AHintSession* (*)(AHintManager*, const s32*, size_t, s64);
|
||||
using PFN_CloseSession = void (*)(AHintSession*);
|
||||
using PFN_UpdateTarget = int (*)(AHintSession*, s64);
|
||||
using PFN_ReportActual = int (*)(AHintSession*, s64);
|
||||
using PFN_SetThreads = int (*)(AHintSession*, const pid_t*, size_t);
|
||||
using PFN_SetPowerEfficiency = int (*)(AHintSession*, bool);
|
||||
|
||||
struct Api {
|
||||
PFN_GetManager get_manager = nullptr;
|
||||
PFN_CreateSession create_session = nullptr;
|
||||
PFN_CloseSession close_session = nullptr;
|
||||
PFN_UpdateTarget update_target = nullptr;
|
||||
PFN_ReportActual report_actual = nullptr;
|
||||
PFN_SetThreads set_threads = nullptr;
|
||||
PFN_SetPowerEfficiency set_power_efficiency = nullptr;
|
||||
AHintManager* manager = nullptr;
|
||||
bool usable = false;
|
||||
};
|
||||
|
||||
const Api& Resolve() {
|
||||
static const Api api = [] {
|
||||
Api resolved;
|
||||
void* library = dlopen("libandroid.so", RTLD_NOW);
|
||||
if (library == nullptr) {
|
||||
LOG_INFO(Common, "libandroid.so unavailable, ADPF is disabled");
|
||||
return resolved;
|
||||
}
|
||||
const auto load = [library](const char* name) { return dlsym(library, name); };
|
||||
|
||||
resolved.get_manager = reinterpret_cast<PFN_GetManager>(load("APerformanceHint_getManager"));
|
||||
resolved.create_session =
|
||||
reinterpret_cast<PFN_CreateSession>(load("APerformanceHint_createSession"));
|
||||
resolved.close_session =
|
||||
reinterpret_cast<PFN_CloseSession>(load("APerformanceHint_closeSession"));
|
||||
resolved.update_target =
|
||||
reinterpret_cast<PFN_UpdateTarget>(load("APerformanceHint_updateTargetWorkDuration"));
|
||||
resolved.report_actual =
|
||||
reinterpret_cast<PFN_ReportActual>(load("APerformanceHint_reportActualWorkDuration"));
|
||||
resolved.set_threads =
|
||||
reinterpret_cast<PFN_SetThreads>(load("APerformanceHint_setThreads"));
|
||||
resolved.set_power_efficiency = reinterpret_cast<PFN_SetPowerEfficiency>(
|
||||
load("APerformanceHint_setPreferPowerEfficiency"));
|
||||
|
||||
if (resolved.get_manager == nullptr || resolved.create_session == nullptr ||
|
||||
resolved.close_session == nullptr || resolved.update_target == nullptr ||
|
||||
resolved.report_actual == nullptr) {
|
||||
LOG_INFO(Common, "Performance hint API not exported, ADPF is disabled");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
resolved.manager = resolved.get_manager();
|
||||
if (resolved.manager == nullptr) {
|
||||
LOG_INFO(Common, "Device does not provide a performance hint manager");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
resolved.usable = true;
|
||||
LOG_INFO(Common, "ADPF available, setThreads {}, power efficiency {}",
|
||||
resolved.set_threads != nullptr ? "yes" : "no",
|
||||
resolved.set_power_efficiency != nullptr ? "yes" : "no");
|
||||
return resolved;
|
||||
}();
|
||||
return api;
|
||||
}
|
||||
|
||||
struct SessionState {
|
||||
AHintSession* handle = nullptr;
|
||||
std::vector<pid_t> threads;
|
||||
bool unsupported = false;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::array<SessionState, 2> g_sessions;
|
||||
|
||||
constexpr s64 MAX_REPORTED_TARGETS = 4;
|
||||
|
||||
std::atomic<s64> g_target_ns{DEFAULT_TARGET.count()};
|
||||
thread_local std::chrono::steady_clock::time_point t_last_frame{};
|
||||
|
||||
SessionState& StateOf(Session session) {
|
||||
return g_sessions[static_cast<size_t>(session)];
|
||||
}
|
||||
|
||||
bool IsBackgroundUsable(const Api& api) {
|
||||
return api.set_power_efficiency != nullptr;
|
||||
}
|
||||
|
||||
void CloseLocked(SessionState& state) {
|
||||
if (state.handle != nullptr) {
|
||||
Resolve().close_session(state.handle);
|
||||
state.handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AHintSession* CreateSessionFor(Session session, const std::vector<pid_t>& threads) {
|
||||
const Api& api = Resolve();
|
||||
const s64 target = session == Session::Render ? g_target_ns.load(std::memory_order_relaxed) : 0;
|
||||
|
||||
std::vector<s32> ids;
|
||||
ids.reserve(threads.size());
|
||||
for (const pid_t tid : threads) {
|
||||
ids.push_back(static_cast<s32>(tid));
|
||||
}
|
||||
|
||||
AHintSession* handle = api.create_session(api.manager, ids.data(), ids.size(), target);
|
||||
if (handle == nullptr && target == 0) {
|
||||
handle = api.create_session(api.manager, ids.data(), ids.size(), DEFAULT_TARGET.count());
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (session == Session::Background && api.set_power_efficiency != nullptr) {
|
||||
api.set_power_efficiency(handle, true);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool SyncLocked(Session session, SessionState& state) {
|
||||
if (state.threads.empty()) {
|
||||
CloseLocked(state);
|
||||
return false;
|
||||
}
|
||||
|
||||
const Api& api = Resolve();
|
||||
if (state.handle != nullptr && api.set_threads != nullptr) {
|
||||
std::vector<pid_t> ids = state.threads;
|
||||
if (api.set_threads(state.handle, ids.data(), ids.size()) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
AHintSession* const replacement = CreateSessionFor(session, state.threads);
|
||||
if (replacement == nullptr) {
|
||||
if (state.handle == nullptr) {
|
||||
state.unsupported = true;
|
||||
}
|
||||
LOG_WARNING(Common, "Could not open a performance hint session for {} threads, falling back",
|
||||
state.threads.size());
|
||||
return false;
|
||||
}
|
||||
CloseLocked(state);
|
||||
state.handle = replacement;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
bool IsSessionSupported(Session session) {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable) {
|
||||
return false;
|
||||
}
|
||||
if (session == Session::Background && !IsBackgroundUsable(api)) {
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
return !StateOf(session).unsupported;
|
||||
}
|
||||
|
||||
bool AddCurrentThread(Session session) {
|
||||
if (!IsSessionSupported(session)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pid_t tid = gettid();
|
||||
std::scoped_lock lock{g_mutex};
|
||||
|
||||
for (size_t i = 0; i < g_sessions.size(); ++i) {
|
||||
SessionState& state = g_sessions[i];
|
||||
if (static_cast<size_t>(session) == i) {
|
||||
continue;
|
||||
}
|
||||
const auto it = std::find(state.threads.begin(), state.threads.end(), tid);
|
||||
if (it != state.threads.end()) {
|
||||
state.threads.erase(it);
|
||||
SyncLocked(static_cast<Session>(i), state);
|
||||
}
|
||||
}
|
||||
|
||||
SessionState& state = StateOf(session);
|
||||
const bool added =
|
||||
std::find(state.threads.begin(), state.threads.end(), tid) == state.threads.end();
|
||||
if (added) {
|
||||
state.threads.push_back(tid);
|
||||
}
|
||||
if (!SyncLocked(session, state)) {
|
||||
if (added) {
|
||||
std::erase(state.threads, tid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveCurrentThread() {
|
||||
if (!Resolve().usable) {
|
||||
return;
|
||||
}
|
||||
const pid_t tid = gettid();
|
||||
std::scoped_lock lock{g_mutex};
|
||||
for (size_t i = 0; i < g_sessions.size(); ++i) {
|
||||
SessionState& state = g_sessions[i];
|
||||
if (std::erase(state.threads, tid) != 0) {
|
||||
SyncLocked(static_cast<Session>(i), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds target) {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable || target.count() <= 0) {
|
||||
return;
|
||||
}
|
||||
if (g_target_ns.exchange(target.count(), std::memory_order_relaxed) == target.count()) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SessionState& state = StateOf(Session::Render);
|
||||
if (state.handle != nullptr) {
|
||||
api.update_target(state.handle, target.count());
|
||||
}
|
||||
}
|
||||
|
||||
void ReportFrameInterval() {
|
||||
const Api& api = Resolve();
|
||||
if (!api.usable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto previous = t_last_frame;
|
||||
t_last_frame = now;
|
||||
if (previous.time_since_epoch().count() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
s64 actual = std::chrono::duration_cast<std::chrono::nanoseconds>(now - previous).count();
|
||||
if (actual <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const s64 ceiling = g_target_ns.load(std::memory_order_relaxed) * MAX_REPORTED_TARGETS;
|
||||
actual = (std::min)(actual, ceiling);
|
||||
|
||||
std::scoped_lock lock{g_mutex};
|
||||
SessionState& state = StateOf(Session::Render);
|
||||
if (state.handle != nullptr) {
|
||||
api.report_actual(state.handle, actual);
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (!Resolve().usable) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{g_mutex};
|
||||
for (SessionState& state : g_sessions) {
|
||||
CloseLocked(state);
|
||||
state.threads.clear();
|
||||
state.unsupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common::ADPF
|
||||
|
||||
#else
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
bool IsSessionSupported(Session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AddCurrentThread(Session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RemoveCurrentThread() {}
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds) {}
|
||||
|
||||
void ReportFrameInterval() {}
|
||||
|
||||
void Shutdown() {}
|
||||
|
||||
} // namespace Common::ADPF
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Common::ADPF {
|
||||
|
||||
enum class Session {
|
||||
Render,
|
||||
Background,
|
||||
};
|
||||
|
||||
bool IsSessionSupported(Session session);
|
||||
|
||||
bool AddCurrentThread(Session session);
|
||||
void RemoveCurrentThread();
|
||||
|
||||
void SetTargetWorkDuration(std::chrono::nanoseconds target);
|
||||
|
||||
void ReportFrameInterval();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
} // namespace Common::ADPF
|
||||
+1
-62
@@ -4,8 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -17,10 +15,8 @@
|
||||
#include "common/logging.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <share.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -99,65 +95,10 @@ namespace {
|
||||
case FileShareFlag::ShareWriteOnly:
|
||||
return _SH_DENYRD;
|
||||
case FileShareFlag::ShareReadWrite:
|
||||
case FileShareFlag::ShareReadWriteDelete:
|
||||
return _SH_DENYNO;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::FILE* OpenWithWindowsShareDelete(const fs::path& path, FileAccessMode mode,
|
||||
FileType type) {
|
||||
DWORD desired_access{};
|
||||
DWORD creation_disposition{OPEN_EXISTING};
|
||||
int open_flags = type == FileType::BinaryFile ? _O_BINARY : _O_TEXT;
|
||||
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
desired_access = GENERIC_READ;
|
||||
open_flags |= _O_RDONLY;
|
||||
break;
|
||||
case FileAccessMode::Write:
|
||||
desired_access = GENERIC_WRITE;
|
||||
creation_disposition = CREATE_ALWAYS;
|
||||
open_flags |= _O_WRONLY;
|
||||
break;
|
||||
case FileAccessMode::Append:
|
||||
desired_access = GENERIC_WRITE;
|
||||
creation_disposition = OPEN_ALWAYS;
|
||||
open_flags |= _O_WRONLY | _O_APPEND;
|
||||
break;
|
||||
case FileAccessMode::ReadWrite:
|
||||
desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
open_flags |= _O_RDWR;
|
||||
break;
|
||||
case FileAccessMode::ReadAppend:
|
||||
desired_access = GENERIC_READ | GENERIC_WRITE;
|
||||
creation_disposition = OPEN_ALWAYS;
|
||||
open_flags |= _O_RDWR | _O_APPEND;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto handle =
|
||||
CreateFileW(path.c_str(), desired_access,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
errno = EACCES;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto fd = _open_osfhandle(reinterpret_cast<intptr_t>(handle), open_flags);
|
||||
if (fd == -1) {
|
||||
CloseHandle(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* const file = _wfdopen(fd, AccessModeToWStr(mode, type));
|
||||
if (file == nullptr) {
|
||||
_close(fd);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
@@ -313,9 +254,7 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (flag == FileShareFlag::ShareReadWriteDelete) {
|
||||
file = OpenWithWindowsShareDelete(path, mode, type);
|
||||
} else if (flag != FileShareFlag::ShareNone) {
|
||||
if (flag != FileShareFlag::ShareNone) {
|
||||
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
|
||||
} else {
|
||||
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
|
||||
|
||||
@@ -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: Copyright 2021 yuzu Emulator Project
|
||||
@@ -49,11 +49,10 @@ enum class FileType {
|
||||
};
|
||||
|
||||
enum class FileShareFlag {
|
||||
ShareNone, // Provides exclusive access to the file.
|
||||
ShareReadOnly, // Provides read only shared access to the file.
|
||||
ShareWriteOnly, // Provides write only shared access to the file.
|
||||
ShareReadWrite, // Provides read and write shared access to the file.
|
||||
ShareReadWriteDelete, // Provides read, write, and delete shared access to the file.
|
||||
ShareNone, // Provides exclusive access to the file.
|
||||
ShareReadOnly, // Provides read only shared access to the file.
|
||||
ShareWriteOnly, // Provides write only shared access to the file.
|
||||
ShareReadWrite, // Provides read and write shared access to the file.
|
||||
};
|
||||
|
||||
enum class DirEntryFilter {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+340
-6
@@ -51,14 +51,45 @@
|
||||
|
||||
#endif // ^^^ POSIX ^^^
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/free_region_manager.h"
|
||||
#include "common/host_memory.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/memory_detect.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <dlfcn.h>
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
namespace {
|
||||
|
||||
struct NativeHandle {
|
||||
int version;
|
||||
int numFds;
|
||||
int numInts;
|
||||
int data[1];
|
||||
};
|
||||
|
||||
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
|
||||
|
||||
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
|
||||
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
|
||||
if (lib == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
|
||||
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__) && __ANDROID_API__ < 30
|
||||
#include <sys/syscall.h>
|
||||
@@ -75,6 +106,12 @@ namespace Common {
|
||||
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
|
||||
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
|
||||
|
||||
static std::atomic<u64> committed_backing_size{};
|
||||
|
||||
u64 GetCommittedBackingSize() noexcept {
|
||||
return committed_backing_size.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Manually imported for MinGW compatibility
|
||||
@@ -123,7 +160,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
|
||||
|
||||
class HostMemory::Impl {
|
||||
public:
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_)
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
|
||||
: backing_size{backing_size_}
|
||||
, virtual_size{virtual_size_}
|
||||
, process{GetCurrentProcess()}
|
||||
@@ -229,6 +266,10 @@ public:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool IsBackingShared() const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t backing_size; ///< Size of the backing memory in bytes
|
||||
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
||||
|
||||
@@ -501,9 +542,10 @@ static int shm_open_anon(int flags, mode_t mode) {
|
||||
|
||||
class HostMemory::Impl {
|
||||
public:
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_)
|
||||
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
|
||||
: backing_size{backing_size_}
|
||||
, virtual_size{virtual_size_}
|
||||
, preferred_offset{preferred_offset_}
|
||||
{}
|
||||
|
||||
bool Init() {
|
||||
@@ -543,10 +585,15 @@ public:
|
||||
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
|
||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
||||
if (fd > 0) {
|
||||
fd = -1;
|
||||
close(fd);
|
||||
}
|
||||
fd = -1;
|
||||
} else {
|
||||
#ifdef __ANDROID__
|
||||
if (InitAhbBacking()) {
|
||||
return InitVirtual();
|
||||
}
|
||||
#endif
|
||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
||||
}
|
||||
if (backing_base == MAP_FAILED) {
|
||||
@@ -554,7 +601,10 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Virtual memory initialization
|
||||
return InitVirtual();
|
||||
}
|
||||
|
||||
bool InitVirtual() {
|
||||
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
|
||||
if (virtual_base == MAP_FAILED) {
|
||||
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
||||
@@ -567,6 +617,222 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
|
||||
return AHardwareBuffer_Desc{
|
||||
.width = static_cast<u32>(len),
|
||||
.height = 1,
|
||||
.layers = 1,
|
||||
.format = AHARDWAREBUFFER_FORMAT_BLOB,
|
||||
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
|
||||
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
|
||||
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
|
||||
.stride = 0,
|
||||
.rfu0 = 0,
|
||||
.rfu1 = 0,
|
||||
};
|
||||
}
|
||||
|
||||
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
|
||||
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
|
||||
AHardwareBuffer* buffer{};
|
||||
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const NativeHandle* const handle = get_native_handle(buffer);
|
||||
if (handle == nullptr || handle->numFds < 1) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
return false;
|
||||
}
|
||||
const int probe_fd = handle->data[0];
|
||||
bool ok = true;
|
||||
const auto try_map = [&](int prot, off_t offset, const char* what) {
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
|
||||
if (ptr == MAP_FAILED) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
munmap(ptr, PageAlignment);
|
||||
};
|
||||
try_map(PROT_READ | PROT_WRITE, 0, "shared mappings");
|
||||
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment), "mappings at an offset");
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
try_map(PROT_READ | PROT_EXEC, 0, "executable mappings");
|
||||
#endif
|
||||
AHardwareBuffer_release(buffer);
|
||||
return ok;
|
||||
}
|
||||
|
||||
size_t ComputeAhbBudget(size_t window_size) const {
|
||||
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
|
||||
if (total_physical == 0) {
|
||||
return 0;
|
||||
}
|
||||
constexpr u64 MinimumTotalPhysical = 7ULL << 30;
|
||||
if (total_physical < MinimumTotalPhysical) {
|
||||
return 0;
|
||||
}
|
||||
const u64 max_map_count = Common::GetMaxMapCount();
|
||||
constexpr u64 ReservedMaps = 24576;
|
||||
if (max_map_count == 0 || max_map_count <= ReservedMaps) {
|
||||
return 0;
|
||||
}
|
||||
u64 budget = total_physical / 6;
|
||||
budget = (std::min)(budget, (max_map_count - ReservedMaps) * PageAlignment);
|
||||
const u64 available = Common::GetAvailablePhysicalMemory();
|
||||
if (available != 0) {
|
||||
constexpr u64 Headroom = 2ULL << 30;
|
||||
budget = (std::min)(budget, available > Headroom ? available - Headroom : 0);
|
||||
}
|
||||
budget = (std::min)(budget, static_cast<u64>(backing_size));
|
||||
budget = Common::AlignDown(budget, window_size);
|
||||
constexpr u64 MinimumBudget = 256ULL << 20;
|
||||
if (budget < MinimumBudget) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<size_t>(budget);
|
||||
}
|
||||
|
||||
bool InitAhbBacking() {
|
||||
if (!Settings::values.use_unified_memory.GetValue()) {
|
||||
return false;
|
||||
}
|
||||
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
|
||||
ResolveGetNativeHandle();
|
||||
if (get_native_handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
constexpr size_t window_size = 512ULL << 20;
|
||||
const AHardwareBuffer_Desc window_desc = MakeBlobDesc(window_size);
|
||||
if (AHardwareBuffer_isSupported(&window_desc) == 0) {
|
||||
return false;
|
||||
}
|
||||
const size_t budget = ComputeAhbBudget(window_size);
|
||||
if (budget == 0) {
|
||||
return false;
|
||||
}
|
||||
if (!ProbeAhbBacking(get_native_handle)) {
|
||||
return false;
|
||||
}
|
||||
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
|
||||
const size_t region_size = (std::min)(budget, aligned_backing);
|
||||
const size_t region_base = Common::AlignDown(
|
||||
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
|
||||
const size_t num_windows = region_size / window_size;
|
||||
|
||||
std::vector<AHardwareBuffer*> buffers;
|
||||
std::vector<int> buffer_fds;
|
||||
const auto cleanup = [&] {
|
||||
for (AHardwareBuffer* buffer : buffers) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
}
|
||||
buffers.clear();
|
||||
buffer_fds.clear();
|
||||
};
|
||||
for (size_t i = 0; i < num_windows; ++i) {
|
||||
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
|
||||
AHardwareBuffer* buffer{};
|
||||
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
buffers.push_back(buffer);
|
||||
const NativeHandle* const handle = get_native_handle(buffer);
|
||||
if (handle == nullptr || handle->numFds < 1) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
const int buffer_fd = handle->data[0];
|
||||
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
|
||||
if (buffer_len < static_cast<off_t>(window_size)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
buffer_fds.push_back(buffer_fd);
|
||||
}
|
||||
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
|
||||
if (base == MAP_FAILED) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
|
||||
off_t map_offset) {
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
|
||||
map_offset) == MAP_FAILED) {
|
||||
munmap(base, backing_size);
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!map_over_reservation(0, region_base, fd, 0)) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < num_windows; ++i) {
|
||||
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
|
||||
0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const size_t tail_offset = region_base + region_size;
|
||||
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
|
||||
static_cast<off_t>(tail_offset))) {
|
||||
return false;
|
||||
}
|
||||
backing_base = base;
|
||||
ahb_windows = std::move(buffers);
|
||||
ahb_fds = std::move(buffer_fds);
|
||||
ahb_window_size = window_size;
|
||||
ahb_base = region_base;
|
||||
ahb_bytes = region_size;
|
||||
committed_backing_size.store(region_size, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
|
||||
while (length > 0) {
|
||||
int map_fd = fd;
|
||||
off_t map_offset = static_cast<off_t>(host_offset);
|
||||
size_t chunk = length;
|
||||
if (host_offset < ahb_base) {
|
||||
chunk = (std::min)(chunk, ahb_base - host_offset);
|
||||
} else if (host_offset < ahb_base + ahb_bytes) {
|
||||
const size_t relative = host_offset - ahb_base;
|
||||
const size_t window = relative / ahb_window_size;
|
||||
const size_t local = relative % ahb_window_size;
|
||||
map_fd = ahb_fds[window];
|
||||
map_offset = static_cast<off_t>(local);
|
||||
chunk = (std::min)(chunk, ahb_window_size - local);
|
||||
}
|
||||
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
|
||||
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
|
||||
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
|
||||
virtual_offset += chunk;
|
||||
host_offset += chunk;
|
||||
length -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
|
||||
return ahb_windows;
|
||||
}
|
||||
|
||||
size_t AhbWindowSize() const noexcept {
|
||||
return ahb_bytes != 0 ? ahb_window_size : 0;
|
||||
}
|
||||
|
||||
size_t AhbBase() const noexcept {
|
||||
return ahb_base;
|
||||
}
|
||||
#endif
|
||||
|
||||
~Impl() {
|
||||
Release();
|
||||
}
|
||||
@@ -587,6 +853,12 @@ public:
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
if (True(perms & MemoryPermission::Execute))
|
||||
prot_flags |= PROT_EXEC;
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
if (ahb_bytes != 0) {
|
||||
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
|
||||
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
|
||||
@@ -632,8 +904,18 @@ public:
|
||||
virtual_base = nullptr;
|
||||
}
|
||||
|
||||
bool IsBackingShared() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
if (ahb_bytes != 0) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return fd >= 0;
|
||||
}
|
||||
|
||||
const size_t backing_size; ///< Size of the backing memory in bytes
|
||||
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
|
||||
const size_t preferred_offset;
|
||||
|
||||
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
||||
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
|
||||
@@ -656,6 +938,18 @@ private:
|
||||
int ret = close(fd);
|
||||
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
for (AHardwareBuffer* buffer : ahb_windows) {
|
||||
AHardwareBuffer_release(buffer);
|
||||
}
|
||||
ahb_windows.clear();
|
||||
ahb_fds.clear();
|
||||
if (ahb_bytes != 0) {
|
||||
committed_backing_size.store(0, std::memory_order_relaxed);
|
||||
ahb_bytes = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AdjustMap(size_t* virtual_offset, size_t* length) {
|
||||
@@ -681,11 +975,19 @@ private:
|
||||
|
||||
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
|
||||
FreeRegionManager free_manager{};
|
||||
|
||||
#ifdef __ANDROID__
|
||||
std::vector<AHardwareBuffer*> ahb_windows;
|
||||
std::vector<int> ahb_fds;
|
||||
size_t ahb_window_size{};
|
||||
size_t ahb_base{};
|
||||
size_t ahb_bytes{};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // ^^^ POSIX ^^^
|
||||
|
||||
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
||||
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
|
||||
: backing_size(backing_size_)
|
||||
, virtual_size(virtual_size_)
|
||||
{
|
||||
@@ -697,7 +999,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
||||
#else
|
||||
// Try to allocate a fastmem arena.
|
||||
// The implementation will fail with std::bad_alloc on errors.
|
||||
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
|
||||
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
|
||||
if (impl->Init()) {
|
||||
backing_base = impl->backing_base;
|
||||
virtual_base = impl->virtual_base;
|
||||
@@ -767,6 +1069,38 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
|
||||
std::memset(backing_base + physical_offset, fill_value, length);
|
||||
}
|
||||
|
||||
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbWindowSize() : 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool HostMemory::IsBackingShared() const noexcept {
|
||||
#if defined(__OPENORBIS__) || defined(__managarm__)
|
||||
return false;
|
||||
#else
|
||||
return impl && impl->IsBackingShared();
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
|
||||
#ifdef __ANDROID__
|
||||
return impl ? impl->AhbBase() : 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void HostMemory::EnableDirectMappedAddress() {
|
||||
#if !(defined(__OPENORBIS__) || defined(__managarm__))
|
||||
if (impl) {
|
||||
|
||||
@@ -8,12 +8,17 @@
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/virtual_buffer.h"
|
||||
|
||||
struct AHardwareBuffer;
|
||||
|
||||
namespace Common {
|
||||
|
||||
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
|
||||
|
||||
enum class MemoryPermission : u32 {
|
||||
Read = 1 << 0,
|
||||
Write = 1 << 1,
|
||||
@@ -28,7 +33,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
|
||||
*/
|
||||
class HostMemory {
|
||||
public:
|
||||
explicit HostMemory(size_t backing_size_, size_t virtual_size_);
|
||||
explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
|
||||
~HostMemory();
|
||||
|
||||
/**
|
||||
@@ -62,6 +67,18 @@ public:
|
||||
return backing_base;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t BackingSize() const noexcept {
|
||||
return backing_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
|
||||
|
||||
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
|
||||
|
||||
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
|
||||
|
||||
[[nodiscard]] bool IsBackingShared() const noexcept;
|
||||
|
||||
[[nodiscard]] u8* VirtualBasePointer() noexcept {
|
||||
return virtual_base;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/memory_detect.h"
|
||||
|
||||
namespace Common {
|
||||
@@ -69,4 +73,55 @@ const MemoryInfo& GetMemInfo() {
|
||||
return mem_info;
|
||||
}
|
||||
|
||||
u64 GetAvailablePhysicalMemory() {
|
||||
#ifdef _WIN32
|
||||
MEMORYSTATUSEX memorystatus;
|
||||
memorystatus.dwLength = sizeof(memorystatus);
|
||||
if (GlobalMemoryStatusEx(&memorystatus)) {
|
||||
return memorystatus.ullAvailPhys;
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__linux__)
|
||||
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
|
||||
char line[256];
|
||||
u64 available = 0;
|
||||
while (std::fgets(line, sizeof(line), file) != nullptr) {
|
||||
if (std::strncmp(line, "MemAvailable:", 13) == 0) {
|
||||
available = std::strtoull(line + 13, nullptr, 10) * 1024ULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::fclose(file);
|
||||
if (available != 0) {
|
||||
return available;
|
||||
}
|
||||
}
|
||||
struct sysinfo info;
|
||||
if (sysinfo(&info) == 0) {
|
||||
const u64 unit = info.mem_unit != 0 ? info.mem_unit : 1ULL;
|
||||
return (static_cast<u64>(info.freeram) + static_cast<u64>(info.bufferram)) * unit;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 GetMaxMapCount() {
|
||||
#ifdef __linux__
|
||||
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
|
||||
char line[32];
|
||||
u64 count = 0;
|
||||
if (std::fgets(line, sizeof(line), file) != nullptr) {
|
||||
count = std::strtoull(line, nullptr, 10);
|
||||
}
|
||||
std::fclose(file);
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -18,4 +18,8 @@ struct MemoryInfo {
|
||||
*/
|
||||
[[nodiscard]] const MemoryInfo& GetMemInfo();
|
||||
|
||||
[[nodiscard]] u64 GetAvailablePhysicalMemory();
|
||||
|
||||
[[nodiscard]] u64 GetMaxMapCount();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+14
-22
@@ -260,10 +260,10 @@ struct Values {
|
||||
Category::Cpu};
|
||||
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
|
||||
"cpu_accuracy", Category::Cpu};
|
||||
SwitchableSetting<CpuClock> fast_cpu_time{linkage,
|
||||
CpuClock::Off,
|
||||
SwitchableSetting<CpuClock> cpu_clock{linkage,
|
||||
CpuClock::Normal,
|
||||
"fast_cpu_time",
|
||||
Category::Cpu,
|
||||
Category::System,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
@@ -540,13 +540,13 @@ struct Values {
|
||||
#endif
|
||||
|
||||
// Renderer Hacks //
|
||||
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
||||
GpuOverclock::Medium,
|
||||
"fast_gpu_time",
|
||||
Category::RendererHacks,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
SwitchableSetting<GpuClock> gpu_clock{linkage,
|
||||
GpuClock::Boost,
|
||||
"fast_gpu_time",
|
||||
Category::System,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
|
||||
SwitchableSetting<bool> skip_cpu_inner_invalidation{linkage,
|
||||
false,
|
||||
@@ -587,6 +587,9 @@ struct Values {
|
||||
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
|
||||
GpuUnswizzleSize::Large,
|
||||
"gpu_unswizzle_texture_size",
|
||||
@@ -702,15 +705,7 @@ struct Values {
|
||||
|
||||
// Controls
|
||||
InputSetting<std::array<PlayerInput, 10>> players;
|
||||
Setting<bool> disable_wgi_xinput{
|
||||
linkage, false, "disable_wgi_xinput", Category::Controls, Specialization::Default,
|
||||
// Only read/write disable_wgi_xinput on Windows platforms
|
||||
#ifdef _WIN32
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
};
|
||||
|
||||
Setting<bool> enable_raw_input{
|
||||
linkage, false, "enable_raw_input", Category::Controls, Specialization::Default,
|
||||
// Only read/write enable_raw_input on Windows platforms
|
||||
@@ -840,9 +835,6 @@ struct Values {
|
||||
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
|
||||
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
|
||||
Category::Debugging};
|
||||
Setting<HomebrewNxlinkServerMode> homebrew_nxlink_server_mode{
|
||||
linkage, HomebrewNxlinkServerMode::Disabled, "homebrew_nxlink_server_mode",
|
||||
Category::Debugging};
|
||||
|
||||
SwitchableSetting<u16, true> debug_knobs{linkage,
|
||||
0,
|
||||
|
||||
@@ -140,7 +140,7 @@ ENUM(DmaAccuracy, Default, Unsafe, Safe);
|
||||
ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict);
|
||||
ENUM(CpuBackend, Dynarmic, Nce);
|
||||
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging);
|
||||
ENUM(CpuClock, Off, Boost, Fast)
|
||||
ENUM(CpuClock, Normal, Boost, Overclock)
|
||||
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb);
|
||||
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
|
||||
ENUM(FullscreenMode, Borderless, Exclusive);
|
||||
@@ -152,14 +152,13 @@ ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
|
||||
ENUM(ConsoleMode, Handheld, Docked);
|
||||
ENUM(AppletMode, HLE, LLE);
|
||||
ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
|
||||
ENUM(GpuOverclock, Normal, Medium, High)
|
||||
ENUM(GpuClock, Normal, Boost, Overclock)
|
||||
ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
|
||||
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||
ENUM(HomebrewNxlinkServerMode, Disabled, EdenLog, HostStdout, File)
|
||||
ENUM(GameListMode, TreeView, GridView, CarouselView);
|
||||
ENUM(SpeedMode, Standard, Turbo, Slow);
|
||||
|
||||
|
||||
+508
-36
@@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/error.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/assert.h"
|
||||
@@ -39,6 +41,402 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/resource.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
constexpr int NICE_AUDIO = -16;
|
||||
constexpr int NICE_URGENT_DISPLAY = -8;
|
||||
constexpr int NICE_DISPLAY = -4;
|
||||
constexpr int NICE_DEFAULT = 0;
|
||||
constexpr int NICE_BACKGROUND = 10;
|
||||
|
||||
int LowestAllowedNice() {
|
||||
static const int lowest = [] {
|
||||
rlimit limit{};
|
||||
if (getrlimit(RLIMIT_NICE, &limit) != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (limit.rlim_cur >= 40) {
|
||||
return -20;
|
||||
}
|
||||
return 20 - static_cast<int>(limit.rlim_cur);
|
||||
}();
|
||||
return lowest;
|
||||
}
|
||||
|
||||
int NiceValueForPriority(Common::ThreadPriority priority) {
|
||||
const int wanted = [priority] {
|
||||
switch (priority) {
|
||||
case Common::ThreadPriority::Low: return NICE_BACKGROUND;
|
||||
case Common::ThreadPriority::Normal: return NICE_DEFAULT;
|
||||
case Common::ThreadPriority::High: return NICE_DISPLAY;
|
||||
case Common::ThreadPriority::VeryHigh: return NICE_URGENT_DISPLAY;
|
||||
case Common::ThreadPriority::Critical: return NICE_AUDIO;
|
||||
default: return NICE_DEFAULT;
|
||||
}
|
||||
}();
|
||||
return (std::max)(wanted, (std::min)(NICE_DEFAULT, LowestAllowedNice()));
|
||||
}
|
||||
} // Anonymous namespace
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <sys/utsname.h>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
|
||||
|
||||
// A core counts as a performance core while it is within this much of the fastest one.
|
||||
constexpr s64 ANDROID_PERFORMANCE_CAPACITY_PERCENT = 50;
|
||||
|
||||
constexpr std::chrono::nanoseconds ANDROID_POLICY_POLL_INTERVAL = std::chrono::milliseconds{500};
|
||||
|
||||
enum class CoreGroup {
|
||||
Unrestricted,
|
||||
Performance,
|
||||
Efficiency,
|
||||
};
|
||||
|
||||
struct ThreadPolicy {
|
||||
pid_t tid;
|
||||
CoreGroup group;
|
||||
s32 nice_value;
|
||||
bool has_nice;
|
||||
};
|
||||
|
||||
struct CoreInfo {
|
||||
s64 weight;
|
||||
u64 midr;
|
||||
s32 cpu;
|
||||
};
|
||||
|
||||
struct CpuTopologyState {
|
||||
std::mutex topology_mutex;
|
||||
cpu_set_t allowed{};
|
||||
cpu_set_t performance{};
|
||||
cpu_set_t efficiency{};
|
||||
bool separated = false;
|
||||
bool initialized = false;
|
||||
|
||||
pid_t canary_tid = 0;
|
||||
cpu_set_t canary_mask{};
|
||||
bool canary_valid = false;
|
||||
|
||||
std::atomic<s64> next_poll_ns{0};
|
||||
|
||||
std::mutex policy_mutex;
|
||||
std::vector<ThreadPolicy> policies;
|
||||
};
|
||||
|
||||
CpuTopologyState& State() {
|
||||
static CpuTopologyState* const state = new CpuTopologyState();
|
||||
return *state;
|
||||
}
|
||||
|
||||
struct PolicyRegistration {
|
||||
~PolicyRegistration() {
|
||||
const pid_t tid = gettid();
|
||||
::Common::ADPF::RemoveCurrentThread();
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock lock{state.policy_mutex};
|
||||
std::erase_if(state.policies,
|
||||
[tid](const ThreadPolicy& policy) { return policy.tid == tid; });
|
||||
}
|
||||
};
|
||||
|
||||
thread_local PolicyRegistration t_policy_registration;
|
||||
|
||||
s32 PossibleCpuCount() {
|
||||
std::ifstream file("/sys/devices/system/cpu/possible");
|
||||
std::string list;
|
||||
if (file && std::getline(file, list) && !list.empty()) {
|
||||
s64 highest = -1;
|
||||
const char* cursor = list.c_str();
|
||||
while (*cursor != '\0') {
|
||||
char* end = nullptr;
|
||||
const s64 value = std::strtol(cursor, &end, 10);
|
||||
if (end == cursor) {
|
||||
break;
|
||||
}
|
||||
highest = (std::max)(highest, value);
|
||||
cursor = end;
|
||||
while (*cursor == '-' || *cursor == ',') {
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
if (highest >= 0) {
|
||||
return static_cast<s32>((std::min<s64>)(highest + 1, CPU_SETSIZE));
|
||||
}
|
||||
}
|
||||
const s64 configured = sysconf(_SC_NPROCESSORS_CONF);
|
||||
if (configured > 0) {
|
||||
return static_cast<s32>((std::min<s64>)(configured, CPU_SETSIZE));
|
||||
}
|
||||
return static_cast<s32>((std::min<s64>)(std::thread::hardware_concurrency(), CPU_SETSIZE));
|
||||
}
|
||||
|
||||
s64 ReadCpuScalar(s32 cpu, const char* node) {
|
||||
s64 value = 0;
|
||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node);
|
||||
if (!file || !(file >> value) || value <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
u64 ReadCpuMidr(s32 cpu) {
|
||||
u64 midr = 0;
|
||||
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) +
|
||||
"/regs/identification/midr_el1");
|
||||
if (!file || !(file >> std::hex >> midr)) {
|
||||
return 0;
|
||||
}
|
||||
return midr;
|
||||
}
|
||||
|
||||
std::vector<CoreInfo> CollectCores(const cpu_set_t& allowed, s32 total, const char* node,
|
||||
bool require_all) {
|
||||
std::vector<CoreInfo> cores;
|
||||
for (s32 cpu = 0; cpu < total; ++cpu) {
|
||||
if (!CPU_ISSET(cpu, &allowed)) {
|
||||
continue;
|
||||
}
|
||||
const s64 weight = ReadCpuScalar(cpu, node);
|
||||
if (weight <= 0) {
|
||||
if (require_all) {
|
||||
return {};
|
||||
}
|
||||
LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core",
|
||||
node, cpu);
|
||||
continue;
|
||||
}
|
||||
cores.push_back(CoreInfo{weight, ReadCpuMidr(cpu), cpu});
|
||||
}
|
||||
return cores;
|
||||
}
|
||||
|
||||
bool WeightsAreUniform(const std::vector<CoreInfo>& cores) {
|
||||
return std::all_of(cores.begin(), cores.end(),
|
||||
[&](const CoreInfo& core) { return core.weight == cores.front().weight; });
|
||||
}
|
||||
|
||||
bool MidrsAreDistinct(const std::vector<CoreInfo>& cores) {
|
||||
return std::none_of(cores.begin(), cores.end(),
|
||||
[](const CoreInfo& core) { return core.midr == 0; }) &&
|
||||
std::any_of(cores.begin(), cores.end(),
|
||||
[&](const CoreInfo& core) { return core.midr != cores.front().midr; });
|
||||
}
|
||||
|
||||
void ComputeTopologyLocked(CpuTopologyState& state) {
|
||||
state.initialized = true;
|
||||
state.separated = false;
|
||||
CPU_ZERO(&state.allowed);
|
||||
CPU_ZERO(&state.performance);
|
||||
CPU_ZERO(&state.efficiency);
|
||||
|
||||
if (sched_getaffinity(getpid(), sizeof(state.allowed), &state.allowed) != 0) {
|
||||
LOG_WARNING(Common, "Could not query process CPU affinity: {}",
|
||||
::Common::GetLastErrorMsg());
|
||||
return;
|
||||
}
|
||||
|
||||
const s32 total = PossibleCpuCount();
|
||||
auto cores = CollectCores(state.allowed, total, "cpu_capacity", true);
|
||||
if (cores.empty() || (WeightsAreUniform(cores) && MidrsAreDistinct(cores))) {
|
||||
auto by_frequency = CollectCores(state.allowed, total, "cpufreq/cpuinfo_max_freq", false);
|
||||
if (!by_frequency.empty()) {
|
||||
cores = std::move(by_frequency);
|
||||
}
|
||||
}
|
||||
if (cores.empty()) {
|
||||
LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (WeightsAreUniform(cores)) {
|
||||
if (MidrsAreDistinct(cores)) {
|
||||
LOG_WARNING(Common, "CPU clusters differ but rank identically, thread placement is "
|
||||
"disabled");
|
||||
} else {
|
||||
LOG_INFO(Common, "CPU cores are symmetric, thread placement is disabled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(cores.begin(), cores.end(), [](const CoreInfo& lhs, const CoreInfo& rhs) {
|
||||
if (lhs.weight != rhs.weight) {
|
||||
return lhs.weight > rhs.weight;
|
||||
}
|
||||
return lhs.cpu < rhs.cpu;
|
||||
});
|
||||
|
||||
const s64 fastest = cores.front().weight;
|
||||
size_t taken = 0;
|
||||
for (const auto& core : cores) {
|
||||
const bool fast_enough =
|
||||
core.weight * 100 >= fastest * ANDROID_PERFORMANCE_CAPACITY_PERCENT;
|
||||
if (!fast_enough && taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
|
||||
break;
|
||||
}
|
||||
CPU_SET(core.cpu, &state.performance);
|
||||
++taken;
|
||||
}
|
||||
if (taken == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (s32 cpu = 0; cpu < total; ++cpu) {
|
||||
if (CPU_ISSET(cpu, &state.allowed) && !CPU_ISSET(cpu, &state.performance)) {
|
||||
CPU_SET(cpu, &state.efficiency);
|
||||
}
|
||||
}
|
||||
|
||||
state.separated = CPU_COUNT(&state.efficiency) > 0;
|
||||
LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}",
|
||||
CPU_COUNT(&state.performance), CPU_COUNT(&state.efficiency),
|
||||
state.separated ? "enabled" : "unavailable");
|
||||
}
|
||||
|
||||
void EnsureTopologyLocked(CpuTopologyState& state) {
|
||||
if (!state.initialized) {
|
||||
ComputeTopologyLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshTopologyLocked(CpuTopologyState& state) {
|
||||
if (!state.initialized) {
|
||||
ComputeTopologyLocked(state);
|
||||
return;
|
||||
}
|
||||
cpu_set_t current;
|
||||
CPU_ZERO(¤t);
|
||||
if (sched_getaffinity(getpid(), sizeof(current), ¤t) != 0) {
|
||||
return;
|
||||
}
|
||||
if (std::memcmp(¤t, &state.allowed, sizeof(current)) != 0) {
|
||||
ComputeTopologyLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
bool ApplyCoreGroupLocked(CpuTopologyState& state, pid_t tid, CoreGroup group,
|
||||
bool* gone = nullptr) {
|
||||
const bool restrict_group = group != CoreGroup::Unrestricted && state.separated;
|
||||
const cpu_set_t* mask = &state.allowed;
|
||||
if (restrict_group) {
|
||||
mask = group == CoreGroup::Performance ? &state.performance : &state.efficiency;
|
||||
}
|
||||
if (CPU_COUNT(mask) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (sched_setaffinity(tid, sizeof(*mask), mask) != 0) {
|
||||
if (gone != nullptr && errno == ESRCH) {
|
||||
*gone = true;
|
||||
return false;
|
||||
}
|
||||
LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid,
|
||||
::Common::GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelPreservesRequestedAffinity() {
|
||||
utsname info{};
|
||||
if (uname(&info) != 0) {
|
||||
return false;
|
||||
}
|
||||
s32 major = 0;
|
||||
s32 minor = 0;
|
||||
if (std::sscanf(info.release, "%d.%d", &major, &minor) != 2) {
|
||||
return false;
|
||||
}
|
||||
return major > 6 || (major == 6 && minor >= 2);
|
||||
}
|
||||
|
||||
void SnapshotCanaryLocked(CpuTopologyState& state) {
|
||||
state.canary_valid = false;
|
||||
if (!state.separated) {
|
||||
return;
|
||||
}
|
||||
for (const auto& policy : state.policies) {
|
||||
if (policy.group == CoreGroup::Unrestricted) {
|
||||
continue;
|
||||
}
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO(&mask);
|
||||
if (sched_getaffinity(policy.tid, sizeof(mask), &mask) != 0) {
|
||||
continue;
|
||||
}
|
||||
state.canary_tid = policy.tid;
|
||||
state.canary_mask = mask;
|
||||
state.canary_valid = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool DueForPoll(CpuTopologyState& state) {
|
||||
const auto now = std::chrono::steady_clock::now().time_since_epoch();
|
||||
const s64 now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
|
||||
s64 next = state.next_poll_ns.load(std::memory_order_relaxed);
|
||||
if (now_ns < next) {
|
||||
return false;
|
||||
}
|
||||
return state.next_poll_ns.compare_exchange_strong(
|
||||
next, now_ns + ANDROID_POLICY_POLL_INTERVAL.count(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
ThreadPolicy& AcquirePolicyLocked(CpuTopologyState& state, pid_t tid) {
|
||||
for (auto& policy : state.policies) {
|
||||
if (policy.tid == tid) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
return state.policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false});
|
||||
}
|
||||
|
||||
void SetCurrentThreadCoreGroup(CoreGroup group) {
|
||||
const pid_t tid = gettid();
|
||||
(void)&t_policy_registration;
|
||||
|
||||
CoreGroup effective = group;
|
||||
if (tid == getpid() && group != CoreGroup::Unrestricted) {
|
||||
LOG_WARNING(Common, "Refusing to place the main thread: the CPU topology is read from it");
|
||||
effective = CoreGroup::Unrestricted;
|
||||
}
|
||||
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock topology_lock{state.topology_mutex};
|
||||
EnsureTopologyLocked(state);
|
||||
ApplyCoreGroupLocked(state, tid, effective);
|
||||
|
||||
std::scoped_lock policy_lock{state.policy_mutex};
|
||||
AcquirePolicyLocked(state, tid).group = effective;
|
||||
if (!state.canary_valid) {
|
||||
SnapshotCanaryLocked(state);
|
||||
}
|
||||
}
|
||||
|
||||
void RememberCurrentThreadNice(pid_t tid, s32 nice_value) {
|
||||
(void)&t_policy_registration;
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock lock{state.policy_mutex};
|
||||
ThreadPolicy& policy = AcquirePolicyLocked(state, tid);
|
||||
policy.nice_value = nice_value;
|
||||
policy.has_nice = true;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
#endif
|
||||
|
||||
#include "common/cpu_features.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#ifdef _MSC_VER
|
||||
@@ -48,7 +446,6 @@
|
||||
#endif
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
#include "core/core_timing.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -78,21 +475,31 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
}
|
||||
}();
|
||||
set_thread_priority(find_thread(NULL), priority);
|
||||
#else
|
||||
pthread_t this_thread = pthread_self();
|
||||
const auto scheduling_type = SCHED_OTHER;
|
||||
s32 max_prio = sched_get_priority_max(scheduling_type);
|
||||
s32 min_prio = sched_get_priority_min(scheduling_type);
|
||||
u32 level = (std::max)(u32(new_priority) + 1, 4U);
|
||||
|
||||
struct sched_param params;
|
||||
if (max_prio > min_prio) {
|
||||
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
|
||||
} else {
|
||||
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
|
||||
#elif defined(__ANDROID__)
|
||||
const int nice_value = NiceValueForPriority(new_priority);
|
||||
const pid_t tid = gettid();
|
||||
if (setpriority(PRIO_PROCESS, static_cast<id_t>(tid), nice_value) != 0) {
|
||||
LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
return;
|
||||
}
|
||||
RememberCurrentThreadNice(tid, nice_value);
|
||||
#elif defined(__linux__)
|
||||
const int nice_value = NiceValueForPriority(new_priority);
|
||||
if (setpriority(PRIO_PROCESS, 0, nice_value) != 0) {
|
||||
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
|
||||
GetLastErrorMsg());
|
||||
}
|
||||
#else
|
||||
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
|
||||
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
|
||||
if (max_prio > min_prio) {
|
||||
const u32 level = (std::min)(static_cast<u32>(new_priority), 4U);
|
||||
sched_param params{};
|
||||
params.sched_priority =
|
||||
min_prio + static_cast<s32>(static_cast<u32>(max_prio - min_prio) * level) / 4;
|
||||
pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms);
|
||||
}
|
||||
|
||||
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -132,29 +539,94 @@ void SetCurrentThreadName(const char* name) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id) {
|
||||
ASSERT(core_id < 4);
|
||||
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
|
||||
// run in ANY processor!
|
||||
auto const total_cores = std::thread::hardware_concurrency();
|
||||
if (core_id < total_cores) {
|
||||
void SetCurrentThreadToPerformanceCores() {
|
||||
#if defined(__ANDROID__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
sched_setaffinity(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(_WIN32)
|
||||
DWORD set = 1UL << core_id;
|
||||
SetThreadAffinityMask(GetCurrentThread(), set);
|
||||
#else
|
||||
// No pin functionality implemented
|
||||
#endif
|
||||
if (ADPF::AddCurrentThread(ADPF::Session::Render)) {
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
return;
|
||||
}
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Performance);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToEfficiencyCores() {
|
||||
#if defined(__ANDROID__)
|
||||
if (ADPF::AddCurrentThread(ADPF::Session::Background)) {
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
return;
|
||||
}
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Efficiency);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToBackgroundWork() {
|
||||
#if defined(__ANDROID__)
|
||||
ADPF::AddCurrentThread(ADPF::Session::Background);
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadToAllCores() {
|
||||
#if defined(__ANDROID__)
|
||||
ADPF::RemoveCurrentThread();
|
||||
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RefreshThreadPolicies() {
|
||||
#if defined(__ANDROID__)
|
||||
CpuTopologyState& state = State();
|
||||
std::scoped_lock topology_lock{state.topology_mutex};
|
||||
RefreshTopologyLocked(state);
|
||||
|
||||
std::scoped_lock policy_lock{state.policy_mutex};
|
||||
std::erase_if(state.policies, [&state](const ThreadPolicy& policy) {
|
||||
bool gone = false;
|
||||
if (policy.has_nice &&
|
||||
setpriority(PRIO_PROCESS, static_cast<id_t>(policy.tid), policy.nice_value) != 0 &&
|
||||
errno == ESRCH) {
|
||||
gone = true;
|
||||
}
|
||||
if (!gone) {
|
||||
ApplyCoreGroupLocked(state, policy.tid, policy.group, &gone);
|
||||
}
|
||||
return gone;
|
||||
});
|
||||
SnapshotCanaryLocked(state);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PollThreadPolicies() {
|
||||
#if defined(__ANDROID__)
|
||||
static const bool needed = !KernelPreservesRequestedAffinity();
|
||||
if (!needed) {
|
||||
return;
|
||||
}
|
||||
|
||||
CpuTopologyState& state = State();
|
||||
if (!DueForPoll(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pid_t tid;
|
||||
cpu_set_t expected;
|
||||
{
|
||||
std::scoped_lock lock{state.topology_mutex};
|
||||
if (!state.canary_valid) {
|
||||
return;
|
||||
}
|
||||
tid = state.canary_tid;
|
||||
expected = state.canary_mask;
|
||||
}
|
||||
|
||||
cpu_set_t current;
|
||||
CPU_ZERO(¤t);
|
||||
if (sched_getaffinity(tid, sizeof(current), ¤t) == 0 &&
|
||||
std::memcmp(¤t, &expected, sizeof(current)) == 0) {
|
||||
return;
|
||||
}
|
||||
RefreshThreadPolicies();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
+13
-1
@@ -99,8 +99,20 @@ enum class ThreadPriority : u32 {
|
||||
Critical = 4,
|
||||
};
|
||||
|
||||
enum class ThreadPlacement : u32 {
|
||||
Default = 0,
|
||||
Background = 1,
|
||||
Efficiency = 2,
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
void SetCurrentThreadName(const char* name);
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||
void SetCurrentThreadToPerformanceCores();
|
||||
void SetCurrentThreadToEfficiencyCores();
|
||||
void SetCurrentThreadToBackgroundWork();
|
||||
void SetCurrentThreadToAllCores();
|
||||
|
||||
void RefreshThreadPolicies();
|
||||
void PollThreadPolicies();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -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 2020 yuzu Emulator Project
|
||||
@@ -37,10 +37,25 @@ class StatefulThreadWorker {
|
||||
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
|
||||
|
||||
public:
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
|
||||
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {},
|
||||
ThreadPlacement placement = ThreadPlacement::Default)
|
||||
: workers_queued{num_workers}, thread_name{std::move(name)} {
|
||||
const auto lambda = [this, func](std::stop_token stop_token) {
|
||||
const auto lambda = [this, func, placement](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName(thread_name.c_str());
|
||||
if (placement != ThreadPlacement::Default) {
|
||||
Common::SetCurrentThreadPriority(ThreadPriority::Low);
|
||||
}
|
||||
switch (placement) {
|
||||
case ThreadPlacement::Efficiency:
|
||||
Common::SetCurrentThreadToEfficiencyCores();
|
||||
break;
|
||||
case ThreadPlacement::Background:
|
||||
Common::SetCurrentThreadToBackgroundWork();
|
||||
break;
|
||||
default:
|
||||
Common::SetCurrentThreadToAllCores();
|
||||
break;
|
||||
}
|
||||
{
|
||||
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
|
||||
while (!stop_token.stop_requested()) {
|
||||
|
||||
@@ -208,4 +208,12 @@ UUID UUID::MakeRandomRFC4122V4() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
UUID UUID::MakeRFC4122V5(std::span<u8, 20> sha1) {
|
||||
UUID uuid{};
|
||||
std::memcpy(&uuid.uuid, sha1.data(), sizeof(UUID));
|
||||
uuid.uuid[8] = 0x80 | (uuid.uuid[8] & 0x3F);
|
||||
uuid.uuid[6] = 0x50 | (uuid.uuid[6] & 0xF);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+16
-20
@@ -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
|
||||
|
||||
@@ -5,6 +8,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
@@ -86,28 +90,20 @@ struct UUID {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a random UUID.
|
||||
*
|
||||
* @returns A random UUID.
|
||||
*/
|
||||
static UUID MakeRandom();
|
||||
/// @brief Creates a random UUID.
|
||||
/// @returns A random UUID.
|
||||
[[nodiscard]] static UUID MakeRandom();
|
||||
|
||||
/**
|
||||
* Creates a random UUID with a seed.
|
||||
*
|
||||
* @param seed A seed to initialize the Mersenne-Twister RNG
|
||||
*
|
||||
* @returns A random UUID.
|
||||
*/
|
||||
static UUID MakeRandomWithSeed(u32 seed);
|
||||
/// @brief Creates a random UUID with a seed.
|
||||
/// @param seed A seed to initialize the Mersenne-Twister RNG
|
||||
/// @returns A random UUID.
|
||||
[[nodiscard]] static UUID MakeRandomWithSeed(u32 seed);
|
||||
|
||||
/**
|
||||
* Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant.
|
||||
*
|
||||
* @returns A random UUID that is RFC 4122 Version 4 compliant.
|
||||
*/
|
||||
static UUID MakeRandomRFC4122V4();
|
||||
/// @brief Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant.
|
||||
/// @returns A random UUID that is RFC 4122 Version 4 compliant.
|
||||
[[nodiscard]] static UUID MakeRandomRFC4122V4();
|
||||
|
||||
[[nodiscard]] static UUID MakeRFC4122V5(std::span<u8, 20> sha1);
|
||||
|
||||
friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default;
|
||||
};
|
||||
|
||||
@@ -1139,8 +1139,6 @@ add_library(core STATIC
|
||||
launch_timestamp_cache.h
|
||||
loader/deconstructed_rom_directory.cpp
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/homebrew_nxlink.cpp
|
||||
loader/homebrew_nxlink.h
|
||||
loader/kip.cpp
|
||||
loader/kip.h
|
||||
loader/loader.cpp
|
||||
|
||||
+7
-3
@@ -10,6 +10,7 @@
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/adpf.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "common/string_util.h"
|
||||
@@ -52,7 +53,6 @@
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/homebrew_nxlink.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
@@ -119,6 +119,7 @@ struct System::Impl {
|
||||
|
||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
||||
unified_memory = Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
core_timing.SetMulticore(is_multicore);
|
||||
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
||||
@@ -146,7 +147,8 @@ struct System::Impl {
|
||||
!device_memory.has_value() ||
|
||||
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
||||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
|
||||
Settings::MemoryLayout::Memory_4Gb);
|
||||
Settings::MemoryLayout::Memory_4Gb) ||
|
||||
unified_memory != Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
if (!must_reinitialize) {
|
||||
return;
|
||||
@@ -157,6 +159,7 @@ struct System::Impl {
|
||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
extended_memory_layout =
|
||||
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
|
||||
unified_memory = Settings::values.use_unified_memory.GetValue();
|
||||
|
||||
Initialize(system);
|
||||
}
|
||||
@@ -386,6 +389,7 @@ struct System::Impl {
|
||||
|
||||
void ShutdownMainProcess() {
|
||||
SetShuttingDown(true);
|
||||
Common::ADPF::Shutdown();
|
||||
|
||||
// Reset per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
@@ -398,7 +402,6 @@ struct System::Impl {
|
||||
|
||||
stop_event.request_stop();
|
||||
core_timing.SyncPause(false);
|
||||
Loader::HomebrewNxlink::StopServer();
|
||||
Network::CancelPendingSocketOperations();
|
||||
kernel.SuspendEmulation(true);
|
||||
kernel.CloseServices();
|
||||
@@ -503,6 +506,7 @@ struct System::Impl {
|
||||
std::atomic_bool is_powered_on{};
|
||||
bool is_multicore : 1 = false;
|
||||
bool extended_memory_layout : 1 = false;
|
||||
bool unified_memory : 1 = false;
|
||||
bool exit_locked : 1 = false;
|
||||
bool exit_requested : 1 = false;
|
||||
bool nvdec_active : 1 = false;
|
||||
|
||||
@@ -23,6 +23,21 @@ namespace Core::Timing {
|
||||
|
||||
constexpr s64 MAX_SLICE_LENGTH = 10000;
|
||||
|
||||
constexpr u32 CPU_CLOCK_BASE_MHZ = 1020;
|
||||
constexpr u32 CPU_CLOCK_BOOST_MHZ = 1734;
|
||||
constexpr u32 CPU_CLOCK_OVERCLOCK_MHZ = 2040;
|
||||
|
||||
constexpr u32 CpuClockTargetMhz(Settings::CpuClock clock) {
|
||||
switch (clock) {
|
||||
case Settings::CpuClock::Boost:
|
||||
return CPU_CLOCK_BOOST_MHZ;
|
||||
case Settings::CpuClock::Overclock:
|
||||
return CPU_CLOCK_OVERCLOCK_MHZ;
|
||||
default:
|
||||
return CPU_CLOCK_BASE_MHZ;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
|
||||
return std::make_shared<EventType>(std::move(callback), std::move(name));
|
||||
}
|
||||
@@ -58,7 +73,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
if (is_multicore) {
|
||||
timer_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("HostTiming");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
on_thread_init();
|
||||
has_started = true;
|
||||
|
||||
@@ -209,8 +225,9 @@ void CoreTiming::ResetTicks() {
|
||||
|
||||
u64 CoreTiming::GetClockTicks() const {
|
||||
u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) {
|
||||
fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock)));
|
||||
if (const u32 target = CpuClockTargetMhz(Settings::values.cpu_clock.GetValue());
|
||||
target != CPU_CLOCK_BASE_MHZ) {
|
||||
fres = fres * target / CPU_CLOCK_BASE_MHZ;
|
||||
}
|
||||
if (::Settings::values.sync_core_speed.GetValue()) {
|
||||
auto const ticks = f64(fres);
|
||||
|
||||
@@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
||||
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
#ifdef __ANDROID__
|
||||
// Aimed specifically for Snapdragon 8 Elite devices
|
||||
// This kills performance on desktop, but boosts perf for UMA devices
|
||||
// like the S8E. Mediatek and Mali likely won't suffer.
|
||||
Common::PinCurrentThreadToPerformanceCore(core);
|
||||
#endif
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
auto& data = core_data[core];
|
||||
data.host_context = Common::Fiber::ThreadToFiber();
|
||||
|
||||
|
||||
@@ -12,9 +12,18 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
|
||||
constexpr size_t VirtualReserveSize = 1ULL << 39;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
size_t ApplicationPoolOffset() {
|
||||
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
|
||||
const size_t dram_size = Init::GetIntendedMemorySize();
|
||||
const size_t application_pool_size = Init::GetApplicationPoolSize();
|
||||
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceMemory::DeviceMemory()
|
||||
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
|
||||
VirtualReserveSize} {}
|
||||
VirtualReserveSize, ApplicationPoolOffset()} {}
|
||||
|
||||
DeviceMemory::~DeviceMemory() = default;
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/virtual_buffer.h"
|
||||
|
||||
struct AHardwareBuffer;
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr size_t DEVICE_PAGEBITS = 12ULL;
|
||||
@@ -95,6 +97,34 @@ public:
|
||||
ApplyOpOnPAddr(address, buffer, operation);
|
||||
}
|
||||
|
||||
u8* GetPhysicalBase() noexcept {
|
||||
return reinterpret_cast<u8*>(physical_base);
|
||||
}
|
||||
|
||||
const u8* GetPhysicalBase() const noexcept {
|
||||
return reinterpret_cast<const u8*>(physical_base);
|
||||
}
|
||||
|
||||
size_t GetPhysicalSize() const noexcept {
|
||||
return physical_size;
|
||||
}
|
||||
|
||||
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
|
||||
return ahb_windows;
|
||||
}
|
||||
|
||||
size_t GetBackingHardwareBufferWindowSize() const noexcept {
|
||||
return ahb_window_size;
|
||||
}
|
||||
|
||||
size_t GetBackingHardwareBufferBase() const noexcept {
|
||||
return ahb_base;
|
||||
}
|
||||
|
||||
bool IsBackingShared() const noexcept {
|
||||
return backing_is_shared;
|
||||
}
|
||||
|
||||
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
|
||||
PAddr subbits = PAddr(address & page_mask);
|
||||
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
|
||||
@@ -171,6 +201,11 @@ private:
|
||||
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
|
||||
|
||||
const uintptr_t physical_base;
|
||||
const size_t physical_size;
|
||||
const std::span<AHardwareBuffer* const> ahb_windows;
|
||||
const size_t ahb_window_size;
|
||||
const size_t ahb_base;
|
||||
const bool backing_is_shared;
|
||||
DeviceInterface* device_inter;
|
||||
|
||||
struct TrackedEntry {
|
||||
|
||||
@@ -171,6 +171,11 @@ struct DeviceMemoryManagerAllocator {
|
||||
template <typename Traits>
|
||||
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
|
||||
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
|
||||
, physical_size{device_memory_.buffer.BackingSize()}
|
||||
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
|
||||
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
|
||||
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
|
||||
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
|
||||
, device_inter{nullptr}
|
||||
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
|
||||
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
|
||||
|
||||
@@ -274,23 +274,22 @@ public:
|
||||
explicit NACP(VirtualFile file);
|
||||
~NACP();
|
||||
|
||||
const LanguageEntry& GetLanguageEntry() const;
|
||||
std::string GetApplicationName() const;
|
||||
std::string GetDeveloperName() const;
|
||||
u64 GetTitleId() const;
|
||||
u64 GetDLCBaseTitleId() const;
|
||||
std::string GetVersionString() const;
|
||||
u64 GetDefaultNormalSaveSize() const;
|
||||
u64 GetDefaultJournalSaveSize() const;
|
||||
u32 GetSupportedLanguages() const;
|
||||
std::vector<std::string> GetApplicationNames() const;
|
||||
std::vector<u8> GetRawBytes() const;
|
||||
bool GetUserAccountSwitchLock() const;
|
||||
u64 GetDeviceSaveDataSize() const;
|
||||
u32 GetParentalControlFlag() const;
|
||||
const std::array<u8, 0x20>& GetRatingAge() const;
|
||||
[[nodiscard]] const LanguageEntry& GetLanguageEntry() const;
|
||||
[[nodiscard]] std::string GetApplicationName() const;
|
||||
[[nodiscard]] std::string GetDeveloperName() const;
|
||||
[[nodiscard]] u64 GetTitleId() const;
|
||||
[[nodiscard]] u64 GetDLCBaseTitleId() const;
|
||||
[[nodiscard]] std::string GetVersionString() const;
|
||||
[[nodiscard]] u64 GetDefaultNormalSaveSize() const;
|
||||
[[nodiscard]] u64 GetDefaultJournalSaveSize() const;
|
||||
[[nodiscard]] u32 GetSupportedLanguages() const;
|
||||
[[nodiscard]] std::vector<std::string> GetApplicationNames() const;
|
||||
[[nodiscard]] std::vector<u8> GetRawBytes() const;
|
||||
[[nodiscard]] bool GetUserAccountSwitchLock() const;
|
||||
[[nodiscard]] u64 GetDeviceSaveDataSize() const;
|
||||
[[nodiscard]] u32 GetParentalControlFlag() const;
|
||||
[[nodiscard]] const std::array<u8, 0x20>& GetRatingAge() const;
|
||||
|
||||
private:
|
||||
RawNACP raw{};
|
||||
std::vector<LanguageEntry> language_entries;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
namespace FileSys {
|
||||
|
||||
enum class OpenMode : u32 {
|
||||
Default = 0,
|
||||
Read = (1 << 0),
|
||||
Write = (1 << 1),
|
||||
AllowAppend = (1 << 2),
|
||||
|
||||
@@ -1156,4 +1156,14 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||
|
||||
return {std::move(nacp), icon_file};
|
||||
}
|
||||
|
||||
[[nodiscard]] PatchManager::Metadata PatchManager::GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept {
|
||||
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr)
|
||||
return metadata;
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -105,6 +105,9 @@ public:
|
||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||
[[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
|
||||
|
||||
/// @brief Gets NACP metadata (accounting for any patches or updates)
|
||||
[[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const;
|
||||
|
||||
@@ -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: Copyright 2018 yuzu Emulator Project
|
||||
@@ -40,7 +40,7 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
|
||||
|
||||
VirtualFile VfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
|
||||
const auto path = Common::FS::SanitizePath(path_);
|
||||
return root->GetFileRelative(path, perms);
|
||||
return root->GetFileRelative(path);
|
||||
}
|
||||
|
||||
VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
|
||||
@@ -201,7 +201,7 @@ std::string VfsFile::GetFullPath() const {
|
||||
return GetContainingDirectory()->GetFullPath() + '/' + GetName();
|
||||
}
|
||||
|
||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
|
||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
auto vec = Common::FS::SplitPathComponents(path);
|
||||
if (vec.empty()) {
|
||||
return nullptr;
|
||||
@@ -224,10 +224,7 @@ VirtualFile VfsDirectory::GetFileRelative(std::string_view path, OpenMode perms)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (perms == OpenMode::Default) {
|
||||
return dir->GetFile(vec.back());
|
||||
}
|
||||
return dir->GetFileRelative(vec.back(), perms);
|
||||
return dir->GetFile(vec.back());
|
||||
}
|
||||
|
||||
VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
|
||||
|
||||
@@ -201,8 +201,7 @@ public:
|
||||
|
||||
// Retrieves the file located at path as if the current directory was root. Returns nullptr if
|
||||
// not found.
|
||||
virtual VirtualFile GetFileRelative(std::string_view path,
|
||||
OpenMode perms = OpenMode::Default) const;
|
||||
virtual VirtualFile GetFileRelative(std::string_view path) const;
|
||||
// Calls GetFileRelative(path) on the root of the current directory.
|
||||
virtual VirtualFile GetFileAbsolute(std::string_view path) const;
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dir
|
||||
return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
|
||||
}
|
||||
|
||||
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
|
||||
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
for (const auto& layer : dirs) {
|
||||
const auto file = layer->GetFileRelative(path, perms);
|
||||
const auto file = layer->GetFileRelative(path);
|
||||
if (file != nullptr)
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -23,8 +20,7 @@ public:
|
||||
/// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
|
||||
static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
|
||||
|
||||
VirtualFile GetFileRelative(std::string_view path,
|
||||
OpenMode perms = OpenMode::Default) const override;
|
||||
VirtualFile GetFileRelative(std::string_view path) const override;
|
||||
VirtualDir GetDirectoryRelative(std::string_view path) const override;
|
||||
VirtualFile GetFile(std::string_view file_name) const override;
|
||||
VirtualDir GetSubdirectory(std::string_view subdir_name) const override;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/vfs/vfs_real.h"
|
||||
|
||||
@@ -45,11 +46,17 @@ bool IsWithinRoot(std::string_view root, std::string_view full_path) {
|
||||
}
|
||||
|
||||
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
|
||||
if (True(mode & OpenMode::Write) || True(mode & OpenMode::AllowAppend)) {
|
||||
switch (mode) {
|
||||
case OpenMode::Read:
|
||||
return FS::FileAccessMode::Read;
|
||||
case OpenMode::Write:
|
||||
case OpenMode::ReadWrite:
|
||||
case OpenMode::AllowAppend:
|
||||
case OpenMode::All:
|
||||
return FS::FileAccessMode::ReadWrite;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
return FS::FileAccessMode::Read;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
@@ -87,11 +94,9 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
|
||||
std::optional<std::string> parent_path,
|
||||
OpenMode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
const auto open_perms = perms == OpenMode::Default ? OpenMode::Read : perms;
|
||||
std::scoped_lock lk{list_lock};
|
||||
|
||||
const CacheKey cache_key{path, open_perms};
|
||||
if (auto it = cache.find(cache_key); it != cache.end()) {
|
||||
if (auto it = cache.find(path); it != cache.end()) {
|
||||
if (auto file = it->second.lock(); file) {
|
||||
return file;
|
||||
}
|
||||
@@ -105,9 +110,8 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
|
||||
this->InsertReferenceIntoListLocked(*reference);
|
||||
|
||||
auto file = std::shared_ptr<RealVfsFile>(
|
||||
new RealVfsFile(*this, std::move(reference), path, open_perms, size,
|
||||
std::move(parent_path)));
|
||||
cache[cache_key] = file;
|
||||
new RealVfsFile(*this, std::move(reference), path, perms, size, std::move(parent_path)));
|
||||
cache[path] = file;
|
||||
|
||||
return file;
|
||||
}
|
||||
@@ -120,7 +124,7 @@ VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
CloseCachedFileReferenceLocked(path);
|
||||
cache.erase(path);
|
||||
}
|
||||
|
||||
// Current usages of CreateFile expect to delete the contents of an existing file.
|
||||
@@ -153,8 +157,8 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
|
||||
const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
CloseCachedFileReferenceLocked(old_path);
|
||||
CloseCachedFileReferenceLocked(new_path);
|
||||
cache.erase(old_path);
|
||||
cache.erase(new_path);
|
||||
}
|
||||
if (!FS::RenameFile(old_path, new_path)) {
|
||||
return nullptr;
|
||||
@@ -166,15 +170,14 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
{
|
||||
std::scoped_lock lk{list_lock};
|
||||
CloseCachedFileReferenceLocked(path);
|
||||
cache.erase(path);
|
||||
}
|
||||
return FS::RemoveFile(path);
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
return std::shared_ptr<RealVfsDirectory>(
|
||||
new RealVfsDirectory(*this, path, perms == OpenMode::Default ? OpenMode::Read : perms));
|
||||
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
|
||||
}
|
||||
|
||||
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
|
||||
@@ -219,8 +222,8 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri
|
||||
if (!reference.file) {
|
||||
this->EvictSingleReferenceLocked();
|
||||
|
||||
reference.file = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms),
|
||||
FS::FileType::BinaryFile, FS::FileShareFlag::ShareReadWriteDelete);
|
||||
reference.file =
|
||||
FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
|
||||
if (reference.file) {
|
||||
num_open_files++;
|
||||
}
|
||||
@@ -294,45 +297,15 @@ RealVfsFile::~RealVfsFile() {
|
||||
base.DropReference(std::move(reference));
|
||||
}
|
||||
|
||||
void RealVfsFilesystem::CloseCachedFileReferenceLocked(const std::string& path) {
|
||||
for (auto it = cache.lower_bound(CacheKey{path, OpenMode::Default});
|
||||
it != cache.end() && it->first.first == path;) {
|
||||
const auto cached_file = it->second.lock();
|
||||
if (cached_file) {
|
||||
auto* const real_file = static_cast<RealVfsFile*>(cached_file.get());
|
||||
auto& reference = real_file->reference;
|
||||
if (reference && reference->file) {
|
||||
RemoveReferenceFromListLocked(*reference);
|
||||
reference->file.reset();
|
||||
num_open_files--;
|
||||
InsertReferenceIntoListLocked(*reference);
|
||||
}
|
||||
}
|
||||
it = cache.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::string RealVfsFile::GetName() const {
|
||||
#ifdef __ANDROID__
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
if (path[0] != '/') {
|
||||
return FS::Android::GetFilename(path);
|
||||
}
|
||||
#endif
|
||||
return path_components.empty() ? "" : std::string(path_components.back());
|
||||
}
|
||||
|
||||
std::string RealVfsFile::GetFullPath() const {
|
||||
#ifdef __ANDROID__
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
auto out = path;
|
||||
std::replace(out.begin(), out.end(), '\\', '/');
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
return VfsFile::GetFullPath();
|
||||
}
|
||||
|
||||
std::size_t RealVfsFile::GetSize() const {
|
||||
if (size) {
|
||||
return *size;
|
||||
@@ -438,14 +411,13 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
|
||||
|
||||
RealVfsDirectory::~RealVfsDirectory() = default;
|
||||
|
||||
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path,
|
||||
OpenMode open_perms) const {
|
||||
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
|
||||
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
|
||||
if (!FS::Exists(full_path) || FS::IsDir(full_path)
|
||||
|| !IsWithinRoot(FS::SanitizePath(path), full_path)) {
|
||||
return nullptr;
|
||||
}
|
||||
return base.OpenFile(full_path, open_perms == OpenMode::Default ? perms : open_perms);
|
||||
return base.OpenFile(full_path, perms);
|
||||
}
|
||||
|
||||
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include "common/intrusive_list.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
@@ -50,9 +49,8 @@ public:
|
||||
bool DeleteDirectory(std::string_view path) override;
|
||||
|
||||
private:
|
||||
using CacheKey = std::pair<std::string, OpenMode>;
|
||||
using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
|
||||
std::map<CacheKey, std::weak_ptr<VfsFile>, std::less<>> cache;
|
||||
std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
|
||||
ReferenceListType open_references;
|
||||
ReferenceListType closed_references;
|
||||
std::mutex list_lock;
|
||||
@@ -65,7 +63,6 @@ private:
|
||||
std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms,
|
||||
FileReference& reference);
|
||||
void DropReference(std::unique_ptr<FileReference>&& reference);
|
||||
void CloseCachedFileReferenceLocked(const std::string& path);
|
||||
|
||||
private:
|
||||
friend class RealVfsDirectory;
|
||||
@@ -88,7 +85,6 @@ public:
|
||||
~RealVfsFile() override;
|
||||
|
||||
std::string GetName() const override;
|
||||
std::string GetFullPath() const override;
|
||||
std::size_t GetSize() const override;
|
||||
bool Resize(std::size_t new_size) override;
|
||||
VirtualDir GetContainingDirectory() const override;
|
||||
@@ -119,8 +115,7 @@ class RealVfsDirectory : public VfsDirectory {
|
||||
public:
|
||||
~RealVfsDirectory() override;
|
||||
|
||||
VirtualFile GetFileRelative(std::string_view relative_path,
|
||||
OpenMode perms = OpenMode::Default) const override;
|
||||
VirtualFile GetFileRelative(std::string_view relative_path) const override;
|
||||
VirtualDir GetDirectoryRelative(std::string_view relative_path) const override;
|
||||
VirtualFile GetFile(std::string_view name) const override;
|
||||
VirtualDir GetSubdirectory(std::string_view name) const override;
|
||||
|
||||
@@ -670,11 +670,6 @@ public:
|
||||
size_t GetHeapRegionSize() const {
|
||||
return m_heap_region_end - m_heap_region_start;
|
||||
}
|
||||
size_t GetCurrentHeapSize() const {
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
return m_current_heap_end - m_heap_region_start;
|
||||
}
|
||||
size_t GetAliasRegionSize() const {
|
||||
return m_alias_region_end - m_alias_region_start;
|
||||
}
|
||||
|
||||
@@ -210,11 +210,6 @@ Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParamete
|
||||
m_arg_pointer = 0;
|
||||
m_arg_return_address = 0;
|
||||
m_main_thread_handle_addr = 0;
|
||||
m_process_handle_addr = 0;
|
||||
m_homebrew_next_load_path_addr = 0;
|
||||
m_homebrew_next_load_argv_addr = 0;
|
||||
m_is_homebrew_in_place_next_load = false;
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
m_code_size = params.code_num_pages * PageSize;
|
||||
m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication);
|
||||
|
||||
@@ -952,7 +947,6 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
|
||||
|
||||
stack_top = stack_bottom + stack_size;
|
||||
m_main_thread_stack_size = stack_size;
|
||||
m_main_thread_stack_top = stack_top;
|
||||
}
|
||||
|
||||
// Ensure our stack is safe to clean up on exit.
|
||||
@@ -1011,11 +1005,6 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
|
||||
if (GetInteger(m_main_thread_handle_addr) != 0) {
|
||||
this->GetMemory().Write32(m_main_thread_handle_addr, thread_handle);
|
||||
}
|
||||
if (GetInteger(m_process_handle_addr) != 0) {
|
||||
Handle process_handle;
|
||||
R_TRY(m_handle_table.Add(kernel, std::addressof(process_handle), this));
|
||||
this->GetMemory().Write32(m_process_handle_addr, process_handle);
|
||||
}
|
||||
} else {
|
||||
main_thread->GetContext().r[0] = 0;
|
||||
main_thread->GetContext().r[1] = thread_handle;
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
@@ -89,10 +87,6 @@ private:
|
||||
KProcessAddress m_arg_pointer{};
|
||||
KProcessAddress m_arg_return_address{};
|
||||
KProcessAddress m_main_thread_handle_addr{};
|
||||
KProcessAddress m_process_handle_addr{};
|
||||
KProcessAddress m_homebrew_next_load_path_addr{};
|
||||
KProcessAddress m_homebrew_next_load_argv_addr{};
|
||||
std::array<char, 16> m_homebrew_nxlink_argv_marker{};
|
||||
KHandleTable m_handle_table;
|
||||
KProcessAddress m_plr_address{};
|
||||
ThreadList m_thread_list{};
|
||||
@@ -118,7 +112,6 @@ private:
|
||||
|
||||
size_t m_code_size{};
|
||||
size_t m_main_thread_stack_size{};
|
||||
KProcessAddress m_main_thread_stack_top{};
|
||||
size_t m_max_process_memory{};
|
||||
size_t m_memory_release_hint{};
|
||||
s64 m_schedule_count{};
|
||||
@@ -146,8 +139,6 @@ private:
|
||||
bool m_is_suspended : 1 = false;
|
||||
bool m_is_immortal : 1 = false;
|
||||
bool m_is_handle_table_initialized : 1 = false;
|
||||
bool m_is_homebrew_in_place_next_load : 1 = false;
|
||||
bool m_has_homebrew_nxlink_argv_marker : 1 = false;
|
||||
|
||||
private:
|
||||
Result StartTermination(KernelCore& kernel);
|
||||
@@ -240,49 +231,6 @@ public:
|
||||
void SetMainThreadHandleAddr(KProcessAddress addr) {
|
||||
m_main_thread_handle_addr = addr;
|
||||
}
|
||||
void SetProcessHandleAddr(KProcessAddress addr) {
|
||||
m_process_handle_addr = addr;
|
||||
}
|
||||
void SetHomebrewNextLoadBufferAddrs(KProcessAddress path_addr, KProcessAddress argv_addr) {
|
||||
m_homebrew_next_load_path_addr = path_addr;
|
||||
m_homebrew_next_load_argv_addr = argv_addr;
|
||||
}
|
||||
void SetHomebrewInPlaceNextLoad(bool enabled) {
|
||||
m_is_homebrew_in_place_next_load = enabled;
|
||||
}
|
||||
void SetHomebrewNxlinkArgvMarker(std::string_view marker) {
|
||||
if (marker.size() != m_homebrew_nxlink_argv_marker.size()) {
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
return;
|
||||
}
|
||||
|
||||
marker.copy(m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size());
|
||||
m_has_homebrew_nxlink_argv_marker = true;
|
||||
}
|
||||
void ClearHomebrewNxlinkArgvMarker() {
|
||||
m_has_homebrew_nxlink_argv_marker = false;
|
||||
}
|
||||
std::string_view GetHomebrewNxlinkArgvMarker() const {
|
||||
if (!m_has_homebrew_nxlink_argv_marker) {
|
||||
return {};
|
||||
}
|
||||
return {m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size()};
|
||||
}
|
||||
bool IsHomebrewInPlaceNextLoad() const {
|
||||
return m_is_homebrew_in_place_next_load;
|
||||
}
|
||||
KProcessAddress GetHomebrewNextLoadPathAddr() const {
|
||||
return m_homebrew_next_load_path_addr;
|
||||
}
|
||||
KProcessAddress GetHomebrewNextLoadArgvAddr() const {
|
||||
return m_homebrew_next_load_argv_addr;
|
||||
}
|
||||
size_t GetCodeSize() const {
|
||||
return m_code_size;
|
||||
}
|
||||
KProcessAddress GetMainThreadStackTop() const {
|
||||
return m_main_thread_stack_top;
|
||||
}
|
||||
|
||||
size_t GetMainStackSize() const {
|
||||
return m_main_thread_stack_size;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// SPDX-License-Identifier: GPL-2.0-or-late
|
||||
|
||||
// This file is automatically generated using svc_generator.py.
|
||||
// DO NOT MODIFY IT MANUALLY
|
||||
@@ -129,7 +128,7 @@ static void SvcWrap_QueryMemory64From32(Core::System& system, std::span<uint64_t
|
||||
}
|
||||
|
||||
static void SvcWrap_ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess64From32(system, args);
|
||||
ExitProcess64From32(system);
|
||||
}
|
||||
|
||||
static void SvcWrap_CreateThread64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
@@ -1299,7 +1298,7 @@ static void SvcWrap_QueryMemory64(Core::System& system, std::span<uint64_t, 8> a
|
||||
}
|
||||
|
||||
static void SvcWrap_ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess64(system, args);
|
||||
ExitProcess64(system);
|
||||
}
|
||||
|
||||
static void SvcWrap_CreateThread64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// SPDX-License-Identifier: GPL-2.0-or-late
|
||||
|
||||
// This file is automatically generated using svc_generator.py.
|
||||
// DO NOT MODIFY IT MANUALLY
|
||||
@@ -26,7 +25,7 @@ Result SetMemoryAttribute(Core::System& system, uint64_t address, uint64_t size,
|
||||
Result MapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result UnmapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address);
|
||||
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args);
|
||||
void ExitProcess(Core::System& system);
|
||||
Result CreateThread(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread(Core::System& system, Handle thread_handle);
|
||||
void ExitThread(Core::System& system);
|
||||
@@ -147,7 +146,7 @@ Result SetMemoryAttribute64From32(Core::System& system, uint32_t address, uint32
|
||||
Result MapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size);
|
||||
Result UnmapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size);
|
||||
Result QueryMemory64From32(Core::System& system, uint32_t out_memory_info, PageInfo* out_page_info, uint32_t address);
|
||||
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args);
|
||||
void ExitProcess64From32(Core::System& system);
|
||||
Result CreateThread64From32(Core::System& system, Handle* out_handle, uint32_t func, uint32_t arg, uint32_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread64From32(Core::System& system, Handle thread_handle);
|
||||
void ExitThread64From32(Core::System& system);
|
||||
@@ -268,7 +267,7 @@ Result SetMemoryAttribute64(Core::System& system, uint64_t address, uint64_t siz
|
||||
Result MapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result UnmapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
|
||||
Result QueryMemory64(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address);
|
||||
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args);
|
||||
void ExitProcess64(Core::System& system);
|
||||
Result CreateThread64(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id);
|
||||
Result StartThread64(Core::System& system, Handle thread_handle);
|
||||
void ExitThread64(Core::System& system);
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
@@ -25,179 +22,6 @@ constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsHomebrewInPlaceNextLoadCodeRange(const KProcess& process, u64 address, u64 size) {
|
||||
if (!process.IsHomebrewInPlaceNextLoad()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 code_start = GetInteger(process.GetEntryPoint());
|
||||
const size_t code_size = process.GetCodeSize();
|
||||
if (code_start == 0 || code_size == 0 || size > code_size || address < code_start) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return address - code_start <= code_size - size;
|
||||
}
|
||||
|
||||
struct HomebrewInPlaceMemoryBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
KMemoryState state;
|
||||
KMemoryPermission permission;
|
||||
KMemoryAttribute attribute;
|
||||
bool use_process_permission;
|
||||
};
|
||||
|
||||
Result SetHomebrewInPlaceMemoryPermissionByBlocks(KProcess& process, u64 address, u64 size,
|
||||
MemoryPermission perm, Result original_result) {
|
||||
auto& page_table = process.GetPageTable();
|
||||
const u64 end = address + size;
|
||||
const auto requested_permission = ConvertToKMemoryPermission(perm);
|
||||
std::vector<HomebrewInPlaceMemoryBlock> blocks;
|
||||
|
||||
for (u64 cursor = address; cursor < end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result = page_table.QueryInfo(std::addressof(info),
|
||||
std::addressof(page_info), cursor);
|
||||
if (query_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission query failed "
|
||||
"address=0x{:016X}, result={:#X}, original={:#X}",
|
||||
cursor, query_result.raw, original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), end);
|
||||
if (block_end <= cursor) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission walk stalled "
|
||||
"cursor=0x{:016X}, block=0x{:016X}/0x{:X}, original={:#X}",
|
||||
cursor, info.GetAddress(), info.GetSize(), original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const bool can_reprotect = True(info.GetState() & KMemoryState::FlagCanReprotect);
|
||||
const bool can_process_reprotect = True(info.GetState() & KMemoryState::FlagCode);
|
||||
if (!can_reprotect && !can_process_reprotect) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission unsupported block "
|
||||
"address=0x{:016X}, size=0x{:X}, state=0x{:08X}, svc_state={}, "
|
||||
"perm=0x{:08X}, attr=0x{:08X}, original={:#X}",
|
||||
cursor, block_end - cursor, static_cast<u32>(info.GetState()),
|
||||
static_cast<u32>(info.GetSvcState()),
|
||||
static_cast<u32>(info.GetPermission()),
|
||||
static_cast<u32>(info.GetAttribute()), original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
blocks.push_back({
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.state = info.GetState(),
|
||||
.permission = info.GetPermission(),
|
||||
.attribute = info.GetAttribute(),
|
||||
.use_process_permission = !can_reprotect && can_process_reprotect,
|
||||
});
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
if (block.permission == requested_permission) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto block_result =
|
||||
block.use_process_permission
|
||||
? page_table.SetProcessMemoryPermission(block.address, block.size, perm)
|
||||
: page_table.SetMemoryPermission(block.address, block.size, perm);
|
||||
if (block_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: split permission block failed "
|
||||
"address=0x{:016X}, size=0x{:X}, state=0x{:08X}, perm=0x{:08X}, "
|
||||
"attr=0x{:08X}, result={:#X}, original={:#X}",
|
||||
block.address, block.size, static_cast<u32>(block.state),
|
||||
static_cast<u32>(block.permission), static_cast<u32>(block.attribute),
|
||||
block_result.raw, original_result.raw);
|
||||
R_RETURN(block_result);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
struct HomebrewInPlaceDeviceSharedBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
u16 device_use_count;
|
||||
};
|
||||
|
||||
Result UnlockHomebrewInPlaceDeviceSharedSource(KProcess& process, u64 address, u64 size,
|
||||
Result original_result) {
|
||||
auto& page_table = process.GetPageTable();
|
||||
const u64 end = address + size;
|
||||
std::vector<HomebrewInPlaceDeviceSharedBlock> blocks;
|
||||
|
||||
for (u64 cursor = address; cursor < end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result = page_table.QueryInfo(std::addressof(info),
|
||||
std::addressof(page_info), cursor);
|
||||
if (query_result.IsError()) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), end);
|
||||
if (block_end <= cursor) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
const bool can_device_map = True(info.GetState() & KMemoryState::FlagCanDeviceMap);
|
||||
const bool is_clean_memory =
|
||||
can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite &&
|
||||
info.GetAttribute() == KMemoryAttribute::None && info.m_device_use_count == 0;
|
||||
const bool is_stale_device_shared =
|
||||
can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite &&
|
||||
info.GetAttribute() == KMemoryAttribute::DeviceShared && info.m_device_use_count > 0;
|
||||
if (is_clean_memory) {
|
||||
cursor = block_end;
|
||||
continue;
|
||||
}
|
||||
if (!is_stale_device_shared) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
blocks.push_back({
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.device_use_count = info.m_device_use_count,
|
||||
});
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
if (blocks.empty()) {
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
for (u16 unlock = 0; unlock < block.device_use_count; unlock++) {
|
||||
const auto unlock_result =
|
||||
page_table.UnlockForDeviceAddressSpace(block.address, block.size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: device-shared source unlock failed "
|
||||
"address=0x{:016X}, size=0x{:X}, remaining={}, result={:#X}, "
|
||||
"original={:#X}",
|
||||
block.address, block.size, block.device_use_count - unlock - 1,
|
||||
unlock_result.raw, original_result.raw);
|
||||
R_RETURN(original_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// Checks if address + size is greater than the given address
|
||||
// This can return false if the size causes an overflow of a 64-bit type
|
||||
// or if the given size is zero.
|
||||
@@ -268,19 +92,11 @@ Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPe
|
||||
R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission);
|
||||
|
||||
// Validate that the region is in range for the current process.
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
auto& page_table = GetCurrentProcess(system.Kernel()).GetPageTable();
|
||||
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
|
||||
|
||||
// Set the memory attribute.
|
||||
const auto result = page_table.SetMemoryPermission(address, size, perm);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw &&
|
||||
IsHomebrewInPlaceNextLoadCodeRange(process, address, size)) {
|
||||
R_RETURN(
|
||||
SetHomebrewInPlaceMemoryPermissionByBlocks(process, address, size, perm, result));
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
R_RETURN(page_table.SetMemoryPermission(address, size, perm));
|
||||
}
|
||||
|
||||
Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, u32 attr) {
|
||||
@@ -319,30 +135,14 @@ Result MapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
|
||||
|
||||
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
|
||||
result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto result = page_table.MapMemory(dst_addr, src_addr, size);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) {
|
||||
if (UnlockHomebrewInPlaceDeviceSharedSource(process, src_addr, size, result).IsSuccess()) {
|
||||
const auto retry_result = page_table.MapMemory(dst_addr, src_addr, size);
|
||||
if (retry_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: svcMapMemory retry failed after "
|
||||
"DeviceShared cleanup dst=0x{:016X}, src=0x{:016X}, size=0x{:X}, "
|
||||
"result={:#X}",
|
||||
dst_addr, src_addr, size, retry_result.raw);
|
||||
}
|
||||
R_RETURN(retry_result);
|
||||
}
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
R_RETURN(page_table.MapMemory(dst_addr, src_addr, size));
|
||||
}
|
||||
|
||||
/// Unmaps a region that was previously mapped with svcMapMemory
|
||||
@@ -350,23 +150,14 @@ Result UnmapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
auto& process = GetCurrentProcess(system.Kernel());
|
||||
auto& page_table = process.GetPageTable();
|
||||
auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
|
||||
|
||||
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
|
||||
result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto result = page_table.UnmapMemory(dst_addr, src_addr, size);
|
||||
if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad in-place: svcUnmapMemory failed dst=0x{:016X}, "
|
||||
"src=0x{:016X}, size=0x{:X}, result={:#X}",
|
||||
dst_addr, src_addr, size, result.raw);
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
R_RETURN(page_table.UnmapMemory(dst_addr, src_addr, size));
|
||||
}
|
||||
|
||||
Result SetMemoryPermission64(Core::System& system, uint64_t address, uint64_t size,
|
||||
|
||||
@@ -4,216 +4,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/input.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/nvdrv/nvdrv_interface.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/resource_manager.h"
|
||||
|
||||
namespace Kernel::Svc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t HomebrewNextLoadPathSize = 0x200;
|
||||
constexpr size_t HomebrewNextLoadArgvSize = 0x800;
|
||||
|
||||
std::string ReadHomebrewString(Core::Memory::Memory& memory, KProcessAddress address,
|
||||
size_t max_size) {
|
||||
if (GetInteger(address) == 0) {
|
||||
return {};
|
||||
}
|
||||
return memory.ReadCString(Common::ProcessAddress{GetInteger(address)}, max_size);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// Exits the current process
|
||||
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
void ExitProcess(Core::System& system) {
|
||||
auto* current_process = GetCurrentProcessPointer(system.Kernel());
|
||||
auto* current_thread = GetCurrentThreadPointer(system.Kernel());
|
||||
|
||||
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessId());
|
||||
const auto next_load_path_addr = current_process->GetHomebrewNextLoadPathAddr();
|
||||
const auto next_load_argv_addr = current_process->GetHomebrewNextLoadArgvAddr();
|
||||
if (GetInteger(next_load_path_addr) != 0) {
|
||||
auto& memory = current_process->GetMemory();
|
||||
const auto next_load_path =
|
||||
ReadHomebrewString(memory, next_load_path_addr, HomebrewNextLoadPathSize);
|
||||
const auto next_load_argv =
|
||||
ReadHomebrewString(memory, next_load_argv_addr, HomebrewNextLoadArgvSize);
|
||||
if (!next_load_path.empty()) {
|
||||
auto guest_path = Common::FS::SanitizePath(next_load_path);
|
||||
constexpr std::string_view SdmcPrefix{"sdmc:"};
|
||||
FileSys::VirtualFile file{};
|
||||
|
||||
const bool is_sdmc_path = guest_path.rfind(SdmcPrefix, 0) == 0;
|
||||
const bool is_absolute_guest_path = !guest_path.empty() && guest_path.front() == '/';
|
||||
if (is_sdmc_path || is_absolute_guest_path) {
|
||||
auto relative_path =
|
||||
is_sdmc_path ? guest_path.substr(SdmcPrefix.size()) : guest_path;
|
||||
while (!relative_path.empty() && relative_path.front() == '/') {
|
||||
relative_path.erase(relative_path.begin());
|
||||
}
|
||||
|
||||
const auto host_path = Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir) /
|
||||
std::filesystem::path{Common::FS::ToU8String(relative_path)};
|
||||
const auto host_path_string = Common::FS::PathToUTF8String(host_path);
|
||||
file = Core::GetGameFileFromPath(system.GetFilesystem(), host_path_string);
|
||||
if (!file) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: failed to open guest_path='{}', host_path='{}'",
|
||||
next_load_path, host_path_string);
|
||||
}
|
||||
} else {
|
||||
file = Core::GetGameFileFromPath(system.GetFilesystem(), guest_path);
|
||||
if (!file) {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: failed to open guest_path='{}'",
|
||||
next_load_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (file) {
|
||||
const auto nvdrv =
|
||||
system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv:s");
|
||||
if (!nvdrv) {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: NVDRV service unavailable for reset");
|
||||
} else {
|
||||
nvdrv->GetModule()->ResetForProcess(current_process);
|
||||
}
|
||||
|
||||
auto& page_table = current_process->GetPageTable();
|
||||
const u64 heap_start = GetInteger(page_table.GetHeapRegionStart());
|
||||
const u64 heap_size = page_table.GetHeapRegionSize();
|
||||
const u64 heap_end = heap_start + heap_size;
|
||||
if (heap_start != 0 && heap_size != 0 && heap_end > heap_start) {
|
||||
struct DeviceSharedBlock {
|
||||
u64 address;
|
||||
u64 size;
|
||||
u16 device_use_count;
|
||||
};
|
||||
|
||||
std::vector<DeviceSharedBlock> blocks;
|
||||
for (u64 cursor = heap_start; cursor < heap_end;) {
|
||||
KMemoryInfo info;
|
||||
PageInfo page_info;
|
||||
const auto query_result =
|
||||
page_table.QueryInfo(std::addressof(info), std::addressof(page_info),
|
||||
cursor);
|
||||
if (query_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup query failed "
|
||||
"address=0x{:016X}, result={:#X}",
|
||||
cursor, query_result.raw);
|
||||
break;
|
||||
}
|
||||
|
||||
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), heap_end);
|
||||
if (block_end <= cursor) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup walk stalled "
|
||||
"cursor=0x{:016X}, block=0x{:016X}/0x{:X}",
|
||||
cursor, info.GetAddress(), info.GetSize());
|
||||
break;
|
||||
}
|
||||
|
||||
const bool is_device_shared =
|
||||
True(info.GetState() & KMemoryState::FlagCanDeviceMap) &&
|
||||
info.GetAttribute() == KMemoryAttribute::DeviceShared &&
|
||||
info.m_device_use_count > 0;
|
||||
if (is_device_shared) {
|
||||
blocks.push_back(DeviceSharedBlock{
|
||||
.address = cursor,
|
||||
.size = block_end - cursor,
|
||||
.device_use_count = info.m_device_use_count,
|
||||
});
|
||||
}
|
||||
cursor = block_end;
|
||||
}
|
||||
|
||||
for (const auto& block : blocks) {
|
||||
for (u16 unlock = 0; unlock < block.device_use_count; unlock++) {
|
||||
const auto unlock_result =
|
||||
page_table.UnlockForDeviceAddressSpace(block.address, block.size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: DeviceShared heap cleanup unlock failed "
|
||||
"address=0x{:016X}, size=0x{:X}, remaining={}, "
|
||||
"result={:#X}",
|
||||
block.address, block.size,
|
||||
block.device_use_count - unlock - 1, unlock_result.raw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Loader::LoadNroInPlace(system, *current_process, *current_thread, file,
|
||||
next_load_path, next_load_argv)) {
|
||||
const auto aruid = current_process->GetProcessId();
|
||||
if (const auto hid =
|
||||
system.ServiceManager().GetService<Service::HID::IHidServer>("hid")) {
|
||||
const auto resource_manager = hid->GetResourceManager();
|
||||
resource_manager->UnregisterAppletResourceUserId(aruid);
|
||||
const auto register_result =
|
||||
resource_manager->RegisterAppletResourceUserId(aruid, true);
|
||||
if (register_result.IsError()) {
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
"NextLoad: failed to register HID applet resource "
|
||||
"aruid={}, result={:#X}",
|
||||
aruid, register_result.raw);
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Kernel_SVC, "NextLoad: HID service unavailable for reset");
|
||||
}
|
||||
|
||||
auto& hid_core = system.HIDCore();
|
||||
hid_core.DisableAllControllerConfiguration();
|
||||
hid_core.SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
|
||||
hid_core.ReloadInputDevices();
|
||||
|
||||
const auto activate_controller = [&](Core::HID::NpadIdType npad_id) {
|
||||
auto* controller = hid_core.GetEmulatedController(npad_id);
|
||||
if (controller == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
|
||||
Common::Input::PollingMode::Active);
|
||||
};
|
||||
|
||||
activate_controller(Core::HID::NpadIdType::Player1);
|
||||
activate_controller(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
system.Kernel().CurrentPhysicalCore().LoadContext(current_thread);
|
||||
|
||||
const auto& context = current_thread->GetContext();
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
args[i] = context.r[i];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_MSG(current_process->GetState() == KProcess::State::Running,
|
||||
"Process has already exited");
|
||||
|
||||
@@ -331,8 +132,8 @@ Result TerminateProcess(Core::System& system, Handle process_handle) {
|
||||
R_THROW(ResultNotImplemented);
|
||||
}
|
||||
|
||||
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess(system, args);
|
||||
void ExitProcess64(Core::System& system) {
|
||||
ExitProcess(system);
|
||||
}
|
||||
|
||||
Result GetProcessId64(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
|
||||
@@ -363,8 +164,8 @@ Result GetProcessInfo64(Core::System& system, int64_t* out_info, Handle process_
|
||||
R_RETURN(GetProcessInfo(system, out_info, process_handle, info_type));
|
||||
}
|
||||
|
||||
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
|
||||
ExitProcess(system, args);
|
||||
void ExitProcess64From32(Core::System& system) {
|
||||
ExitProcess(system);
|
||||
}
|
||||
|
||||
Result GetProcessId64From32(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
@@ -154,20 +156,7 @@ Result IApplicationFunctions::GetDesiredLanguage(Out<u64> out_language_code) {
|
||||
// Default to 0 (all languages supported)
|
||||
u32 supported_languages = 0;
|
||||
|
||||
const auto res = [this] {
|
||||
const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
if (res.first != nullptr) {
|
||||
supported_languages = res.first->GetSupportedLanguages();
|
||||
}
|
||||
@@ -205,20 +194,7 @@ Result IApplicationFunctions::SetTerminateResult(Result terminate_result) {
|
||||
Result IApplicationFunctions::GetDisplayVersion(Out<DisplayVersion> out_display_version) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
const auto res = [this] {
|
||||
const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
if (res.first != nullptr) {
|
||||
const auto& version = res.first->GetVersionString();
|
||||
std::memcpy(out_display_version->string.data(), version.data(),
|
||||
@@ -347,8 +323,21 @@ Result IApplicationFunctions::NotifyRunning(Out<bool> out_became_running) {
|
||||
}
|
||||
|
||||
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
*out_pseudo_device_id = {};
|
||||
LOG_WARNING(Service_AM, "(stubbed)");
|
||||
|
||||
// This should be hashed with the device specific hash
|
||||
// for now this will do
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
|
||||
u8 hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int hash_len = 0;
|
||||
auto const seed = res.first->raw.seed_for_pseudo_device_id;
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
auto const algorithm = EVP_sha1();
|
||||
EVP_DigestInit_ex(ctx, algorithm, nullptr);
|
||||
EVP_DigestUpdate(ctx, &seed, sizeof(seed));
|
||||
EVP_DigestFinal_ex(ctx, hash, &hash_len);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
*out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span<u8, 20>{hash, std::size(hash)});
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -252,19 +252,7 @@ Result ILibraryAppletSelfAccessor::GetMainAppletApplicationDesiredLanguage(
|
||||
// Default to 0 (all languages supported)
|
||||
u32 supported_languages = 0;
|
||||
|
||||
const auto res = [this, identity] {
|
||||
const FileSys::PatchManager pm{identity.application_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(identity.application_id),
|
||||
system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}();
|
||||
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, identity.application_id);
|
||||
|
||||
if (res.first != nullptr) {
|
||||
supported_languages = res.first->GetSupportedLanguages();
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -258,7 +257,7 @@ Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
|
||||
npath.remove_prefix(1);
|
||||
}
|
||||
|
||||
auto file = backing->GetFileRelative(npath, mode);
|
||||
auto file = backing->GetFileRelative(npath);
|
||||
if (file == nullptr) {
|
||||
return FileSys::ResultPathNotFound;
|
||||
}
|
||||
@@ -334,16 +333,14 @@ FileSystemController::~FileSystemController() = default;
|
||||
|
||||
Result FileSystemController::RegisterProcess(
|
||||
ProcessId process_id, ProgramId program_id,
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory, std::string homebrew_initial_cwd) {
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory) {
|
||||
std::scoped_lock lk{registration_lock};
|
||||
|
||||
registrations.insert_or_assign(process_id,
|
||||
Registration{
|
||||
.program_id = program_id,
|
||||
.romfs_factory = std::move(romfs_factory),
|
||||
.save_data_factory = CreateSaveDataFactory(program_id),
|
||||
.homebrew_initial_cwd = std::move(homebrew_initial_cwd),
|
||||
});
|
||||
registrations.emplace(process_id, Registration{
|
||||
.program_id = program_id,
|
||||
.romfs_factory = std::move(romfs_factory),
|
||||
.save_data_factory = CreateSaveDataFactory(program_id),
|
||||
});
|
||||
|
||||
LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
|
||||
return ResultSuccess;
|
||||
@@ -351,8 +348,7 @@ Result FileSystemController::RegisterProcess(
|
||||
|
||||
Result FileSystemController::OpenProcess(
|
||||
ProgramId* out_program_id, std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id,
|
||||
std::string* out_homebrew_initial_cwd) {
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id) {
|
||||
std::scoped_lock lk{registration_lock};
|
||||
|
||||
const auto it = registrations.find(process_id);
|
||||
@@ -365,9 +361,6 @@ Result FileSystemController::OpenProcess(
|
||||
std::make_shared<SaveDataController>(system, it->second.save_data_factory);
|
||||
*out_romfs_controller =
|
||||
std::make_shared<RomFsController>(it->second.romfs_factory, it->second.program_id);
|
||||
if (out_homebrew_initial_cwd != nullptr) {
|
||||
*out_homebrew_initial_cwd = it->second.homebrew_initial_cwd;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
@@ -72,12 +71,11 @@ public:
|
||||
~FileSystemController();
|
||||
|
||||
Result RegisterProcess(ProcessId process_id, ProgramId program_id,
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& factory,
|
||||
std::string homebrew_initial_cwd = {});
|
||||
std::shared_ptr<FileSys::RomFSFactory>&& factory);
|
||||
Result OpenProcess(ProgramId* out_program_id,
|
||||
std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller,
|
||||
ProcessId process_id, std::string* out_homebrew_initial_cwd = nullptr);
|
||||
std::shared_ptr<SaveDataController>* out_save_data_controller,
|
||||
std::shared_ptr<RomFsController>* out_romfs_controller,
|
||||
ProcessId process_id);
|
||||
void SetPackedUpdate(ProcessId process_id, FileSys::VirtualFile update_raw);
|
||||
|
||||
std::shared_ptr<SaveDataController> OpenSaveDataController();
|
||||
@@ -138,7 +136,6 @@ private:
|
||||
ProgramId program_id;
|
||||
std::shared_ptr<FileSys::RomFSFactory> romfs_factory;
|
||||
std::shared_ptr<FileSys::SaveDataFactory> save_data_factory;
|
||||
std::string homebrew_initial_cwd;
|
||||
};
|
||||
|
||||
std::mutex registration_lock;
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/fssrv/fssrv_sf_path.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
@@ -16,29 +13,10 @@
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
static std::string ResolveHomebrewCwdRootAlias(std::string path,
|
||||
std::string_view homebrew_initial_cwd) {
|
||||
if (homebrew_initial_cwd.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const std::string normalized_path = Common::FS::SanitizePath(path);
|
||||
if (normalized_path.empty() || normalized_path == "/" ||
|
||||
normalized_path != homebrew_initial_cwd ||
|
||||
path.size() != normalized_path.size() + 2 ||
|
||||
path.substr(0, normalized_path.size()) != normalized_path ||
|
||||
path.substr(normalized_path.size()) != "//") {
|
||||
return path;
|
||||
}
|
||||
|
||||
return "/";
|
||||
}
|
||||
|
||||
IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
|
||||
std::string homebrew_initial_cwd_)
|
||||
: ServiceFramework{system_, "IFileSystem"},
|
||||
backend{std::make_unique<FileSys::Fsa::IFileSystem>(dir_)},
|
||||
size_getter{std::move(size_getter_)}, homebrew_initial_cwd{std::move(homebrew_initial_cwd_)} {
|
||||
IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_)
|
||||
: ServiceFramework{system_, "IFileSystem"}, backend{std::make_unique<FileSys::Fsa::IFileSystem>(
|
||||
dir_)},
|
||||
size_getter{std::move(size_getter_)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&IFileSystem::CreateFile>, "CreateFile"},
|
||||
{1, D<&IFileSystem::DeleteFile>, "DeleteFile"},
|
||||
@@ -65,8 +43,7 @@ Result IFileSystem::CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_H
|
||||
s32 option, s64 size) {
|
||||
LOG_DEBUG(Service_FS, "called. file={}, option={:#x}, size={:#08x}", path->str, option, size);
|
||||
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_RETURN(backend->CreateFile(FileSys::Path(fs_path.c_str()), size));
|
||||
R_RETURN(backend->CreateFile(FileSys::Path(path->str), size));
|
||||
}
|
||||
|
||||
Result IFileSystem::DeleteFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path) {
|
||||
@@ -117,8 +94,7 @@ Result IFileSystem::OpenFile(OutInterface<IFile> out_interface,
|
||||
LOG_DEBUG(Service_FS, "called. file={}, mode={}", path->str, mode);
|
||||
|
||||
FileSys::VirtualFile vfs_file{};
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(fs_path.c_str()),
|
||||
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(path->str),
|
||||
static_cast<FileSys::OpenMode>(mode)));
|
||||
|
||||
*out_interface = std::make_shared<IFile>(system, vfs_file);
|
||||
@@ -131,8 +107,7 @@ Result IFileSystem::OpenDirectory(OutInterface<IDirectory> out_interface,
|
||||
LOG_DEBUG(Service_FS, "called. directory={}, mode={}", path->str, mode);
|
||||
|
||||
FileSys::VirtualDir vfs_dir{};
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(fs_path.c_str()),
|
||||
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(path->str),
|
||||
static_cast<FileSys::OpenDirectoryMode>(mode)));
|
||||
|
||||
*out_interface = std::make_shared<IDirectory>(system, vfs_dir,
|
||||
@@ -145,8 +120,7 @@ Result IFileSystem::GetEntryType(
|
||||
LOG_DEBUG(Service_FS, "called. file={}", path->str);
|
||||
|
||||
FileSys::DirectoryEntryType vfs_entry_type{};
|
||||
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
|
||||
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(fs_path.c_str())));
|
||||
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(path->str)));
|
||||
|
||||
*out_type = static_cast<u32>(vfs_entry_type);
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
// 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 <string>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/fsa/fs_i_filesystem.h"
|
||||
@@ -28,8 +23,7 @@ class IDirectory;
|
||||
|
||||
class IFileSystem final : public ServiceFramework<IFileSystem> {
|
||||
public:
|
||||
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
|
||||
std::string homebrew_initial_cwd_ = {});
|
||||
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_);
|
||||
|
||||
Result CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, s32 option,
|
||||
s64 size);
|
||||
@@ -61,7 +55,6 @@ public:
|
||||
private:
|
||||
std::unique_ptr<FileSys::Fsa::IFileSystem> backend;
|
||||
SizeGetter size_getter;
|
||||
std::string homebrew_initial_cwd;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -192,9 +192,8 @@ Result FSP_SRV::SetCurrentProcess(ClientProcessId pid) {
|
||||
|
||||
LOG_DEBUG(Service_FS, "called. current_process_id={:#016x}", current_process_id);
|
||||
|
||||
homebrew_initial_cwd.clear();
|
||||
R_RETURN(fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller,
|
||||
current_process_id, &homebrew_initial_cwd));
|
||||
R_RETURN(
|
||||
fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id));
|
||||
}
|
||||
|
||||
Result FSP_SRV::OpenFileSystemWithPatch(OutInterface<IFileSystem> out_interface,
|
||||
@@ -225,8 +224,7 @@ Result FSP_SRV::OpenSdCardFileSystem(OutInterface<IFileSystem> out_interface) {
|
||||
fsc.OpenSDMC(&sdmc_dir);
|
||||
|
||||
*out_interface = std::make_shared<IFileSystem>(
|
||||
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard),
|
||||
homebrew_initial_cwd);
|
||||
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "core/file_sys/fs_save_data_types.h"
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/filesystem/fsp/fsp_types.h"
|
||||
@@ -124,7 +123,6 @@ private:
|
||||
u32 access_log_program_index = 0;
|
||||
AccessLogMode access_log_mode = AccessLogMode::None;
|
||||
u64 program_id = 0;
|
||||
std::string homebrew_initial_cwd;
|
||||
std::shared_ptr<SaveDataController> save_data_controller;
|
||||
std::shared_ptr<RomFsController> romfs_controller;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
@@ -45,16 +40,6 @@ Container::Container(Tegra::Host1x::Host1x& host1x_) {
|
||||
|
||||
Container::~Container() = default;
|
||||
|
||||
static bool IsSameProcess(Kernel::KProcess* lhs, Kernel::KProcess* rhs) {
|
||||
if (lhs == rhs) {
|
||||
return true;
|
||||
}
|
||||
if (lhs == nullptr || rhs == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return lhs->GetProcessId() == rhs->GetProcessId();
|
||||
}
|
||||
|
||||
SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
using namespace Common::Literals;
|
||||
|
||||
@@ -63,7 +48,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
if (!session.is_active) {
|
||||
continue;
|
||||
}
|
||||
if (IsSameProcess(session.process, process)) {
|
||||
if (session.process == process) {
|
||||
session.ref_count++;
|
||||
return session.id;
|
||||
}
|
||||
@@ -131,15 +116,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
|
||||
|
||||
void Container::CloseSession(SessionId session_id) {
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
if (session_id.id >= impl->sessions.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& session = impl->sessions[session_id.id];
|
||||
if (!session.is_active || session.ref_count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (--session.ref_count > 0) {
|
||||
return;
|
||||
}
|
||||
@@ -157,73 +134,6 @@ void Container::CloseSession(SessionId session_id) {
|
||||
impl->id_pool.emplace_front(session_id.id);
|
||||
}
|
||||
|
||||
size_t Container::CloseSessions(std::span<const SessionId> session_ids) {
|
||||
std::vector<SessionId> valid_session_ids;
|
||||
valid_session_ids.reserve(session_ids.size());
|
||||
|
||||
{
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto session_id : session_ids) {
|
||||
if (session_id.id >= impl->sessions.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& session = impl->sessions[session_id.id];
|
||||
if (!session.is_active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto duplicate = std::ranges::any_of(
|
||||
valid_session_ids, [session_id](const auto candidate) {
|
||||
return candidate.id == session_id.id;
|
||||
});
|
||||
if (duplicate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
session.ref_count = 1;
|
||||
valid_session_ids.push_back(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto session_id : valid_session_ids) {
|
||||
CloseSession(session_id);
|
||||
}
|
||||
|
||||
return valid_session_ids.size();
|
||||
}
|
||||
|
||||
std::vector<SessionId> Container::GetSessionIdsForProcess(Kernel::KProcess* process) {
|
||||
std::vector<SessionId> session_ids;
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto& session : impl->sessions) {
|
||||
if (!session.is_active || !IsSameProcess(session.process, process)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
session_ids.push_back(session.id);
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
std::vector<SessionId> Container::GetActiveSessionIds() const {
|
||||
std::vector<SessionId> session_ids;
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
for (const auto& session : impl->sessions) {
|
||||
if (session.is_active) {
|
||||
session_ids.push_back(session.id);
|
||||
}
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
bool Container::IsSessionActive(SessionId session_id) const {
|
||||
std::scoped_lock lk(impl->session_guard);
|
||||
return session_id.id < impl->sessions.size() && impl->sessions[session_id.id].is_active;
|
||||
}
|
||||
|
||||
Session* Container::GetSession(SessionId session_id) {
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
return &impl->sessions[session_id.id];
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <cstddef>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <vector>
|
||||
|
||||
#include "core/device_memory_manager.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
@@ -62,10 +59,6 @@ public:
|
||||
|
||||
SessionId OpenSession(Kernel::KProcess* process);
|
||||
void CloseSession(SessionId id);
|
||||
size_t CloseSessions(std::span<const SessionId> session_ids);
|
||||
std::vector<SessionId> GetSessionIdsForProcess(Kernel::KProcess* process);
|
||||
std::vector<SessionId> GetActiveSessionIds() const;
|
||||
bool IsSessionActive(SessionId id) const;
|
||||
|
||||
Session* GetSession(SessionId id);
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/core/heap_mapper.h"
|
||||
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||
@@ -328,65 +326,19 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
|
||||
}
|
||||
|
||||
void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
|
||||
auto* session = core.GetSession(session_id);
|
||||
auto* process = session != nullptr ? session->process : nullptr;
|
||||
auto handle_ids = [&] {
|
||||
auto handles_copy = [&] {
|
||||
std::scoped_lock lk{handles_lock};
|
||||
std::vector<Handle::Id> ids;
|
||||
ids.reserve(handles.size());
|
||||
|
||||
for (const auto& entry : handles) {
|
||||
ids.push_back(entry.first);
|
||||
}
|
||||
|
||||
return ids;
|
||||
return handles;
|
||||
}();
|
||||
|
||||
for (const auto id : handle_ids) {
|
||||
bool unlocked_pages = false;
|
||||
while (true) {
|
||||
bool last_user_reference = false;
|
||||
VAddr address = 0;
|
||||
size_t size = 0;
|
||||
{
|
||||
const auto handle = GetHandle(id);
|
||||
if (!handle) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::scoped_lock lk{handle->mutex};
|
||||
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
last_user_reference = handle->dupes == 1;
|
||||
address = handle->address;
|
||||
size = handle->size;
|
||||
}
|
||||
|
||||
const auto free_info = FreeHandle(id, false);
|
||||
if (!free_info) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!unlocked_pages && process != nullptr && address != 0 && size != 0 &&
|
||||
(free_info->can_unlock || last_user_reference)) {
|
||||
const auto unlock_result =
|
||||
process->GetPageTable().UnlockForDeviceAddressSpace(address, size);
|
||||
if (unlock_result.IsError()) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"NextLoad: nvmap session cleanup unlock failed, "
|
||||
"handle={}, session={}, address=0x{:016X}, size=0x{:X}, "
|
||||
"result={:#X}",
|
||||
id, session_id.id, address, size, unlock_result.raw);
|
||||
}
|
||||
unlocked_pages = true;
|
||||
}
|
||||
|
||||
if (last_user_reference) {
|
||||
break;
|
||||
for (auto& [id, handle] : handles_copy) {
|
||||
{
|
||||
std::scoped_lock lk{handle->mutex};
|
||||
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
FreeHandle(id, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
@@ -86,11 +87,16 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
|
||||
}
|
||||
|
||||
system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences));
|
||||
Common::ADPF::ReportFrameInterval();
|
||||
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||
system.GetPerfStats().EndSystemFrame();
|
||||
system.GetPerfStats().BeginSystemFrame();
|
||||
}
|
||||
|
||||
void nvdisp_disp0::WaitForComposite() {
|
||||
system.GPU().WaitForComposite();
|
||||
}
|
||||
|
||||
Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
|
||||
LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
|
||||
return nullptr;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -37,6 +40,8 @@ public:
|
||||
/// Performs a screen flip, compositing each buffer.
|
||||
void Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers);
|
||||
|
||||
void WaitForComposite();
|
||||
|
||||
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||
@@ -138,9 +133,6 @@ DeviceFD Module::Open(const std::string& device_name, NvCore::SessionId session_
|
||||
auto device = builder(fd)->second;
|
||||
|
||||
device->OnOpen(session_id, fd);
|
||||
if (container.IsSessionActive(session_id)) {
|
||||
open_file_sessions.emplace(fd, session_id);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
@@ -212,7 +204,6 @@ NvResult Module::Close(DeviceFD fd) {
|
||||
itr->second->OnClose(fd);
|
||||
|
||||
open_files.erase(itr);
|
||||
open_file_sessions.erase(fd);
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
@@ -237,98 +228,4 @@ NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
static bool ContainsSession(std::span<const NvCore::SessionId> session_ids,
|
||||
NvCore::SessionId session_id) {
|
||||
return std::ranges::any_of(session_ids, [session_id](const auto candidate) {
|
||||
return candidate.id == session_id.id;
|
||||
});
|
||||
}
|
||||
|
||||
static void AppendUniqueSession(std::vector<NvCore::SessionId>& session_ids,
|
||||
NvCore::SessionId session_id) {
|
||||
if (!ContainsSession(session_ids, session_id)) {
|
||||
session_ids.push_back(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Module::CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids) {
|
||||
std::vector<DeviceFD> fds;
|
||||
fds.reserve(open_file_sessions.size());
|
||||
|
||||
for (const auto& [fd, session_id] : open_file_sessions) {
|
||||
if (ContainsSession(session_ids, session_id)) {
|
||||
fds.push_back(fd);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto fd : fds) {
|
||||
Close(fd);
|
||||
}
|
||||
|
||||
return fds.size();
|
||||
}
|
||||
|
||||
void Module::CloseSession(NvCore::SessionId session_id) {
|
||||
container.CloseSession(session_id);
|
||||
}
|
||||
|
||||
void Module::TrackSessionAruid(NvCore::SessionId session_id, u64 aruid) {
|
||||
const bool active = container.IsSessionActive(session_id);
|
||||
if (active) {
|
||||
session_aruids[session_id.id] = aruid;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NvCore::SessionId> Module::GetSessionIdsForAruid(u64 aruid) const {
|
||||
std::vector<NvCore::SessionId> session_ids;
|
||||
for (const auto& [session_id, session_aruid] : session_aruids) {
|
||||
if (session_aruid == aruid) {
|
||||
session_ids.push_back(NvCore::SessionId{session_id});
|
||||
}
|
||||
}
|
||||
|
||||
return session_ids;
|
||||
}
|
||||
|
||||
size_t Module::ResetForProcess(Kernel::KProcess* process) {
|
||||
const auto process_id = process != nullptr ? process->GetProcessId() : 0;
|
||||
auto session_ids = container.GetSessionIdsForProcess(process);
|
||||
|
||||
if (process_id != 0) {
|
||||
for (const auto session_id : GetSessionIdsForAruid(process_id)) {
|
||||
AppendUniqueSession(session_ids, session_id);
|
||||
}
|
||||
}
|
||||
|
||||
const auto active_session_ids = container.GetActiveSessionIds();
|
||||
const auto active_before = active_session_ids.size();
|
||||
const bool has_active_candidate =
|
||||
std::ranges::any_of(session_ids, [this](const auto session_id) {
|
||||
return container.IsSessionActive(session_id);
|
||||
});
|
||||
bool used_active_sessions = false;
|
||||
if (!has_active_candidate && !active_session_ids.empty()) {
|
||||
for (const auto session_id : active_session_ids) {
|
||||
AppendUniqueSession(session_ids, session_id);
|
||||
}
|
||||
used_active_sessions = true;
|
||||
}
|
||||
|
||||
const auto closed_files = CloseFilesForSessions(session_ids);
|
||||
const auto closed_sessions = container.CloseSessions(session_ids);
|
||||
for (const auto session_id : session_ids) {
|
||||
if (!container.IsSessionActive(session_id)) {
|
||||
session_aruids.erase(session_id.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (used_active_sessions) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"NextLoad: NVDRV reset used active sessions because process-owned "
|
||||
"sessions were not found, process_id={}, sessions={}, files={}, active_before={}",
|
||||
process_id, closed_sessions, closed_files, active_before);
|
||||
}
|
||||
return closed_sessions;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
@@ -27,7 +26,6 @@ class System;
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KProcess;
|
||||
}
|
||||
|
||||
namespace Service::Nvidia {
|
||||
@@ -91,9 +89,6 @@ public:
|
||||
NvResult Close(DeviceFD fd);
|
||||
|
||||
NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);
|
||||
void CloseSession(NvCore::SessionId session_id);
|
||||
void TrackSessionAruid(NvCore::SessionId session_id, u64 aruid);
|
||||
size_t ResetForProcess(Kernel::KProcess* process);
|
||||
|
||||
NvCore::Container& GetContainer() {
|
||||
return container;
|
||||
@@ -111,17 +106,12 @@ private:
|
||||
using FilesContainerType = ankerl::unordered_dense::map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
|
||||
/// Mapping of file descriptors to the devices they reference.
|
||||
FilesContainerType open_files;
|
||||
ankerl::unordered_dense::map<DeviceFD, NvCore::SessionId> open_file_sessions;
|
||||
ankerl::unordered_dense::map<size_t, u64> session_aruids;
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
EventInterface events_interface;
|
||||
|
||||
ankerl::unordered_dense::map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
|
||||
|
||||
size_t CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids);
|
||||
std::vector<NvCore::SessionId> GetSessionIdsForAruid(u64 aruid) const;
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system);
|
||||
|
||||
@@ -212,10 +212,7 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) {
|
||||
void NVDRV::SetAruid(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
pid = rp.Pop<u64>();
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#X}", pid);
|
||||
if (is_initialized) {
|
||||
nvdrv->TrackSessionAruid(session_id, pid);
|
||||
}
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#x}", pid);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
@@ -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 2024 yuzu Emulator Project
|
||||
@@ -57,18 +57,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
// Set default speed limit to 100%.
|
||||
*out_speed_scale = 1.0f;
|
||||
|
||||
// If no layers are available, skip the logic.
|
||||
bool any_visible = false;
|
||||
for (auto& layer : display.stack.layers) {
|
||||
if (layer->visible) {
|
||||
any_visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!any_visible) {
|
||||
*out_speed_scale = 1.0f;
|
||||
return 1;
|
||||
}
|
||||
nvdisp.WaitForComposite();
|
||||
this->ReleaseFramebuffersLocked(display);
|
||||
|
||||
// Determine the number of vsync periods to wait before composing again.
|
||||
std::optional<s32> swap_interval{};
|
||||
@@ -158,55 +148,30 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
|
||||
nvdisp.Composite(composition_stack);
|
||||
}
|
||||
|
||||
// Batch framebuffer releases, instead of one-into-one.
|
||||
std::vector<std::pair<Layer*, Framebuffer*>> to_release;
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired)
|
||||
continue;
|
||||
|
||||
auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer)
|
||||
continue;
|
||||
|
||||
// Overlay layers always release after every compose
|
||||
// Non-overlay layers release based on their swap interval
|
||||
if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) {
|
||||
to_release.emplace_back(layer.get(), &framebuffer);
|
||||
}
|
||||
}
|
||||
for (auto& [layer, framebuffer] : to_release) {
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence());
|
||||
framebuffer->is_acquired = false;
|
||||
}
|
||||
|
||||
// Advance by 1 frame (60 FPS compositing)
|
||||
m_frame_number += 1;
|
||||
|
||||
// Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
|
||||
return 1;
|
||||
}
|
||||
|
||||
void HardwareComposer::ReleaseFramebuffersLocked(Display& display) {
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (!framebuffer.is_acquired) {
|
||||
// Already released.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
const auto layer = display.stack.FindLayer(layer_id);
|
||||
if (!layer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
|
||||
// Skip overlay layers as they were already released above
|
||||
if (layer->is_overlay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: support release fence
|
||||
// This is needed to prevent screen tearing
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
if (!layer->is_overlay && framebuffer.release_frame_number > m_frame_number) {
|
||||
continue;
|
||||
}
|
||||
|
||||
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
|
||||
|
||||
@@ -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 2024 yuzu Emulator Project
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
private:
|
||||
bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer);
|
||||
CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id);
|
||||
void ReleaseFramebuffersLocked(Display& display);
|
||||
};
|
||||
|
||||
} // namespace Service::Nvnflinger
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/adpf.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/vi/conductor.h"
|
||||
@@ -14,6 +18,8 @@
|
||||
|
||||
constexpr auto FrameNs = std::chrono::nanoseconds{1000000000 / 60};
|
||||
|
||||
constexpr s64 UNLOCKED_TARGET_DIVISOR = 4;
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
Conductor::Conductor(Core::System& system, Container& container, DisplayList& displays)
|
||||
@@ -68,6 +74,9 @@ void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) {
|
||||
}
|
||||
|
||||
void Conductor::ProcessVsync() {
|
||||
Common::PollThreadPolicies();
|
||||
Common::ADPF::SetTargetWorkDuration(std::chrono::nanoseconds{this->GetFramePeriodNs()});
|
||||
|
||||
for (auto& [display_id, manager] : m_vsync_managers) {
|
||||
m_container.ComposeOnDisplay(&m_swap_interval, &m_compose_speed_scale, display_id);
|
||||
manager.SignalVsync(m_system.Kernel());
|
||||
@@ -76,6 +85,8 @@ 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();
|
||||
@@ -114,4 +125,25 @@ s64 Conductor::GetNextTicks() const {
|
||||
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
|
||||
}
|
||||
|
||||
s64 Conductor::GetFramePeriodNs() const {
|
||||
const auto& settings = Settings::values;
|
||||
f32 speed_scale = 1.f;
|
||||
bool unlocked = false;
|
||||
if (settings.use_multi_core.GetValue()) {
|
||||
if (settings.use_speed_limit.GetValue()) {
|
||||
speed_scale = 100.f / Settings::SpeedLimit();
|
||||
} else {
|
||||
unlocked = true;
|
||||
}
|
||||
}
|
||||
speed_scale /= m_compose_speed_scale;
|
||||
|
||||
const f32 effective_fps = 60.f / static_cast<f32>(m_swap_interval);
|
||||
s64 period = static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
|
||||
if (unlocked) {
|
||||
period /= UNLOCKED_TARGET_DIVISOR;
|
||||
}
|
||||
return std::clamp<s64>(period, 1'000'000, 100'000'000);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -44,6 +44,7 @@ private:
|
||||
void ProcessVsync();
|
||||
void VsyncThread(std::stop_token token);
|
||||
s64 GetNextTicks() const;
|
||||
s64 GetFramePeriodNs() const;
|
||||
|
||||
private:
|
||||
Core::System& m_system;
|
||||
|
||||
@@ -40,7 +40,6 @@ namespace Network {
|
||||
namespace {
|
||||
|
||||
enum class CallType {
|
||||
Connect,
|
||||
Send,
|
||||
Other,
|
||||
};
|
||||
@@ -132,7 +131,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
case WSAENOTCONN:
|
||||
return Errno::NOTCONN;
|
||||
case WSAEWOULDBLOCK:
|
||||
return call_type == CallType::Connect ? Errno::INPROGRESS : Errno::AGAIN;
|
||||
return Errno::AGAIN;
|
||||
case WSAECONNREFUSED:
|
||||
return Errno::CONNREFUSED;
|
||||
case WSAECONNABORTED:
|
||||
@@ -564,7 +563,6 @@ int TranslateTypeToNative(Type type) {
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(UDPLITE)
|
||||
#elif defined(_WIN32)
|
||||
#define NETWORK_PROTOCOL_TRANSLATE_LIST \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \
|
||||
/*NETWORK_PROTOCOL_TRANSLATE_ELEM(HOPOPTS)*/ \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \
|
||||
NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \
|
||||
@@ -890,7 +888,7 @@ Errno Socket::Connect(SockAddrIn addr_in) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
return GetAndLogLastError(CallType::Connect);
|
||||
return GetAndLogLastError();
|
||||
}
|
||||
|
||||
std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/loader/homebrew_nxlink.h"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stop_token>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Loader::HomebrewNxlink {
|
||||
namespace {
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
constexpr u16 NxlinkClientPort = 28771;
|
||||
constexpr size_t ReceiveBufferSize = 1024;
|
||||
constexpr size_t MaxLogLineSize = 16 * 1024;
|
||||
|
||||
struct LogConnection {
|
||||
explicit LogConnection(boost::asio::io_context& io_context_) : socket{io_context_} {}
|
||||
|
||||
tcp::socket socket;
|
||||
std::array<char, ReceiveBufferSize> buffer{};
|
||||
std::string line;
|
||||
};
|
||||
|
||||
struct LogServerState {
|
||||
LogServerState() : acceptor{io_context} {}
|
||||
|
||||
boost::asio::io_context io_context;
|
||||
tcp::acceptor acceptor;
|
||||
};
|
||||
|
||||
std::mutex server_mutex;
|
||||
std::shared_ptr<LogServerState> server_state;
|
||||
std::optional<std::jthread> server_thread;
|
||||
|
||||
std::optional<std::string> GetLastArgvToken(std::string_view argv_string) {
|
||||
while (!argv_string.empty() && argv_string.back() == '\0') {
|
||||
argv_string.remove_suffix(1);
|
||||
}
|
||||
|
||||
std::optional<std::string_view> last_token;
|
||||
bool in_token = false;
|
||||
bool quoted = false;
|
||||
size_t token_begin = 0;
|
||||
size_t token_size = 0;
|
||||
|
||||
for (size_t i = 0; i <= argv_string.size(); i++) {
|
||||
const char c = i < argv_string.size() ? argv_string[i] : '\0';
|
||||
|
||||
if (!in_token) {
|
||||
if (c == '\0' || std::isspace(static_cast<unsigned char>(c))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
in_token = true;
|
||||
token_size = 0;
|
||||
if (c == '"') {
|
||||
quoted = true;
|
||||
token_begin = i + 1;
|
||||
} else {
|
||||
quoted = false;
|
||||
token_begin = i;
|
||||
token_size = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool token_end =
|
||||
quoted ? c == '"' || c == '\0'
|
||||
: c == '\0' || std::isspace(static_cast<unsigned char>(c));
|
||||
if (token_end) {
|
||||
if (token_size != 0) {
|
||||
last_token = argv_string.substr(token_begin, token_size);
|
||||
}
|
||||
in_token = false;
|
||||
quoted = false;
|
||||
token_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
token_size++;
|
||||
}
|
||||
|
||||
if (!last_token) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::string{*last_token};
|
||||
}
|
||||
|
||||
void AppendArgvToken(std::string& argv_string, std::string_view token) {
|
||||
while (!argv_string.empty() && argv_string.back() == '\0') {
|
||||
argv_string.pop_back();
|
||||
}
|
||||
if (!argv_string.empty()) {
|
||||
argv_string.push_back(' ');
|
||||
}
|
||||
argv_string.append(token);
|
||||
}
|
||||
|
||||
void FlushLogLine(std::string& line) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
if (!line.empty()) {
|
||||
LOG_INFO(Loader, "{}", line);
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ConsumeLogBytes(LogConnection& connection, std::string_view bytes) {
|
||||
for (const char byte : bytes) {
|
||||
if (byte == '\n') {
|
||||
FlushLogLine(connection.line);
|
||||
continue;
|
||||
}
|
||||
|
||||
connection.line.push_back(byte);
|
||||
if (connection.line.size() >= MaxLogLineSize) {
|
||||
FlushLogLine(connection.line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StartRead(std::shared_ptr<LogConnection> connection) {
|
||||
connection->socket.async_read_some(
|
||||
boost::asio::buffer(connection->buffer),
|
||||
[connection](const boost::system::error_code& error, size_t bytes_read) {
|
||||
if (error.failed()) {
|
||||
FlushLogLine(connection->line);
|
||||
return;
|
||||
}
|
||||
|
||||
ConsumeLogBytes(*connection, {connection->buffer.data(), bytes_read});
|
||||
StartRead(connection);
|
||||
});
|
||||
}
|
||||
|
||||
void StartAccept(std::shared_ptr<LogServerState> state) {
|
||||
auto connection = std::make_shared<LogConnection>(state->io_context);
|
||||
state->acceptor.async_accept(
|
||||
connection->socket,
|
||||
[state, connection](const boost::system::error_code& error) {
|
||||
if (!error.failed()) {
|
||||
LOG_INFO(Loader, "Homebrew nxlink log client connected");
|
||||
StartRead(connection);
|
||||
}
|
||||
if (state->acceptor.is_open()) {
|
||||
StartAccept(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StartLogServer() {
|
||||
std::scoped_lock lock{server_mutex};
|
||||
if (server_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto state = std::make_shared<LogServerState>();
|
||||
const tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), NxlinkClientPort};
|
||||
|
||||
boost::system::error_code error;
|
||||
state->acceptor.open(endpoint.protocol(), error);
|
||||
if (!error.failed()) {
|
||||
state->acceptor.set_option(tcp::acceptor::reuse_address(true), error);
|
||||
}
|
||||
if (!error.failed()) {
|
||||
state->acceptor.bind(endpoint, error);
|
||||
}
|
||||
if (!error.failed()) {
|
||||
state->acceptor.listen(boost::asio::socket_base::max_listen_connections, error);
|
||||
}
|
||||
if (error.failed()) {
|
||||
LOG_WARNING(Loader, "Homebrew nxlink log server could not listen on 127.0.0.1:{}: {}",
|
||||
NxlinkClientPort, error.message());
|
||||
return;
|
||||
}
|
||||
|
||||
StartAccept(state);
|
||||
server_state = state;
|
||||
server_thread.emplace([state](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("HomebrewNxlink");
|
||||
std::stop_callback stop_callback{stop_token, [state] {
|
||||
boost::system::error_code ignored;
|
||||
state->acceptor.close(ignored);
|
||||
state->io_context.stop();
|
||||
}};
|
||||
|
||||
LOG_INFO(Loader, "Homebrew nxlink log server listening on 127.0.0.1:{}",
|
||||
NxlinkClientPort);
|
||||
state->io_context.run();
|
||||
LOG_INFO(Loader, "Homebrew nxlink log server stopped");
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsArgvMarker(std::string_view token) {
|
||||
if (token.size() != ArgvMarkerSize || token.substr(8) != ArgvMarkerSuffix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
if (!std::isxdigit(static_cast<unsigned char>(token[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsLoopbackArgvMarker(std::string_view token) {
|
||||
if (!IsArgvMarker(token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
if (std::tolower(static_cast<unsigned char>(token[i])) !=
|
||||
std::tolower(static_cast<unsigned char>(LoopbackArgvMarker[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> GetArgvMarker(std::string_view argv_string) {
|
||||
auto last_token = GetLastArgvToken(argv_string);
|
||||
if (!last_token || !IsArgvMarker(*last_token)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return last_token;
|
||||
}
|
||||
|
||||
std::optional<std::string> PrepareArgv(std::string& argv_string,
|
||||
std::string_view inherited_marker,
|
||||
bool append_loopback_marker) {
|
||||
auto marker = GetArgvMarker(argv_string);
|
||||
if (!marker && IsArgvMarker(inherited_marker)) {
|
||||
AppendArgvToken(argv_string, inherited_marker);
|
||||
marker = std::string{inherited_marker};
|
||||
}
|
||||
if (!marker && append_loopback_marker) {
|
||||
AppendArgvToken(argv_string, LoopbackArgvMarker);
|
||||
marker = std::string{LoopbackArgvMarker};
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
void ApplyServerMode(Settings::HomebrewNxlinkServerMode mode,
|
||||
const std::optional<std::string>& active_marker) {
|
||||
switch (mode) {
|
||||
case Settings::HomebrewNxlinkServerMode::Disabled:
|
||||
StopServer();
|
||||
return;
|
||||
case Settings::HomebrewNxlinkServerMode::EdenLog:
|
||||
if (active_marker && IsLoopbackArgvMarker(*active_marker)) {
|
||||
StartLogServer();
|
||||
return;
|
||||
}
|
||||
StopServer();
|
||||
if (active_marker) {
|
||||
LOG_INFO(Loader,
|
||||
"Homebrew nxlink server mode overridden by argv marker '{}'; not starting "
|
||||
"local nxlink log server",
|
||||
*active_marker);
|
||||
} else {
|
||||
LOG_WARNING(Loader,
|
||||
"Homebrew nxlink log server requested, but no argv marker is active");
|
||||
}
|
||||
return;
|
||||
case Settings::HomebrewNxlinkServerMode::HostStdout:
|
||||
case Settings::HomebrewNxlinkServerMode::File:
|
||||
StopServer();
|
||||
LOG_WARNING(Loader, "Homebrew nxlink server mode {} is not implemented",
|
||||
static_cast<int>(mode));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void StopServer() {
|
||||
std::shared_ptr<LogServerState> state;
|
||||
std::optional<std::jthread> thread;
|
||||
{
|
||||
std::scoped_lock lock{server_mutex};
|
||||
state = std::move(server_state);
|
||||
thread = std::move(server_thread);
|
||||
}
|
||||
|
||||
if (state) {
|
||||
boost::system::error_code ignored;
|
||||
state->acceptor.close(ignored);
|
||||
state->io_context.stop();
|
||||
}
|
||||
if (thread) {
|
||||
thread->request_stop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Loader::HomebrewNxlink
|
||||
@@ -1,30 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "common/settings_enums.h"
|
||||
|
||||
namespace Loader::HomebrewNxlink {
|
||||
|
||||
constexpr size_t ArgvMarkerSize = 16;
|
||||
constexpr std::string_view ArgvMarkerSuffix = "_NXLINK_";
|
||||
constexpr std::string_view LoopbackArgvMarker = "0100007F_NXLINK_";
|
||||
|
||||
bool IsArgvMarker(std::string_view token);
|
||||
bool IsLoopbackArgvMarker(std::string_view token);
|
||||
std::optional<std::string> GetArgvMarker(std::string_view argv_string);
|
||||
std::optional<std::string> PrepareArgv(std::string& argv_string,
|
||||
std::string_view inherited_marker,
|
||||
bool append_loopback_marker = false);
|
||||
|
||||
void ApplyServerMode(Settings::HomebrewNxlinkServerMode mode,
|
||||
const std::optional<std::string>& active_marker);
|
||||
void StopServer();
|
||||
|
||||
} // namespace Loader::HomebrewNxlink
|
||||
+51
-503
@@ -4,20 +4,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/random.h"
|
||||
@@ -26,14 +23,11 @@
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/vfs/vfs_offset.h"
|
||||
#include "core/hardware_properties.h"
|
||||
#include "core/hle/api_version.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/k_page_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/homebrew_nxlink.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -158,432 +152,8 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
|
||||
}
|
||||
|
||||
static std::string MakeHomebrewSdmcPath(std::string path) {
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
while (!path.empty() && path.front() == '/') {
|
||||
path.erase(path.begin());
|
||||
}
|
||||
return "sdmc:/" + path;
|
||||
}
|
||||
|
||||
static std::optional<std::string> TryMakeHomebrewSdmcPathFromHostPath(std::string path) {
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
|
||||
std::string sdmc_root =
|
||||
Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir));
|
||||
std::replace(sdmc_root.begin(), sdmc_root.end(), '\\', '/');
|
||||
while (!sdmc_root.empty() && sdmc_root.back() == '/') {
|
||||
sdmc_root.pop_back();
|
||||
}
|
||||
|
||||
if (!sdmc_root.empty() && path.size() > sdmc_root.size() &&
|
||||
path.compare(0, sdmc_root.size(), sdmc_root) == 0 && path[sdmc_root.size()] == '/') {
|
||||
return MakeHomebrewSdmcPath(path.substr(sdmc_root.size() + 1));
|
||||
}
|
||||
|
||||
constexpr std::string_view SdmcMarker{"/sdmc/"};
|
||||
if (const auto pos = path.rfind(SdmcMarker); pos != std::string::npos) {
|
||||
return MakeHomebrewSdmcPath(path.substr(pos + SdmcMarker.size()));
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::string MakeHomebrewArgv0(std::string nro_path, std::string file_name) {
|
||||
if (nro_path.empty()) {
|
||||
nro_path = std::move(file_name);
|
||||
}
|
||||
|
||||
std::replace(nro_path.begin(), nro_path.end(), '\\', '/');
|
||||
|
||||
if (nro_path.rfind("sdmc:/", 0) == 0) {
|
||||
return nro_path;
|
||||
}
|
||||
|
||||
if (auto sdmc_path = TryMakeHomebrewSdmcPathFromHostPath(nro_path)) {
|
||||
return *sdmc_path;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (nro_path.find('%') != std::string::npos) {
|
||||
const auto percent_decode = [](std::string value) {
|
||||
const auto hex_value = [](char c) -> int {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
std::string decoded;
|
||||
decoded.reserve(value.size());
|
||||
for (size_t i = 0; i < value.size(); i++) {
|
||||
if (value[i] == '%' && i + 2 < value.size()) {
|
||||
const int high = hex_value(value[i + 1]);
|
||||
const int low = hex_value(value[i + 2]);
|
||||
if (high >= 0 && low >= 0) {
|
||||
decoded.push_back(static_cast<char>((high << 4) | low));
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
decoded.push_back(value[i]);
|
||||
}
|
||||
return decoded;
|
||||
};
|
||||
|
||||
if (auto sdmc_path = TryMakeHomebrewSdmcPathFromHostPath(percent_decode(nro_path))) {
|
||||
return *sdmc_path;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!nro_path.empty() && nro_path.front() == '/') {
|
||||
return "sdmc:" + nro_path;
|
||||
}
|
||||
|
||||
return nro_path.empty() ? "homebrew" : nro_path;
|
||||
}
|
||||
|
||||
static std::string QuoteHomebrewArgvComponent(const std::string& argument) {
|
||||
if (argument.find_first_of(" \t\r\n") == std::string::npos) {
|
||||
return argument;
|
||||
}
|
||||
|
||||
std::string quoted{"\""};
|
||||
quoted += argument;
|
||||
quoted.push_back('"');
|
||||
return quoted;
|
||||
}
|
||||
|
||||
static std::string GetHomebrewInitialCwd(const std::string& argv0) {
|
||||
constexpr std::string_view SdmcPrefix = "sdmc:";
|
||||
if (argv0.substr(0, SdmcPrefix.size()) != SdmcPrefix) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto last_slash = argv0.find_last_of('/');
|
||||
if (last_slash == std::string_view::npos || last_slash < SdmcPrefix.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string cwd = argv0.substr(SdmcPrefix.size(), last_slash - SdmcPrefix.size());
|
||||
if (cwd.empty()) {
|
||||
cwd = "/";
|
||||
}
|
||||
while (cwd.size() > 1 && cwd.back() == '/') {
|
||||
cwd.pop_back();
|
||||
}
|
||||
return cwd;
|
||||
}
|
||||
|
||||
constexpr size_t HomebrewNextLoadPathSize = 0x200;
|
||||
constexpr size_t HomebrewNextLoadArgvSize = 0x800;
|
||||
constexpr u32 HomebrewSvcExitProcessInstruction = 0xD40000E1;
|
||||
constexpr u32 HomebrewEntryEndOfList = 0;
|
||||
constexpr u32 HomebrewEntryMainThreadHandle = 1;
|
||||
constexpr u32 HomebrewEntryNextLoadPath = 2;
|
||||
constexpr u32 HomebrewEntryOverrideHeap = 3;
|
||||
constexpr u32 HomebrewEntryArgv = 5;
|
||||
constexpr u32 HomebrewEntrySyscallAvailableHint = 6;
|
||||
constexpr u32 HomebrewEntryAppletType = 7;
|
||||
constexpr u32 HomebrewEntryProcessHandle = 10;
|
||||
constexpr u32 HomebrewEntryRandomSeed = 14;
|
||||
constexpr u32 HomebrewEntryHosVersion = 16;
|
||||
constexpr u32 HomebrewEntrySyscallAvailableHint2 = 17;
|
||||
constexpr u32 HomebrewAppletTypeApplication = 0;
|
||||
constexpr u64 HomebrewAllSvcHints = ~u64{0};
|
||||
constexpr u32 HomebrewHosVersion = (u32{HLE::ApiVersion::HOS_VERSION_MAJOR} << 16) |
|
||||
(u32{HLE::ApiVersion::HOS_VERSION_MINOR} << 8) |
|
||||
u32{HLE::ApiVersion::HOS_VERSION_MICRO};
|
||||
|
||||
struct HomebrewConfigEntry {
|
||||
u32_le key;
|
||||
u32_le flags;
|
||||
u64_le value[2];
|
||||
};
|
||||
static_assert(sizeof(HomebrewConfigEntry) == 0x18);
|
||||
constexpr size_t HomebrewBaseConfigEntryCount = 10;
|
||||
constexpr size_t HomebrewInPlaceConfigEntryCount = 11;
|
||||
constexpr size_t HomebrewBaseConfigTableSize =
|
||||
HomebrewBaseConfigEntryCount * sizeof(HomebrewConfigEntry);
|
||||
constexpr size_t HomebrewInPlaceConfigTableSize =
|
||||
HomebrewInPlaceConfigEntryCount * sizeof(HomebrewConfigEntry);
|
||||
|
||||
struct HomebrewNroImage {
|
||||
Kernel::CodeSet codeset;
|
||||
size_t image_size{};
|
||||
size_t args_offset{};
|
||||
std::optional<size_t> exit_process_offset;
|
||||
std::optional<std::string> nxlink_argv_marker;
|
||||
std::string argv_string;
|
||||
};
|
||||
|
||||
static void SetHomebrewConfigPointers(Kernel::KProcess& process, u64 config_addr,
|
||||
u64 next_load_path_addr, u64 next_load_argv_addr) {
|
||||
constexpr size_t MainThreadHandleEntryIndex = 0;
|
||||
constexpr size_t ProcessHandleEntryIndex = 1;
|
||||
constexpr size_t EntryValueOffset = offsetof(HomebrewConfigEntry, value);
|
||||
|
||||
process.SetArgPointer(Kernel::KProcessAddress{config_addr});
|
||||
process.SetMainThreadHandleAddr(Kernel::KProcessAddress{
|
||||
config_addr + MainThreadHandleEntryIndex * sizeof(HomebrewConfigEntry) + EntryValueOffset});
|
||||
process.SetProcessHandleAddr(Kernel::KProcessAddress{
|
||||
config_addr + ProcessHandleEntryIndex * sizeof(HomebrewConfigEntry) + EntryValueOffset});
|
||||
process.SetHomebrewNextLoadBufferAddrs(Kernel::KProcessAddress{next_load_path_addr},
|
||||
Kernel::KProcessAddress{next_load_argv_addr});
|
||||
}
|
||||
|
||||
static std::optional<HomebrewNroImage> BuildHomebrewNroImage(const std::vector<u8>& data,
|
||||
std::string nro_path,
|
||||
std::string file_name,
|
||||
std::string launch_argv,
|
||||
std::string_view inherited_marker,
|
||||
bool append_loopback_nxlink_marker) {
|
||||
if (data.size() < sizeof(NroHeader)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
NroHeader nro_header{};
|
||||
std::memcpy(&nro_header, data.data(), sizeof(NroHeader));
|
||||
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (data.size() < nro_header.file_size ||
|
||||
nro_header.module_header_offset + sizeof(ModHeader) > PageAlignSize(nro_header.file_size)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
|
||||
std::memcpy(program_image.data(), data.data(), nro_header.file_size);
|
||||
|
||||
Kernel::CodeSet codeset;
|
||||
for (std::size_t i = 0; i < nro_header.segments.size(); ++i) {
|
||||
codeset.segments[i].addr = nro_header.segments[i].offset;
|
||||
codeset.segments[i].offset = nro_header.segments[i].offset;
|
||||
codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size);
|
||||
}
|
||||
|
||||
u32 bss_size{PageAlignSize(nro_header.bss_size)};
|
||||
ModHeader mod_header{};
|
||||
std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset,
|
||||
sizeof(ModHeader));
|
||||
|
||||
if (mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')) {
|
||||
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
|
||||
}
|
||||
|
||||
codeset.DataSegment().size += bss_size;
|
||||
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
|
||||
|
||||
HomebrewNroImage image{.codeset = std::move(codeset)};
|
||||
const auto argv0 = MakeHomebrewArgv0(std::move(nro_path), std::move(file_name));
|
||||
|
||||
if (!launch_argv.empty()) {
|
||||
image.argv_string = std::move(launch_argv);
|
||||
} else {
|
||||
image.argv_string = QuoteHomebrewArgvComponent(argv0);
|
||||
}
|
||||
image.nxlink_argv_marker = HomebrewNxlink::PrepareArgv(
|
||||
image.argv_string, inherited_marker, append_loopback_nxlink_marker);
|
||||
if (image.argv_string.empty() || image.argv_string.back() != '\0') {
|
||||
image.argv_string.push_back('\0');
|
||||
}
|
||||
|
||||
const auto& code = image.codeset.CodeSegment();
|
||||
const size_t code_end = (std::min)(program_image.size(), code.offset + code.size);
|
||||
for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) {
|
||||
u32 instruction{};
|
||||
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
|
||||
if (instruction == HomebrewSvcExitProcessInstruction) {
|
||||
image.exit_process_offset = offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t entries_and_buffers =
|
||||
Common::AlignUp(HomebrewInPlaceConfigTableSize + HomebrewNextLoadPathSize +
|
||||
HomebrewNextLoadArgvSize + image.argv_string.size(),
|
||||
Core::Memory::YUZU_PAGESIZE);
|
||||
|
||||
image.args_offset = program_image.size();
|
||||
image.codeset.DataSegment().size += static_cast<u32>(entries_and_buffers);
|
||||
program_image.resize(image.args_offset + entries_and_buffers);
|
||||
image.image_size = program_image.size();
|
||||
image.codeset.memory = std::move(program_image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, Kernel::KThread& thread,
|
||||
const FileSys::VirtualFile& nro_file, const std::string& nro_path,
|
||||
const std::string& launch_argv) {
|
||||
if (!nro_file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAS_NCE
|
||||
if (Settings::IsNceEnabled()) {
|
||||
LOG_WARNING(Loader,
|
||||
"Homebrew next-load: in-place handoff unavailable because NCE is enabled");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t live_threads = 0;
|
||||
for (auto& candidate : process.GetThreadList()) {
|
||||
if (candidate.GetState() != Kernel::ThreadState::Terminated) {
|
||||
live_threads++;
|
||||
}
|
||||
}
|
||||
if (live_threads != 1) {
|
||||
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because live_threads={}",
|
||||
live_threads);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto stack_top = process.GetMainThreadStackTop();
|
||||
if (GetInteger(stack_top) == 0) {
|
||||
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because stack_top=0");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto nxlink_server_mode = Settings::values.homebrew_nxlink_server_mode.GetValue();
|
||||
const bool append_loopback_nxlink_marker =
|
||||
nxlink_server_mode != Settings::HomebrewNxlinkServerMode::Disabled;
|
||||
auto image = BuildHomebrewNroImage(
|
||||
nro_file->ReadAllBytes(), nro_path, nro_file->GetName(), launch_argv,
|
||||
process.GetHomebrewNxlinkArgvMarker(), append_loopback_nxlink_marker);
|
||||
if (!image) {
|
||||
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because '{}' is invalid",
|
||||
nro_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto capacity = process.GetCodeSize();
|
||||
if (image->image_size > capacity) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because image_size=0x{:X} exceeds "
|
||||
"capacity=0x{:X}",
|
||||
image->image_size, capacity);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 base = GetInteger(process.GetEntryPoint());
|
||||
const u64 heap_addr = GetInteger(process.GetPageTable().GetHeapRegionStart());
|
||||
const size_t heap_size = process.GetPageTable().GetBasePageTable().GetCurrentHeapSize();
|
||||
if (heap_addr == 0 || heap_size == 0) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because heap override is "
|
||||
"unavailable (addr=0x{:016X}, size=0x{:X})",
|
||||
heap_addr, heap_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 config_addr = base + image->args_offset;
|
||||
const u64 next_load_path_addr = config_addr + HomebrewInPlaceConfigTableSize;
|
||||
const u64 next_load_argv_addr = next_load_path_addr + HomebrewNextLoadPathSize;
|
||||
const u64 argv_addr = next_load_argv_addr + HomebrewNextLoadArgvSize;
|
||||
const u64 argv_entry_addr = image->argv_string.empty() ? 0 : argv_addr;
|
||||
const std::string argv0 = MakeHomebrewArgv0(nro_path, nro_file->GetName());
|
||||
const std::string homebrew_initial_cwd = GetHomebrewInitialCwd(argv0);
|
||||
u64 program_id{};
|
||||
AppLoader_NRO loader{nro_file};
|
||||
if (loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) {
|
||||
LOG_WARNING(Loader, "NextLoad: in-place handoff could not read NRO program id");
|
||||
}
|
||||
|
||||
Kernel::Handle main_thread_handle{};
|
||||
if (process.GetHandleTable().Add(system.Kernel(), std::addressof(main_thread_handle), &thread)
|
||||
.IsError()) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because main thread handle failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
Kernel::Handle process_handle{};
|
||||
if (process.GetHandleTable().Add(system.Kernel(), std::addressof(process_handle), &process)
|
||||
.IsError()) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because process handle failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!process.GetMemory().ZeroBlock(Common::ProcessAddress{heap_addr}, heap_size)) {
|
||||
LOG_WARNING(Loader,
|
||||
"NextLoad: in-place handoff failed because heap clear failed "
|
||||
"(addr=0x{:016X}, size=0x{:X})",
|
||||
heap_addr, heap_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
process.LoadModule(system.Kernel(), std::move(image->codeset), process.GetEntryPoint());
|
||||
|
||||
const HomebrewConfigEntry entries[HomebrewInPlaceConfigEntryCount] = {
|
||||
{HomebrewEntryMainThreadHandle, 0, {main_thread_handle, 0}},
|
||||
{HomebrewEntryProcessHandle, 0, {process_handle, 0}},
|
||||
{HomebrewEntryNextLoadPath, 0, {next_load_path_addr, next_load_argv_addr}},
|
||||
{HomebrewEntryOverrideHeap, 0, {heap_addr, heap_size}},
|
||||
{HomebrewEntryAppletType, 0, {HomebrewAppletTypeApplication, 0}},
|
||||
{HomebrewEntryArgv, 0, {0, argv_entry_addr}},
|
||||
{HomebrewEntrySyscallAvailableHint, 0, {HomebrewAllSvcHints, HomebrewAllSvcHints}},
|
||||
{HomebrewEntryRandomSeed, 0,
|
||||
{process.GetRandomEntropy(0), process.GetRandomEntropy(1)}},
|
||||
{HomebrewEntryHosVersion, 0, {HomebrewHosVersion, 0}},
|
||||
{HomebrewEntrySyscallAvailableHint2, 0, {HomebrewAllSvcHints, 0}},
|
||||
{HomebrewEntryEndOfList, 0, {0, 0}},
|
||||
};
|
||||
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries));
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, image->argv_string.data(),
|
||||
image->argv_string.size());
|
||||
process.GetMemory().Write32(Common::ProcessAddress{GetInteger(thread.GetTlsAddress()) + 0x110},
|
||||
main_thread_handle);
|
||||
|
||||
process.SetArgReturnAddress(Kernel::KProcessAddress{
|
||||
image->exit_process_offset ? base + *image->exit_process_offset : 0});
|
||||
SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr);
|
||||
if (image->nxlink_argv_marker) {
|
||||
process.SetHomebrewNxlinkArgvMarker(*image->nxlink_argv_marker);
|
||||
} else {
|
||||
process.ClearHomebrewNxlinkArgvMarker();
|
||||
}
|
||||
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, image->nxlink_argv_marker);
|
||||
system.GetFileSystemController().RegisterProcess(
|
||||
process.GetProcessId(), program_id,
|
||||
std::make_unique<FileSys::RomFSFactory>(loader, system.GetContentProvider(),
|
||||
system.GetFileSystemController()),
|
||||
homebrew_initial_cwd);
|
||||
process.SetHomebrewInPlaceNextLoad(true);
|
||||
|
||||
auto& context = thread.GetContext();
|
||||
context = {};
|
||||
context.r[0] = config_addr;
|
||||
context.r[1] = UINT64_MAX;
|
||||
context.r[18] = Common::Random::Random64(0) | 1;
|
||||
context.lr = image->exit_process_offset ? base + *image->exit_process_offset : 0;
|
||||
context.pc = base;
|
||||
context.sp = GetInteger(stack_top);
|
||||
context.fpcr = 0;
|
||||
context.fpsr = 0;
|
||||
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
if (auto* arm = process.GetArmInterface(core); arm != nullptr) {
|
||||
arm->ClearInstructionCache();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
const std::vector<u8>& data, std::string nro_path,
|
||||
std::string file_name) {
|
||||
const std::vector<u8>& data) {
|
||||
if (data.size() < sizeof(NroHeader)) {
|
||||
return {};
|
||||
}
|
||||
@@ -625,49 +195,47 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
|
||||
codeset.DataSegment().size += bss_size;
|
||||
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
|
||||
struct ConfigEntry {
|
||||
u32_le key;
|
||||
u32_le flags;
|
||||
u64_le value[2];
|
||||
};
|
||||
static_assert(sizeof(ConfigEntry) == 0x18);
|
||||
// AArch64 encoding for svc #0x7 (ExitProcess).
|
||||
constexpr u32 kSvcExitProcessInstruction = 0xD40000E1;
|
||||
constexpr size_t kNumEntries = 4; // MainThreadHandle, AppletType, Argv, EndOfList
|
||||
constexpr size_t kConfigTableSize = kNumEntries * sizeof(ConfigEntry);
|
||||
std::string argv_string;
|
||||
size_t args_offset_in_image = 0;
|
||||
std::optional<size_t> exit_process_offset_in_image;
|
||||
const auto& program_args = Settings::values.program_args.GetValue();
|
||||
const std::string argv0 = MakeHomebrewArgv0(std::move(nro_path), std::move(file_name));
|
||||
argv_string = QuoteHomebrewArgvComponent(argv0);
|
||||
if (!program_args.empty()) {
|
||||
argv_string.push_back(' ');
|
||||
argv_string = "homebrew ";
|
||||
argv_string += program_args;
|
||||
}
|
||||
const auto nxlink_server_mode = Settings::values.homebrew_nxlink_server_mode.GetValue();
|
||||
const bool append_loopback_nxlink_marker =
|
||||
nxlink_server_mode != Settings::HomebrewNxlinkServerMode::Disabled;
|
||||
const auto nxlink_argv_marker =
|
||||
HomebrewNxlink::PrepareArgv(argv_string, {}, append_loopback_nxlink_marker);
|
||||
if (argv_string.empty() || argv_string.back() != '\0') {
|
||||
argv_string.push_back('\0');
|
||||
}
|
||||
|
||||
const auto& code_segment = codeset.CodeSegment();
|
||||
const size_t code_end =
|
||||
(std::min)(program_image.size(), code_segment.offset + code_segment.size);
|
||||
for (size_t offset = code_segment.offset; offset + sizeof(u32) <= code_end;
|
||||
offset += sizeof(u32)) {
|
||||
u32 instruction{};
|
||||
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
|
||||
if (instruction == HomebrewSvcExitProcessInstruction) {
|
||||
exit_process_offset_in_image = offset;
|
||||
break;
|
||||
const auto& code = codeset.CodeSegment();
|
||||
const size_t code_end = (std::min)(program_image.size(), code.offset + code.size);
|
||||
for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) {
|
||||
u32 instruction{};
|
||||
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
|
||||
if (instruction == kSvcExitProcessInstruction) {
|
||||
exit_process_offset_in_image = offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exit_process_offset_in_image) {
|
||||
LOG_WARNING(Loader,
|
||||
"Unable to find svcExitProcess in NRO; returning from main may fault");
|
||||
}
|
||||
}
|
||||
if (!exit_process_offset_in_image) {
|
||||
LOG_WARNING(Loader, "Unable to find svcExitProcess in NRO; returning from main may fault");
|
||||
}
|
||||
|
||||
const size_t entries_and_buffers =
|
||||
Common::AlignUp(HomebrewBaseConfigTableSize + HomebrewNextLoadPathSize +
|
||||
HomebrewNextLoadArgvSize + argv_string.size(),
|
||||
Core::Memory::YUZU_PAGESIZE);
|
||||
const size_t entries_and_argv =
|
||||
Common::AlignUp(kConfigTableSize + argv_string.size(), Core::Memory::YUZU_PAGESIZE);
|
||||
|
||||
args_offset_in_image = program_image.size();
|
||||
codeset.DataSegment().size += static_cast<u32>(entries_and_buffers);
|
||||
program_image.resize(args_offset_in_image + entries_and_buffers);
|
||||
args_offset_in_image = program_image.size();
|
||||
codeset.DataSegment().size += static_cast<u32>(entries_and_argv);
|
||||
program_image.resize(args_offset_in_image + entries_and_argv);
|
||||
}
|
||||
size_t image_size = program_image.size();
|
||||
|
||||
#ifdef HAS_NCE
|
||||
@@ -695,9 +263,6 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
image_size += patch_segment.size;
|
||||
}
|
||||
#endif
|
||||
// In-place NextLoad reuses the original code mapping; leave room for larger homebrew cores.
|
||||
constexpr size_t HomebrewCodeArenaSize = 192 * 1024 * 1024;
|
||||
image_size = (std::max)(image_size, HomebrewCodeArenaSize);
|
||||
|
||||
// Enable direct memory mapping in case of NCE.
|
||||
const u64 fastmem_base = [&]() -> size_t {
|
||||
@@ -732,44 +297,31 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
// Load codeset for current process
|
||||
codeset.memory = std::move(program_image);
|
||||
process.LoadModule(system.Kernel(), std::move(codeset), process.GetEntryPoint());
|
||||
process.SetHomebrewInPlaceNextLoad(false);
|
||||
if (nxlink_argv_marker) {
|
||||
process.SetHomebrewNxlinkArgvMarker(*nxlink_argv_marker);
|
||||
} else {
|
||||
process.ClearHomebrewNxlinkArgvMarker();
|
||||
}
|
||||
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, nxlink_argv_marker);
|
||||
{
|
||||
if (!argv_string.empty()) {
|
||||
constexpr u32 kEntryEndOfList = 0;
|
||||
constexpr u32 kEntryMainThreadHandle = 1;
|
||||
constexpr u32 kEntryArgv = 5;
|
||||
constexpr u32 kEntryAppletType = 7;
|
||||
constexpr u32 kAppletTypeApplication = 0;
|
||||
|
||||
const u64 base = GetInteger(process.GetEntryPoint());
|
||||
const u64 config_addr = base + args_offset_in_image;
|
||||
const u64 next_load_path_addr = config_addr + HomebrewBaseConfigTableSize;
|
||||
const u64 next_load_argv_addr = next_load_path_addr + HomebrewNextLoadPathSize;
|
||||
const u64 argv_addr = next_load_argv_addr + HomebrewNextLoadArgvSize;
|
||||
const u64 argv_entry_addr = argv_string.empty() ? 0 : argv_addr;
|
||||
const u64 argv_addr = config_addr + kConfigTableSize;
|
||||
|
||||
const HomebrewConfigEntry entries[HomebrewBaseConfigEntryCount] = {
|
||||
{HomebrewEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run()
|
||||
{HomebrewEntryProcessHandle, 0, {0, 0}}, // Value[0] patched in Run()
|
||||
{HomebrewEntryNextLoadPath, 0, {next_load_path_addr, next_load_argv_addr}},
|
||||
{HomebrewEntryAppletType, 0, {HomebrewAppletTypeApplication, 0}},
|
||||
{HomebrewEntryArgv, 0, {0, argv_entry_addr}},
|
||||
{HomebrewEntrySyscallAvailableHint, 0, {HomebrewAllSvcHints, HomebrewAllSvcHints}},
|
||||
{HomebrewEntryRandomSeed, 0,
|
||||
{process.GetRandomEntropy(0), process.GetRandomEntropy(1)}},
|
||||
{HomebrewEntryHosVersion, 0, {HomebrewHosVersion, 0}},
|
||||
{HomebrewEntrySyscallAvailableHint2, 0, {HomebrewAllSvcHints, 0}},
|
||||
{HomebrewEntryEndOfList, 0, {0, 0}},
|
||||
const ConfigEntry entries[kNumEntries] = {
|
||||
{kEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run()
|
||||
{kEntryAppletType, 0, {kAppletTypeApplication, 0}},
|
||||
{kEntryArgv, 0, {0, argv_addr}},
|
||||
{kEntryEndOfList, 0, {0, 0}},
|
||||
};
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries,
|
||||
sizeof(entries));
|
||||
if (!argv_string.empty()) {
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(),
|
||||
argv_string.size());
|
||||
}
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries));
|
||||
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(), argv_string.size());
|
||||
constexpr size_t kMainThreadHandleValueOffset = offsetof(ConfigEntry, value);
|
||||
process.SetArgPointer(Kernel::KProcessAddress{config_addr});
|
||||
if (exit_process_offset_in_image) {
|
||||
process.SetArgReturnAddress(Kernel::KProcessAddress{base + *exit_process_offset_in_image});
|
||||
}
|
||||
SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr);
|
||||
process.SetMainThreadHandleAddr(Kernel::KProcessAddress{config_addr + kMainThreadHandleValueOffset});
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -777,8 +329,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
|
||||
bool AppLoader_NRO::LoadNro(Core::System& system, Kernel::KProcess& process,
|
||||
const FileSys::VfsFile& nro_file) {
|
||||
return LoadNroImpl(system, process, nro_file.ReadAllBytes(), nro_file.GetFullPath(),
|
||||
nro_file.GetName());
|
||||
return LoadNroImpl(system, process, nro_file.ReadAllBytes());
|
||||
}
|
||||
|
||||
AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::System& system) {
|
||||
@@ -792,13 +343,10 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
|
||||
|
||||
u64 program_id{};
|
||||
ReadProgramId(program_id);
|
||||
const std::string argv0 = MakeHomebrewArgv0(file->GetFullPath(), file->GetName());
|
||||
const std::string homebrew_initial_cwd = GetHomebrewInitialCwd(argv0);
|
||||
system.GetFileSystemController().RegisterProcess(
|
||||
process.GetProcessId(), program_id,
|
||||
std::make_unique<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
|
||||
system.GetFileSystemController()),
|
||||
homebrew_initial_cwd);
|
||||
system.GetFileSystemController()));
|
||||
|
||||
is_loaded = true;
|
||||
return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -22,15 +19,10 @@ class NACP;
|
||||
|
||||
namespace Kernel {
|
||||
class KProcess;
|
||||
class KThread;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
|
||||
[[nodiscard]] bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process,
|
||||
Kernel::KThread& thread, const FileSys::VirtualFile& nro_file,
|
||||
const std::string& nro_path, const std::string& launch_argv);
|
||||
|
||||
/// Loads an NRO file
|
||||
class AppLoader_NRO final : public AppLoader {
|
||||
public:
|
||||
|
||||
@@ -504,9 +504,12 @@ void NPad::OnUpdate(Kernel::KernelCore& kernel, const Core::Timing::CoreTiming&
|
||||
|
||||
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
|
||||
auto& controller = controller_data[aruid_index][i];
|
||||
controller.shared_memory =
|
||||
&data->shared_memory_format->npad.npad_entry[i].internal_state;
|
||||
controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state;
|
||||
auto* npad = controller.shared_memory;
|
||||
if (!npad || !controller.device) {
|
||||
LOG_WARNING(Service_HID, "No device for {}", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& controller_type = controller.device->GetNpadStyleIndex();
|
||||
|
||||
|
||||
@@ -648,13 +648,6 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
|
||||
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, Settings::values.enable_raw_input ? "1" : "0");
|
||||
|
||||
#ifdef _WIN32
|
||||
if (Settings::values.disable_wgi_xinput) {
|
||||
SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, "0", SDL_HINT_OVERRIDE);
|
||||
SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_WGI, "0", SDL_HINT_OVERRIDE);
|
||||
}
|
||||
#endif
|
||||
|
||||
// SDL3 defaults Steam Controller Bluetooth HIDAPI support to off, which can disable gyro.
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1");
|
||||
|
||||
@@ -92,12 +92,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
tr("Change the accuracy of the emulated CPU (for debugging only)."));
|
||||
INSERT(Settings, cpu_backend, tr("Backend:"), QString());
|
||||
|
||||
INSERT(Settings, fast_cpu_time, tr("CPU Overclock"),
|
||||
tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see "
|
||||
"reduced performance, "
|
||||
"and certain games may behave improperly.\nUse Boost (1700MHz) to run at the "
|
||||
"Switch's highest native "
|
||||
"clock, or Fast (2000MHz) to run at 2x clock."));
|
||||
INSERT(Settings, cpu_clock, tr("CPU Clocks"),
|
||||
tr("Raises the clock the emulated CPU reports, which removes some FPS limiters.\n"
|
||||
"Weaker CPUs may see reduced performance, and certain games may behave improperly."));
|
||||
|
||||
INSERT(Settings, use_custom_cpu_ticks, QString(), QString());
|
||||
INSERT(Settings, cpu_ticks, tr("Custom CPU Ticks"),
|
||||
@@ -230,9 +227,11 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
|
||||
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
|
||||
tr("May reduce shader stutter."));
|
||||
INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"),
|
||||
tr("Overclocks the emulated GPU to increase dynamic resolution and render "
|
||||
"distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity."));
|
||||
INSERT(Settings, use_unified_memory, tr("Enable unified memory access"),
|
||||
tr("Lets the GPU write buffer readbacks directly into guest memory."));
|
||||
INSERT(Settings, gpu_clock, tr("GPU Clocks"),
|
||||
tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering "
|
||||
"resolution and render distance to fit the Switch's clocks."));
|
||||
INSERT(Settings, gpu_unswizzle_enabled, tr("GPU Unswizzle"),
|
||||
tr("Accelerates BCn 3D texture decoding using GPU compute.\n"
|
||||
"Disable if experiencing crashes or graphical glitches."));
|
||||
@@ -302,8 +301,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
INSERT(Settings, device_name, tr("Device Name"), tr("The name of the console."));
|
||||
INSERT(Settings, program_args, tr("Homebrew Args"),
|
||||
tr("Command-line arguments passed to homebrew at launch (e.g. -noglsl)."));
|
||||
INSERT(Settings, homebrew_nxlink_server_mode, tr("nxlink Server"),
|
||||
tr("Starts a local nxlink server for homebrew stdout/stderr streams."));
|
||||
INSERT(Settings, custom_rtc, tr("Custom RTC Date:"),
|
||||
tr("This option allows to change the clock of the console.\n"
|
||||
"Can be used to manipulate time in games."));
|
||||
@@ -641,9 +638,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::CpuClock>::Index(),
|
||||
{
|
||||
PAIR(CpuClock, Off, tr("Off")),
|
||||
PAIR(CpuClock, Boost, tr("Boost (1700MHz)")),
|
||||
PAIR(CpuClock, Fast, tr("Fast (2000MHz)")),
|
||||
PAIR(CpuClock, Normal, tr("Normal")),
|
||||
PAIR(CpuClock, Boost, tr("Boost")),
|
||||
PAIR(CpuClock, Overclock, tr("Overclock")),
|
||||
}});
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
|
||||
@@ -652,11 +649,11 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
||||
PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")),
|
||||
PAIR(ConfirmStop, Ask_Never, tr("Never ask")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuOverclock>::Index(),
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuClock>::Index(),
|
||||
{
|
||||
PAIR(GpuOverclock, Normal, tr("Off")),
|
||||
PAIR(GpuOverclock, Medium, tr("Medium (256)")),
|
||||
PAIR(GpuOverclock, High, tr("High (512)")),
|
||||
PAIR(GpuClock, Normal, tr("Normal")),
|
||||
PAIR(GpuClock, Boost, tr("Boost")),
|
||||
PAIR(GpuClock, Overclock, tr("Overclock")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuUnswizzleSize>::Index(),
|
||||
{
|
||||
|
||||
@@ -159,9 +159,7 @@ void GameListModel::RemoveFavorite(u64 program_id) {
|
||||
|
||||
void GameListModel::Repopulate() {
|
||||
current_worker.reset();
|
||||
if (!QtCommon::system->IsPoweredOn()) {
|
||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||
}
|
||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||
PopulateAsync(UISettings::values.game_dirs);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,8 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/vk_compute_pass.h
|
||||
renderer_vulkan/vk_compute_pipeline.cpp
|
||||
renderer_vulkan/vk_compute_pipeline.h
|
||||
renderer_vulkan/vk_descriptor_buffer.cpp
|
||||
renderer_vulkan/vk_descriptor_buffer.h
|
||||
renderer_vulkan/vk_descriptor_pool.cpp
|
||||
renderer_vulkan/vk_descriptor_pool.h
|
||||
renderer_vulkan/vk_fence_manager.cpp
|
||||
|
||||
@@ -571,7 +571,11 @@ void BufferCache<P>::AccumulateFlushes() {
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
|
||||
return (!async_buffers.empty() && async_buffers.front().has_value());
|
||||
if (async_buffers.empty()) {
|
||||
return false;
|
||||
}
|
||||
return async_buffers.front().has_value() ||
|
||||
!pending_downloads.front().unified_copies.empty();
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -579,6 +583,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
||||
AccumulateFlushes();
|
||||
|
||||
if (committed_gpu_modified_ranges.empty()) {
|
||||
pending_downloads.emplace_back();
|
||||
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
||||
return;
|
||||
}
|
||||
@@ -638,27 +643,83 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
||||
}
|
||||
committed_gpu_modified_ranges.clear();
|
||||
if (downloads.empty()) {
|
||||
pending_downloads.emplace_back();
|
||||
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
||||
return;
|
||||
}
|
||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
|
||||
boost::container::small_vector<BufferCopy, 4> normalized_copies;
|
||||
runtime.PreCopyBarrier();
|
||||
|
||||
struct QueuedUnifiedCopy {
|
||||
u64 window;
|
||||
BufferId buffer_id;
|
||||
boost::container::small_vector<BufferCopy, 16> copies;
|
||||
};
|
||||
|
||||
AsyncDownloadBatch batch;
|
||||
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> staging_downloads;
|
||||
boost::container::small_vector<QueuedUnifiedCopy, 4> unified_copy_queue;
|
||||
boost::container::small_vector<u64, 4> window_ids;
|
||||
UnifiedWindowGroups groups;
|
||||
u64 staging_size_bytes = 0;
|
||||
for (auto& [copy, buffer_id] : downloads) {
|
||||
copy.dst_offset += download_staging.offset;
|
||||
const std::array copies{copy};
|
||||
BufferCopy second_copy{copy};
|
||||
Buffer& buffer = slot_buffers[buffer_id];
|
||||
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
|
||||
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset);
|
||||
const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
|
||||
bool unified = false;
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
if (runtime.HasUnifiedMemory()) {
|
||||
window_ids.clear();
|
||||
groups.clear();
|
||||
unified = ResolveUnifiedWindows(orig_device_addr, copy.src_offset, copy.size,
|
||||
window_ids, groups);
|
||||
}
|
||||
}
|
||||
BufferCopy record{copy};
|
||||
record.src_offset = static_cast<size_t>(orig_device_addr);
|
||||
if (unified) {
|
||||
async_downloads.Add(orig_device_addr, copy.size);
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
unified_copy_queue.push_back(
|
||||
QueuedUnifiedCopy{window_ids[i], buffer_id, std::move(groups[i])});
|
||||
}
|
||||
batch.unified_copies.push_back(record);
|
||||
continue;
|
||||
}
|
||||
copy.dst_offset = staging_size_bytes;
|
||||
constexpr u64 align = 64ULL;
|
||||
staging_size_bytes += (copy.size + align - 1) & ~(align - 1ULL);
|
||||
staging_downloads.push_back({copy, buffer_id});
|
||||
}
|
||||
|
||||
std::optional<Async_Buffer> download_staging;
|
||||
if (!staging_downloads.empty()) {
|
||||
download_staging = runtime.DownloadStagingBuffer(staging_size_bytes, true);
|
||||
}
|
||||
runtime.PreCopyBarrier();
|
||||
for (auto& [copy, buffer_id] : staging_downloads) {
|
||||
copy.dst_offset += download_staging->offset;
|
||||
const std::array copies{copy};
|
||||
Buffer& buffer = slot_buffers[buffer_id];
|
||||
BufferCopy record{copy};
|
||||
record.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
|
||||
const DAddr orig_device_addr = static_cast<DAddr>(record.src_offset);
|
||||
async_downloads.Add(orig_device_addr, copy.size);
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
|
||||
normalized_copies.push_back(second_copy);
|
||||
runtime.CopyBuffer(download_staging->buffer, buffer, copies, false);
|
||||
batch.staging_copies.push_back(record);
|
||||
}
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
for (const auto& queued : unified_copy_queue) {
|
||||
const std::span<const BufferCopy> group_span(queued.copies.data(),
|
||||
queued.copies.size());
|
||||
runtime.CopyToUnifiedMemory(queued.window, slot_buffers[queued.buffer_id], group_span);
|
||||
}
|
||||
if (!unified_copy_queue.empty()) {
|
||||
runtime.UnifiedMemoryHostBarrier();
|
||||
}
|
||||
}
|
||||
runtime.PostCopyBarrier();
|
||||
pending_downloads.emplace_back(std::move(normalized_copies));
|
||||
async_buffers.emplace_back(download_staging);
|
||||
pending_downloads.emplace_back(std::move(batch));
|
||||
async_buffers.emplace_back(std::move(download_staging));
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -673,32 +734,49 @@ void BufferCache<P>::PopAsyncFlushes() {
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::PopAsyncBuffers() {
|
||||
if (async_buffers.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!async_buffers.front().has_value()) {
|
||||
struct Writeback {
|
||||
DAddr addr;
|
||||
const u8* src;
|
||||
u64 size;
|
||||
};
|
||||
boost::container::small_vector<Writeback, 8> writebacks;
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
if (async_buffers.empty()) {
|
||||
return;
|
||||
}
|
||||
auto& batch = pending_downloads.front();
|
||||
auto& async_buffer = async_buffers.front();
|
||||
if (async_buffer.has_value()) {
|
||||
const u8* base = async_buffer->mapped_span.data();
|
||||
const size_t base_offset = async_buffer->offset;
|
||||
for (const auto& copy : batch.staging_copies) {
|
||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||
const u64 dst_offset = copy.dst_offset - base_offset;
|
||||
const u8* read_mapped_memory = base + dst_offset;
|
||||
async_downloads.ForEachInRange(
|
||||
device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
|
||||
writebacks.push_back(
|
||||
{start, &read_mapped_memory[start - device_addr], end - start});
|
||||
});
|
||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
||||
gpu_modified_ranges.Subtract(start, end - start);
|
||||
});
|
||||
}
|
||||
async_buffers_death_ring.emplace_back(*async_buffer);
|
||||
}
|
||||
for (const auto& copy : batch.unified_copies) {
|
||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
||||
gpu_modified_ranges.Subtract(start, end - start);
|
||||
});
|
||||
}
|
||||
async_buffers.pop_front();
|
||||
return;
|
||||
pending_downloads.pop_front();
|
||||
}
|
||||
auto& downloads = pending_downloads.front();
|
||||
auto& async_buffer = async_buffers.front();
|
||||
u8* base = async_buffer->mapped_span.data();
|
||||
const size_t base_offset = async_buffer->offset;
|
||||
for (const auto& copy : downloads) {
|
||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||
const u64 dst_offset = copy.dst_offset - base_offset;
|
||||
const u8* read_mapped_memory = base + dst_offset;
|
||||
async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
|
||||
device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr],
|
||||
end - start);
|
||||
});
|
||||
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
|
||||
gpu_modified_ranges.Subtract(start, end - start);
|
||||
});
|
||||
for (const auto& wb : writebacks) {
|
||||
device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size);
|
||||
}
|
||||
async_buffers_death_ring.emplace_back(*async_buffer);
|
||||
async_buffers.pop_front();
|
||||
pending_downloads.pop_front();
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@@ -1699,6 +1777,98 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::ResolveUnifiedWindows(
|
||||
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
|
||||
[[maybe_unused]] u64 size, [[maybe_unused]] boost::container::small_vector<u64, 4>& window_ids,
|
||||
[[maybe_unused]] UnifiedWindowGroups& groups) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
const u8* const physical_base = device_memory.GetPhysicalBase();
|
||||
const u64 unified_base = runtime.UnifiedMemoryBase();
|
||||
const u64 unified_size = runtime.UnifiedMemorySize();
|
||||
const u64 window_size = runtime.UnifiedMemoryWindowSize();
|
||||
if (window_size == 0) {
|
||||
return false;
|
||||
}
|
||||
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
if (window_ids[i] == window) {
|
||||
return groups[i];
|
||||
}
|
||||
}
|
||||
window_ids.push_back(window);
|
||||
groups.emplace_back();
|
||||
return groups.back();
|
||||
};
|
||||
u64 downloaded = 0;
|
||||
while (downloaded < size) {
|
||||
const DAddr page_addr = device_addr + downloaded;
|
||||
const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
|
||||
if (ptr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
|
||||
u64 chunk = (std::min)(size - downloaded,
|
||||
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
|
||||
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
|
||||
if (phys_offset < unified_base || phys_offset - unified_base + chunk > unified_size) {
|
||||
return false;
|
||||
}
|
||||
const u64 relative = phys_offset - unified_base;
|
||||
const u64 window = relative / window_size;
|
||||
const u64 local_offset = relative % window_size;
|
||||
chunk = (std::min)(chunk, window_size - local_offset);
|
||||
auto& group = group_for(window);
|
||||
if (!group.empty()) {
|
||||
BufferCopy& last = group.back();
|
||||
if (last.src_offset + last.size == buffer_offset + downloaded &&
|
||||
last.dst_offset + last.size == local_offset) {
|
||||
last.size += chunk;
|
||||
downloaded += chunk;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
group.push_back(BufferCopy{
|
||||
.src_offset = buffer_offset + downloaded,
|
||||
.dst_offset = local_offset,
|
||||
.size = chunk,
|
||||
});
|
||||
downloaded += chunk;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
|
||||
[[maybe_unused]] std::span<BufferCopy> copies) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
boost::container::small_vector<u64, 4> window_ids;
|
||||
UnifiedWindowGroups groups;
|
||||
for (const BufferCopy& copy : copies) {
|
||||
if (!ResolveUnifiedWindows(buffer.CpuAddr() + copy.src_offset, copy.src_offset,
|
||||
copy.size, window_ids, groups)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const BufferCopy& copy : copies) {
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
}
|
||||
runtime.PreCopyBarrier();
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
|
||||
runtime.CopyToUnifiedMemory(window_ids[i], buffer, group_span);
|
||||
}
|
||||
runtime.UnifiedMemoryHostBarrier();
|
||||
runtime.Finish();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
|
||||
[[maybe_unused]] u64 total_size_bytes,
|
||||
@@ -1802,6 +1972,12 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
|
||||
}
|
||||
|
||||
if constexpr (USE_MEMORY_MAPS) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
if (runtime.HasUnifiedMemory() &&
|
||||
TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
|
||||
const u8* const mapped_memory = download_staging.mapped_span.data();
|
||||
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
|
||||
|
||||
@@ -180,6 +180,7 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
|
||||
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
|
||||
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
|
||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
|
||||
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
|
||||
|
||||
#ifdef YUZU_LEGACY
|
||||
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
|
||||
@@ -443,6 +444,15 @@ private:
|
||||
|
||||
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
|
||||
|
||||
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
|
||||
|
||||
using UnifiedWindowGroups =
|
||||
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4>;
|
||||
|
||||
bool ResolveUnifiedWindows(DAddr device_addr, u64 buffer_offset, u64 size,
|
||||
boost::container::small_vector<u64, 4>& window_ids,
|
||||
UnifiedWindowGroups& groups);
|
||||
|
||||
void DownloadBufferMemory(Buffer& buffer_id);
|
||||
|
||||
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
|
||||
@@ -498,9 +508,14 @@ private:
|
||||
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
|
||||
|
||||
// Async Buffers
|
||||
struct AsyncDownloadBatch {
|
||||
boost::container::small_vector<BufferCopy, 4> staging_copies;
|
||||
boost::container::small_vector<BufferCopy, 4> unified_copies;
|
||||
};
|
||||
|
||||
Common::OverlapRangeSet<DAddr> async_downloads;
|
||||
std::deque<std::optional<Async_Buffer>> async_buffers;
|
||||
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
|
||||
std::deque<AsyncDownloadBatch> pending_downloads;
|
||||
std::optional<Async_Buffer> current_buffer;
|
||||
|
||||
std::deque<Async_Buffer> async_buffers_death_ring;
|
||||
|
||||
@@ -195,6 +195,7 @@ private:
|
||||
void ReleaseThreadFunc(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("GPUFencingThread");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
|
||||
TFence current_fence;
|
||||
std::deque<std::function<void()>> current_operations;
|
||||
|
||||
+58
-30
@@ -10,6 +10,7 @@
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/settings.h"
|
||||
@@ -39,6 +40,19 @@
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
namespace {
|
||||
constexpr u64 GpuClockMultiplier(Settings::GpuClock clock) {
|
||||
switch (clock) {
|
||||
case Settings::GpuClock::Boost:
|
||||
return 256;
|
||||
case Settings::GpuClock::Overclock:
|
||||
return 512;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
struct GPU::Impl {
|
||||
explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_)
|
||||
: system{system_}
|
||||
@@ -116,7 +130,7 @@ struct GPU::Impl {
|
||||
[[nodiscard]] u64 RequestSyncOperation(Func&& action) {
|
||||
std::unique_lock lck{sync_request_mutex};
|
||||
const u64 fence = ++last_sync_fence;
|
||||
sync_requests.emplace_back(action);
|
||||
sync_requests.emplace_back(std::forward<Func>(action));
|
||||
return fence;
|
||||
}
|
||||
|
||||
@@ -145,14 +159,8 @@ struct GPU::Impl {
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 GetTicks() const {
|
||||
u64 gpu_tick = system.CoreTiming().GetGPUTicks();
|
||||
Settings::GpuOverclock overclock = Settings::values.fast_gpu_time.GetValue();
|
||||
|
||||
if (overclock != Settings::GpuOverclock::Normal) {
|
||||
gpu_tick /= 256 * u64(overclock);
|
||||
}
|
||||
|
||||
return gpu_tick;
|
||||
const u64 gpu_tick = system.CoreTiming().GetGPUTicks();
|
||||
return gpu_tick / GpuClockMultiplier(Settings::values.gpu_clock.GetValue());
|
||||
}
|
||||
|
||||
void RendererFrameEndNotify() {
|
||||
@@ -225,9 +233,9 @@ struct GPU::Impl {
|
||||
}
|
||||
|
||||
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers, std::vector<Service::Nvidia::NvFence>&& fences) {
|
||||
size_t num_fences{fences.size()};
|
||||
const size_t num_fences{fences.size()};
|
||||
size_t current_request_counter{};
|
||||
{
|
||||
if (num_fences != 0) {
|
||||
std::unique_lock<std::mutex> lk(request_swap_mutex);
|
||||
if (free_swap_counters.empty()) {
|
||||
current_request_counter = request_swap_counters.size();
|
||||
@@ -238,27 +246,42 @@ struct GPU::Impl {
|
||||
free_swap_counters.pop_front();
|
||||
}
|
||||
}
|
||||
const auto wait_fence = RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] {
|
||||
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
|
||||
if (num_fences == 0) {
|
||||
renderer->Composite(layers);
|
||||
}
|
||||
const auto executer = [this, current_request_counter, layers_copy = layers]() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(request_swap_mutex);
|
||||
if (--request_swap_counters[current_request_counter] != 0) {
|
||||
return;
|
||||
}
|
||||
free_swap_counters.push_back(current_request_counter);
|
||||
pending_composite_fence = RequestSyncOperation(
|
||||
[this, current_request_counter, num_fences, composite_layers = std::move(layers),
|
||||
composite_fences = std::move(fences)] {
|
||||
if (num_fences == 0) {
|
||||
renderer->Composite(composite_layers);
|
||||
return;
|
||||
}
|
||||
renderer->Composite(layers_copy);
|
||||
};
|
||||
for (size_t i = 0; i < num_fences; i++) {
|
||||
syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
|
||||
}
|
||||
});
|
||||
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
|
||||
const auto executer = [this, current_request_counter, composite_layers]() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(request_swap_mutex);
|
||||
if (--request_swap_counters[current_request_counter] != 0) {
|
||||
return;
|
||||
}
|
||||
free_swap_counters.push_back(current_request_counter);
|
||||
}
|
||||
renderer->Composite(composite_layers);
|
||||
};
|
||||
for (size_t i = 0; i < num_fences; i++) {
|
||||
syncpoint_manager.RegisterGuestAction(composite_fences[i].id,
|
||||
composite_fences[i].value, executer);
|
||||
}
|
||||
});
|
||||
gpu_thread.TickGPU(is_async);
|
||||
WaitForSyncOperation(wait_fence);
|
||||
}
|
||||
|
||||
void WaitForComposite() {
|
||||
const u64 fence = pending_composite_fence;
|
||||
if (fence == 0) {
|
||||
return;
|
||||
}
|
||||
pending_composite_fence = 0;
|
||||
if (shutting_down.load(std::memory_order_relaxed)) {
|
||||
return;
|
||||
}
|
||||
WaitForSyncOperation(fence);
|
||||
}
|
||||
|
||||
std::vector<u8> GetAppletCaptureBuffer() {
|
||||
@@ -311,6 +334,7 @@ struct GPU::Impl {
|
||||
std::deque<size_t> free_swap_counters;
|
||||
std::deque<size_t> request_swap_counters;
|
||||
std::mutex request_swap_mutex;
|
||||
u64 pending_composite_fence{};
|
||||
};
|
||||
|
||||
GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
|
||||
@@ -428,6 +452,10 @@ void GPU::RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
|
||||
impl->RequestComposite(std::move(layers), std::move(fences));
|
||||
}
|
||||
|
||||
void GPU::WaitForComposite() {
|
||||
impl->WaitForComposite();
|
||||
}
|
||||
|
||||
std::vector<u8> GPU::GetAppletCaptureBuffer() {
|
||||
return impl->GetAppletCaptureBuffer();
|
||||
}
|
||||
|
||||
@@ -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 2018 yuzu Emulator Project
|
||||
@@ -218,6 +218,8 @@ public:
|
||||
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
|
||||
std::vector<Service::Nvidia::NvFence>&& fences);
|
||||
|
||||
void WaitForComposite();
|
||||
|
||||
std::vector<u8> GetAppletCaptureBuffer();
|
||||
|
||||
/// Performs any additional setup necessary in order to begin GPU emulation.
|
||||
|
||||
@@ -30,6 +30,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
|
||||
thread = std::jthread([&](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("GPU");
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
Common::SetCurrentThreadToPerformanceCores();
|
||||
system.RegisterHostThread();
|
||||
|
||||
auto current_context = context.Acquire();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user