Compare commits

...

13 Commits

Author SHA1 Message Date
xbzk a0bc8bfde4 [video_core] Avoid stale macro upload references 2026-08-01 19:44:23 -03:00
xbzk e5b247db35 [frontend] Expose nxlink server mode 2026-08-01 19:44:23 -03:00
xbzk 44c0815dcd [loader] Add nxlink log server mode 2026-08-01 19:44:23 -03:00
xbzk b468602ffa [loader] Preserve nxlink argv markers 2026-08-01 19:44:23 -03:00
xbzk bc7219fd01 [network] Return in-progress for nonblocking connect 2026-08-01 19:44:23 -03:00
xbzk a1caf852ff [fs] Preserve guest file open modes in real VFS 2026-08-01 19:44:23 -03:00
xbzk 801c472017 [input] added option to disable wgi/xinput to prevent SDL GUIDE hack 2026-08-01 19:44:23 -03:00
xbzk 53a98f9de1 [qt_common] Avoid FS factory refresh while powered
Skip FileSystemController factory recreation during game-list repopulation while emulation is powered on.

This avoids poking live FS/VFS state during homebrew self-update and in-place NextLoad flows.
2026-08-01 19:44:22 -03:00
xbzk 6bad422d08 [core] Support libnx homebrew NextLoad handoff
Implement the homebrew NextLoad path used by libnx NROs to request another NRO from svcExitProcess.

Keep the existing process alive, rebuild the homebrew config and argv buffers, reset thread context, refresh process metadata, and add memory/address-space fallbacks needed for repeated in-place handoffs.

Reference: https://switchbrew.github.io/libnx/env_8h.html
2026-08-01 19:44:22 -03:00
xbzk 3469f3789f [nvdrv] Reset process resources for homebrew handoff
Track NVDRV sessions by process and aruid so in-place homebrew handoffs can close process-owned device files and sessions before loading the next NRO.

Also unlock nvmap device-shared pages during session cleanup to avoid stale GPU mappings leaking across repeated handoffs.
2026-08-01 19:44:22 -03:00
xbzk 221ffea4c1 [fsp] Preserve homebrew cwd for SDMC root aliases
Carry the initial homebrew working directory through filesystem process registration and FSP current-process state.

Use that cwd to resolve the homebrew cwd-plus-double-slash alias back to the SDMC root, allowing file browsers to navigate above their launch directory.
2026-08-01 19:44:22 -03:00
xbzk c87f6202d3 [fs] Allow real VFS files to be replaced while open
Add a Windows share-delete file open mode and use it for cached real VFS files. Close cached references before create, move, and delete so guest-side self-update flows can rename or replace files that Eden previously opened.

Also preserve Android real VFS full paths so homebrew path derivation does not lose the original file path.
2026-08-01 16:46:42 -03:00
xbzk 8ab9521cea [video_core] Restrict macro JIT zero-register skips
Only apply the zero-register ALU skip when the operation is safe to elide without changing carry/result semantics.

This avoids invalid-instruction floods seen with Macro JIT enabled while keeping the optimization for operations where a zero source is harmless.
2026-08-01 16:26:57 -03:00
51 changed files with 1994 additions and 199 deletions
@@ -50,6 +50,7 @@ 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"),
@@ -132,6 +132,15 @@ 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,
@@ -1288,6 +1288,7 @@ 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))
@@ -630,6 +630,16 @@
<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,6 +436,8 @@
<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>
+19 -15
View File
@@ -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) {
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;
alloc_start = virt_start;
} else {
return {}; // AS is full
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
}
}
}
@@ -364,6 +364,10 @@ 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
+62 -1
View File
@@ -4,6 +4,8 @@
// 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"
@@ -15,8 +17,10 @@
#include "common/logging.h"
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#include <share.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
@@ -95,10 +99,65 @@ 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
/**
@@ -254,7 +313,9 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File
errno = 0;
#ifdef _WIN32
if (flag != FileShareFlag::ShareNone) {
if (flag == FileShareFlag::ShareReadWriteDelete) {
file = OpenWithWindowsShareDelete(path, mode, type);
} else if (flag != FileShareFlag::ShareNone) {
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
} else {
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
+6 -5
View File
@@ -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 2021 yuzu Emulator Project
@@ -49,10 +49,11 @@ 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.
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.
};
enum class DirEntryFilter {
+12 -1
View File
@@ -702,7 +702,15 @@ 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
@@ -832,6 +840,9 @@ 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,
+1
View File
@@ -159,6 +159,7 @@ 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);
+2
View File
@@ -1139,6 +1139,8 @@ 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
+2
View File
@@ -52,6 +52,7 @@
#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"
@@ -397,6 +398,7 @@ struct System::Impl {
stop_event.request_stop();
core_timing.SyncPause(false);
Loader::HomebrewNxlink::StopServer();
Network::CancelPendingSocketOperations();
kernel.SuspendEmulation(true);
kernel.CloseServices();
+1
View File
@@ -12,6 +12,7 @@
namespace FileSys {
enum class OpenMode : u32 {
Default = 0,
Read = (1 << 0),
Write = (1 << 1),
AllowAppend = (1 << 2),
+7 -4
View File
@@ -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
@@ -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);
return root->GetFileRelative(path, perms);
}
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) const {
VirtualFile VfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
auto vec = Common::FS::SplitPathComponents(path);
if (vec.empty()) {
return nullptr;
@@ -224,7 +224,10 @@ VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
return nullptr;
}
return dir->GetFile(vec.back());
if (perms == OpenMode::Default) {
return dir->GetFile(vec.back());
}
return dir->GetFileRelative(vec.back(), perms);
}
VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
+2 -1
View File
@@ -201,7 +201,8 @@ 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) const;
virtual VirtualFile GetFileRelative(std::string_view path,
OpenMode perms = OpenMode::Default) const;
// Calls GetFileRelative(path) on the root of the current directory.
virtual VirtualFile GetFileAbsolute(std::string_view path) const;
+2 -2
View File
@@ -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) const {
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
for (const auto& layer : dirs) {
const auto file = layer->GetFileRelative(path);
const auto file = layer->GetFileRelative(path, perms);
if (file != nullptr)
return file;
}
+5 -1
View File
@@ -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
@@ -20,7 +23,8 @@ 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) const override;
VirtualFile GetFileRelative(std::string_view path,
OpenMode perms = OpenMode::Default) 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;
+51 -23
View File
@@ -12,7 +12,6 @@
#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"
@@ -46,17 +45,11 @@ bool IsWithinRoot(std::string_view root, std::string_view full_path) {
}
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
switch (mode) {
case OpenMode::Read:
return FS::FileAccessMode::Read;
case OpenMode::Write:
case OpenMode::ReadWrite:
case OpenMode::AllowAppend:
case OpenMode::All:
if (True(mode & OpenMode::Write) || True(mode & OpenMode::AllowAppend)) {
return FS::FileAccessMode::ReadWrite;
default:
return {};
}
return FS::FileAccessMode::Read;
}
} // Anonymous namespace
@@ -94,9 +87,11 @@ 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};
if (auto it = cache.find(path); it != cache.end()) {
const CacheKey cache_key{path, open_perms};
if (auto it = cache.find(cache_key); it != cache.end()) {
if (auto file = it->second.lock(); file) {
return file;
}
@@ -110,8 +105,9 @@ 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, perms, size, std::move(parent_path)));
cache[path] = file;
new RealVfsFile(*this, std::move(reference), path, open_perms, size,
std::move(parent_path)));
cache[cache_key] = file;
return file;
}
@@ -124,7 +120,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};
cache.erase(path);
CloseCachedFileReferenceLocked(path);
}
// Current usages of CreateFile expect to delete the contents of an existing file.
@@ -157,8 +153,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};
cache.erase(old_path);
cache.erase(new_path);
CloseCachedFileReferenceLocked(old_path);
CloseCachedFileReferenceLocked(new_path);
}
if (!FS::RenameFile(old_path, new_path)) {
return nullptr;
@@ -170,14 +166,15 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
{
std::scoped_lock lk{list_lock};
cache.erase(path);
CloseCachedFileReferenceLocked(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));
return std::shared_ptr<RealVfsDirectory>(
new RealVfsDirectory(*this, path, perms == OpenMode::Default ? OpenMode::Read : perms));
}
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
@@ -222,8 +219,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);
reference.file = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms),
FS::FileType::BinaryFile, FS::FileShareFlag::ShareReadWriteDelete);
if (reference.file) {
num_open_files++;
}
@@ -297,15 +294,45 @@ 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[0] != '/') {
if (!path.empty() && 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;
@@ -411,13 +438,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
RealVfsDirectory::~RealVfsDirectory() = default;
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path,
OpenMode open_perms) 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, perms);
return base.OpenFile(full_path, open_perms == OpenMode::Default ? perms : open_perms);
}
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
+7 -2
View File
@@ -10,6 +10,7 @@
#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"
@@ -49,8 +50,9 @@ public:
bool DeleteDirectory(std::string_view path) override;
private:
using CacheKey = std::pair<std::string, OpenMode>;
using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
std::map<CacheKey, std::weak_ptr<VfsFile>, std::less<>> cache;
ReferenceListType open_references;
ReferenceListType closed_references;
std::mutex list_lock;
@@ -63,6 +65,7 @@ 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;
@@ -85,6 +88,7 @@ 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;
@@ -115,7 +119,8 @@ class RealVfsDirectory : public VfsDirectory {
public:
~RealVfsDirectory() override;
VirtualFile GetFileRelative(std::string_view relative_path) const override;
VirtualFile GetFileRelative(std::string_view relative_path,
OpenMode perms = OpenMode::Default) 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;
+5
View File
@@ -670,6 +670,11 @@ 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;
}
+11
View File
@@ -210,6 +210,11 @@ 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);
@@ -947,6 +952,7 @@ 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.
@@ -1005,6 +1011,11 @@ 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;
+52
View File
@@ -6,7 +6,9 @@
#pragma once
#include <array>
#include <map>
#include <string_view>
#include "core/arm/arm_interface.h"
#include "core/file_sys/program_metadata.h"
@@ -87,6 +89,10 @@ 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{};
@@ -112,6 +118,7 @@ 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{};
@@ -139,6 +146,8 @@ 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);
@@ -231,6 +240,49 @@ 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;
+5 -4
View File
@@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-late
// SPDX-License-Identifier: GPL-2.0-or-later
// This file is automatically generated using svc_generator.py.
// DO NOT MODIFY IT MANUALLY
@@ -128,7 +129,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);
ExitProcess64From32(system, args);
}
static void SvcWrap_CreateThread64From32(Core::System& system, std::span<uint64_t, 8> args) {
@@ -1298,7 +1299,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);
ExitProcess64(system, args);
}
static void SvcWrap_CreateThread64(Core::System& system, std::span<uint64_t, 8> args) {
+6 -5
View File
@@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-late
// SPDX-License-Identifier: GPL-2.0-or-later
// This file is automatically generated using svc_generator.py.
// DO NOT MODIFY IT MANUALLY
@@ -25,7 +26,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);
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args);
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);
@@ -146,7 +147,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);
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args);
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);
@@ -267,7 +268,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);
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args);
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);
+215 -6
View File
@@ -4,6 +4,9 @@
// 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"
@@ -22,6 +25,179 @@ 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.
@@ -92,11 +268,19 @@ 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& page_table = GetCurrentProcess(system.Kernel()).GetPageTable();
auto& process = GetCurrentProcess(system.Kernel());
auto& page_table = process.GetPageTable();
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
// Set the memory attribute.
R_RETURN(page_table.SetMemoryPermission(address, size, perm));
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);
}
Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, u32 attr) {
@@ -135,14 +319,30 @@ 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& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
auto& process = GetCurrentProcess(system.Kernel());
auto& page_table = process.GetPageTable();
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
result.IsError()) {
return result;
}
R_RETURN(page_table.MapMemory(dst_addr, src_addr, size));
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);
}
/// Unmaps a region that was previously mapped with svcMapMemory
@@ -150,14 +350,23 @@ 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& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
auto& process = GetCurrentProcess(system.Kernel());
auto& page_table = process.GetPageTable();
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
result.IsError()) {
return result;
}
R_RETURN(page_table.UnmapMemory(dst_addr, src_addr, size));
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);
}
Result SetMemoryPermission64(Core::System& system, uint64_t address, uint64_t size,
+204 -5
View File
@@ -4,17 +4,216 @@
// 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) {
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args) {
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");
@@ -132,8 +331,8 @@ Result TerminateProcess(Core::System& system, Handle process_handle) {
R_THROW(ResultNotImplemented);
}
void ExitProcess64(Core::System& system) {
ExitProcess(system);
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
ExitProcess(system, args);
}
Result GetProcessId64(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
@@ -164,8 +363,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) {
ExitProcess(system);
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
ExitProcess(system, args);
}
Result GetProcessId64From32(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
+15 -8
View File
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
#include <utility>
#include "common/assert.h"
@@ -257,7 +258,7 @@ Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
npath.remove_prefix(1);
}
auto file = backing->GetFileRelative(npath);
auto file = backing->GetFileRelative(npath, mode);
if (file == nullptr) {
return FileSys::ResultPathNotFound;
}
@@ -333,14 +334,16 @@ FileSystemController::~FileSystemController() = default;
Result FileSystemController::RegisterProcess(
ProcessId process_id, ProgramId program_id,
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory) {
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory, std::string homebrew_initial_cwd) {
std::scoped_lock lk{registration_lock};
registrations.emplace(process_id, Registration{
.program_id = program_id,
.romfs_factory = std::move(romfs_factory),
.save_data_factory = CreateSaveDataFactory(program_id),
});
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),
});
LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
return ResultSuccess;
@@ -348,7 +351,8 @@ 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::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id,
std::string* out_homebrew_initial_cwd) {
std::scoped_lock lk{registration_lock};
const auto it = registrations.find(process_id);
@@ -361,6 +365,9 @@ 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;
}
+7 -4
View File
@@ -8,6 +8,7 @@
#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"
@@ -71,11 +72,12 @@ public:
~FileSystemController();
Result RegisterProcess(ProcessId process_id, ProgramId program_id,
std::shared_ptr<FileSys::RomFSFactory>&& factory);
std::shared_ptr<FileSys::RomFSFactory>&& factory,
std::string homebrew_initial_cwd = {});
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::shared_ptr<SaveDataController>* out_save_data_controller,
std::shared_ptr<RomFsController>* out_romfs_controller,
ProcessId process_id, std::string* out_homebrew_initial_cwd = nullptr);
void SetPackedUpdate(ProcessId process_id, FileSys::VirtualFile update_raw);
std::shared_ptr<SaveDataController> OpenSaveDataController();
@@ -136,6 +138,7 @@ 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,6 +4,9 @@
// 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"
@@ -13,10 +16,29 @@
namespace Service::FileSystem {
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 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_)} {
static const FunctionInfo functions[] = {
{0, D<&IFileSystem::CreateFile>, "CreateFile"},
{1, D<&IFileSystem::DeleteFile>, "DeleteFile"},
@@ -43,7 +65,8 @@ 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);
R_RETURN(backend->CreateFile(FileSys::Path(path->str), size));
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
R_RETURN(backend->CreateFile(FileSys::Path(fs_path.c_str()), size));
}
Result IFileSystem::DeleteFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path) {
@@ -94,7 +117,8 @@ Result IFileSystem::OpenFile(OutInterface<IFile> out_interface,
LOG_DEBUG(Service_FS, "called. file={}, mode={}", path->str, mode);
FileSys::VirtualFile vfs_file{};
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(path->str),
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(fs_path.c_str()),
static_cast<FileSys::OpenMode>(mode)));
*out_interface = std::make_shared<IFile>(system, vfs_file);
@@ -107,7 +131,8 @@ Result IFileSystem::OpenDirectory(OutInterface<IDirectory> out_interface,
LOG_DEBUG(Service_FS, "called. directory={}, mode={}", path->str, mode);
FileSys::VirtualDir vfs_dir{};
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(path->str),
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(fs_path.c_str()),
static_cast<FileSys::OpenDirectoryMode>(mode)));
*out_interface = std::make_shared<IDirectory>(system, vfs_dir,
@@ -120,7 +145,8 @@ Result IFileSystem::GetEntryType(
LOG_DEBUG(Service_FS, "called. file={}", path->str);
FileSys::DirectoryEntryType vfs_entry_type{};
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(path->str)));
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(fs_path.c_str())));
*out_type = static_cast<u32>(vfs_entry_type);
R_SUCCEED();
@@ -1,8 +1,13 @@
// 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"
@@ -23,7 +28,8 @@ class IDirectory;
class IFileSystem final : public ServiceFramework<IFileSystem> {
public:
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_);
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
std::string homebrew_initial_cwd_ = {});
Result CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, s32 option,
s64 size);
@@ -55,6 +61,7 @@ public:
private:
std::unique_ptr<FileSys::Fsa::IFileSystem> backend;
SizeGetter size_getter;
std::string homebrew_initial_cwd;
};
} // namespace Service::FileSystem
@@ -192,8 +192,9 @@ Result FSP_SRV::SetCurrentProcess(ClientProcessId pid) {
LOG_DEBUG(Service_FS, "called. current_process_id={:#016x}", current_process_id);
R_RETURN(
fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id));
homebrew_initial_cwd.clear();
R_RETURN(fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller,
current_process_id, &homebrew_initial_cwd));
}
Result FSP_SRV::OpenFileSystemWithPatch(OutInterface<IFileSystem> out_interface,
@@ -224,7 +225,8 @@ 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));
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard),
homebrew_initial_cwd);
R_SUCCEED();
}
@@ -7,6 +7,7 @@
#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"
@@ -123,6 +124,7 @@ 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;
};
+91 -1
View File
@@ -1,10 +1,15 @@
// 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"
@@ -40,6 +45,16 @@ 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;
@@ -48,7 +63,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
if (!session.is_active) {
continue;
}
if (session.process == process) {
if (IsSameProcess(session.process, process)) {
session.ref_count++;
return session.id;
}
@@ -116,7 +131,15 @@ 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;
}
@@ -134,6 +157,73 @@ 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,7 +9,10 @@
#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"
@@ -59,6 +62,10 @@ 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);
+56 -8
View File
@@ -6,10 +6,12 @@
// 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"
@@ -326,19 +328,65 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
}
void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
auto handles_copy = [&] {
auto* session = core.GetSession(session_id);
auto* process = session != nullptr ? session->process : nullptr;
auto handle_ids = [&] {
std::scoped_lock lk{handles_lock};
return handles;
std::vector<Handle::Id> ids;
ids.reserve(handles.size());
for (const auto& entry : handles) {
ids.push_back(entry.first);
}
return ids;
}();
for (auto& [id, handle] : handles_copy) {
{
std::scoped_lock lk{handle->mutex};
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
continue;
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;
}
}
FreeHandle(id, false);
}
}
+104 -1
View File
@@ -1,12 +1,17 @@
// 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"
@@ -133,6 +138,9 @@ 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;
}
@@ -204,6 +212,7 @@ NvResult Module::Close(DeviceFD fd) {
itr->second->OnClose(fd);
open_files.erase(itr);
open_file_sessions.erase(fd);
return NvResult::Success;
}
@@ -228,4 +237,98 @@ 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
+10
View File
@@ -12,6 +12,7 @@
#include <memory>
#include <span>
#include <string>
#include <vector>
#include <ankerl/unordered_dense.h>
#include "common/common_types.h"
@@ -26,6 +27,7 @@ class System;
namespace Kernel {
class KEvent;
class KProcess;
}
namespace Service::Nvidia {
@@ -89,6 +91,9 @@ 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;
@@ -106,12 +111,17 @@ 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,7 +212,10 @@ 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);
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#X}", pid);
if (is_initialized) {
nvdrv->TrackSessionAruid(session_id, pid);
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
+4 -2
View File
@@ -40,6 +40,7 @@ namespace Network {
namespace {
enum class CallType {
Connect,
Send,
Other,
};
@@ -131,7 +132,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
case WSAENOTCONN:
return Errno::NOTCONN;
case WSAEWOULDBLOCK:
return Errno::AGAIN;
return call_type == CallType::Connect ? Errno::INPROGRESS : Errno::AGAIN;
case WSAECONNREFUSED:
return Errno::CONNREFUSED;
case WSAECONNABORTED:
@@ -563,6 +564,7 @@ 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) \
@@ -888,7 +890,7 @@ Errno Socket::Connect(SockAddrIn addr_in) {
return Errno::SUCCESS;
}
return GetAndLogLastError();
return GetAndLogLastError(CallType::Connect);
}
std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
+311
View File
@@ -0,0 +1,311 @@
// 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
+30
View File
@@ -0,0 +1,30 @@
// 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
+507 -55
View File
@@ -4,17 +4,20 @@
// 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"
@@ -23,11 +26,14 @@
#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"
@@ -152,8 +158,432 @@ 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) {
const std::vector<u8>& data, std::string nro_path,
std::string file_name) {
if (data.size() < sizeof(NroHeader)) {
return {};
}
@@ -195,47 +625,49 @@ 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 = "homebrew ";
argv_string.push_back(' ');
argv_string += program_args;
argv_string.push_back('\0');
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");
}
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_argv);
program_image.resize(args_offset_in_image + entries_and_argv);
}
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;
}
}
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);
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);
size_t image_size = program_image.size();
#ifdef HAS_NCE
@@ -263,6 +695,9 @@ 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 {
@@ -297,31 +732,44 @@ 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());
if (!argv_string.empty()) {
constexpr u32 kEntryEndOfList = 0;
constexpr u32 kEntryMainThreadHandle = 1;
constexpr u32 kEntryArgv = 5;
constexpr u32 kEntryAppletType = 7;
constexpr u32 kAppletTypeApplication = 0;
process.SetHomebrewInPlaceNextLoad(false);
if (nxlink_argv_marker) {
process.SetHomebrewNxlinkArgvMarker(*nxlink_argv_marker);
} else {
process.ClearHomebrewNxlinkArgvMarker();
}
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, nxlink_argv_marker);
{
const u64 base = GetInteger(process.GetEntryPoint());
const u64 config_addr = base + args_offset_in_image;
const u64 argv_addr = config_addr + kConfigTableSize;
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 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}},
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}},
};
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});
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());
}
if (exit_process_offset_in_image) {
process.SetArgReturnAddress(Kernel::KProcessAddress{base + *exit_process_offset_in_image});
}
process.SetMainThreadHandleAddr(Kernel::KProcessAddress{config_addr + kMainThreadHandleValueOffset});
SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr);
}
return true;
@@ -329,7 +777,8 @@ 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());
return LoadNroImpl(system, process, nro_file.ReadAllBytes(), nro_file.GetFullPath(),
nro_file.GetName());
}
AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::System& system) {
@@ -343,10 +792,13 @@ 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()));
system.GetFileSystemController()),
homebrew_initial_cwd);
is_loaded = true;
return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
+8
View File
@@ -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
@@ -19,10 +22,15 @@ 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:
+7
View File
@@ -648,6 +648,13 @@ 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");
@@ -302,6 +302,8 @@ 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."));
+3 -1
View File
@@ -159,7 +159,9 @@ void GameListModel::RemoveFavorite(u64 program_id) {
void GameListModel::Repopulate() {
current_worker.reset();
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
if (!QtCommon::system->IsPoweredOn()) {
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
}
PopulateAsync(UISettings::values.game_dirs);
}
+23 -22
View File
@@ -782,13 +782,17 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
const bool valid_operation = !is_a_zero && !is_b_zero;
[[maybe_unused]] const bool is_move_operation = !is_a_zero && is_b_zero;
const bool has_zero_register = is_a_zero || is_b_zero;
const bool no_zero_reg_skip = opcode.alu_operation == Macro::ALUOperation::AddWithCarry ||
opcode.alu_operation == Macro::ALUOperation::SubtractWithBorrow;
const bool zero_reg_skip =
optimizer.zero_reg_skip && !is_a_zero && is_b_zero &&
(opcode.alu_operation == Macro::ALUOperation::Xor ||
opcode.alu_operation == Macro::ALUOperation::Or ||
(optimizer.can_skip_carry && (opcode.alu_operation == Macro::ALUOperation::Add ||
opcode.alu_operation == Macro::ALUOperation::Subtract)));
Xbyak::Reg32 src_a;
Xbyak::Reg32 src_b;
if (!optimizer.zero_reg_skip || no_zero_reg_skip) {
if (!zero_reg_skip) {
src_a = Compile_GetRegister(opcode.src_a, RESULT);
src_b = Compile_GetRegister(opcode.src_b, eax);
} else {
@@ -804,7 +808,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
switch (opcode.alu_operation) {
case Macro::ALUOperation::Add:
if (optimizer.zero_reg_skip) {
if (zero_reg_skip) {
if (valid_operation) {
add(src_a, src_b);
}
@@ -822,7 +826,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
setc(byte[STATE + offsetof(JITState, carry_flag)]);
break;
case Macro::ALUOperation::Subtract:
if (optimizer.zero_reg_skip) {
if (zero_reg_skip) {
if (valid_operation) {
sub(src_a, src_b);
has_emitted = true;
@@ -841,7 +845,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
setc(byte[STATE + offsetof(JITState, carry_flag)]);
break;
case Macro::ALUOperation::Xor:
if (optimizer.zero_reg_skip) {
if (zero_reg_skip) {
if (valid_operation) {
xor_(src_a, src_b);
}
@@ -850,7 +854,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
}
break;
case Macro::ALUOperation::Or:
if (optimizer.zero_reg_skip) {
if (zero_reg_skip) {
if (valid_operation) {
or_(src_a, src_b);
}
@@ -859,7 +863,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
}
break;
case Macro::ALUOperation::And:
if (optimizer.zero_reg_skip) {
if (zero_reg_skip) {
if (!has_zero_register) {
and_(src_a, src_b);
}
@@ -868,7 +872,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
}
break;
case Macro::ALUOperation::AndNot:
if (optimizer.zero_reg_skip) {
if (zero_reg_skip) {
if (!is_a_zero) {
not_(src_b);
and_(src_a, src_b);
@@ -879,7 +883,7 @@ void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) {
}
break;
case Macro::ALUOperation::Nand:
if (optimizer.zero_reg_skip) {
if (zero_reg_skip) {
if (!is_a_zero) {
and_(src_a, src_b);
not_(src_a);
@@ -1387,29 +1391,26 @@ void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u
std::span<const u32> code;
auto macro_code = uploaded_macro_code.find(method);
if (macro_code == uploaded_macro_code.end()) {
std::optional<u32> mid_method;
for (const auto& [method_base, uploaded_code] : uploaded_macro_code) {
for (auto it = uploaded_macro_code.begin(); it != uploaded_macro_code.end(); ++it) {
const auto& [method_base, uploaded_code] = *it;
if (method >= method_base && (method - method_base) < uploaded_code.size()) {
mid_method = method_base;
macro_code = it;
break;
}
}
if (!mid_method) {
if (macro_code == uploaded_macro_code.end()) {
ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method);
return;
}
const auto source = uploaded_macro_code.find(*mid_method);
ASSERT(source != uploaded_macro_code.end());
const auto rebased_method = method - *mid_method;
std::vector<u32> rebased_code(source->second.begin() + rebased_method,
source->second.end());
const auto rebased_method = method - macro_code->first;
std::vector<u32> rebased_code(macro_code->second.begin() + rebased_method,
macro_code->second.end());
const auto [it, inserted] = uploaded_macro_code.emplace(method, std::move(rebased_code));
ASSERT(inserted);
code = it->second;
} else {
code = macro_code->second;
macro_code = it;
}
code = macro_code->second;
auto& ci = macro_cache[method];
ci.hash = Common::HashRange(code.begin(), code.end());
@@ -67,6 +67,11 @@ void ConfigureDebug::SetConfiguration() {
ui->homebrew_args_edit->setEnabled(runtime_lock);
ui->homebrew_args_edit->setText(
QString::fromStdString(Settings::values.program_args.GetValue()));
ui->nxlink_server_mode->setEnabled(runtime_lock);
const int nxlink_server_mode =
static_cast<int>(Settings::values.homebrew_nxlink_server_mode.GetValue());
ui->nxlink_server_mode->setCurrentIndex(
nxlink_server_mode < ui->nxlink_server_mode->count() ? nxlink_server_mode : 0);
ui->toggle_console->setEnabled(runtime_lock);
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
ui->fs_access_log->setEnabled(runtime_lock);
@@ -116,6 +121,8 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.log_flush_line = ui->flush_line->isChecked();
Settings::values.censor_username = ui->censor_username->isChecked();
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
Settings::values.homebrew_nxlink_server_mode =
static_cast<Settings::HomebrewNxlinkServerMode>(ui->nxlink_server_mode->currentIndex());
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
Settings::values.reporting_services = ui->reporting_services->isChecked();
Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
+30 -9
View File
@@ -309,20 +309,40 @@
<property name="title">
<string>Homebrew</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Arguments String</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="homebrew_args_edit"/>
</item>
<item>
<widget class="QLabel" name="nxlink_server_mode_label">
<property name="text">
<string>nxlink Server</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="nxlink_server_mode">
<property name="toolTip">
<string>Starts a local nxlink server for homebrew stdout/stderr streams.</string>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Arguments String</string>
</property>
</widget>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<widget class="QLineEdit" name="homebrew_args_edit"/>
<property name="text">
<string>Eden Log</string>
</property>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
@@ -800,6 +820,7 @@
<tabstop>extended_logging</tabstop>
<tabstop>open_log_button</tabstop>
<tabstop>homebrew_args_edit</tabstop>
<tabstop>nxlink_server_mode</tabstop>
<tabstop>enable_graphics_debugging</tabstop>
<tabstop>enable_shader_feedback</tabstop>
<tabstop>enable_nsight_aftermath</tabstop>
@@ -99,6 +99,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(Core::HID::HIDCore& hid_core_, QW
#ifndef _WIN32
ui->enable_raw_input->setVisible(false);
ui->disable_wgi_xinput->setVisible(false);
#endif
LoadConfiguration();
@@ -139,6 +140,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked();
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
Settings::values.disable_wgi_xinput = ui->disable_wgi_xinput->isChecked();
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
@@ -174,6 +176,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard.GetValue());
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
ui->disable_wgi_xinput->setChecked(Settings::values.disable_wgi_xinput.GetValue());
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
@@ -2757,6 +2757,22 @@
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="disable_wgi_xinput">
<property name="toolTip">
<string>Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff</string>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>23</height>
</size>
</property>
<property name="text">
<string>Disable SDL WGI/XInput (Requires restart)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>