Core and UI: Implement missing service functions and QLaunch improvements

This commit is contained in:
collecting
2026-02-03 15:42:35 -05:00
parent 544456b8be
commit c5eef10697
72 changed files with 3529 additions and 1518 deletions

View File

@@ -44,7 +44,7 @@ protected:
void SaveUIValues() override;
void SaveUIGamelistValues() override;
void SaveUILayoutValues() override;
void SaveMultiplayerValues();
void SaveMultiplayerValues() override;
void SaveNetworkValues();
public:

File diff suppressed because it is too large Load Diff

View File

@@ -12,27 +12,29 @@
#include <QLineEdit>
#include <QList>
#include <QListView>
#include <QPushButton>
#include <QSlider>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QProgressBar>
#include <QPushButton>
#include <QResizeEvent>
#include <QSlider>
#include <QStandardItemModel>
#include <QString>
#include <QResizeEvent>
#include <QTimer>
#include <QToolButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <QVector>
#include <QWidget>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "citron/compatibility_list.h"
#include "citron/multiplayer/state.h"
#include "citron/play_time_manager.h"
#include "common/common_types.h"
#include "core/core.h"
#include "uisettings.h"
#include "citron/compatibility_list.h"
#include "citron/play_time_manager.h"
#include "citron/multiplayer/state.h"
class ControllerNavigation;
class GameListWorker;
@@ -43,8 +45,8 @@ enum class AmLaunchType;
enum class StartGameType;
namespace FileSys {
class ManualContentProvider;
class VfsFilesystem;
class ManualContentProvider;
class VfsFilesystem;
} // namespace FileSys
enum class GameListOpenTarget {
@@ -190,8 +192,10 @@ private:
void FilterTreeView(const QString& filter_text);
void PopupContextMenu(const QPoint& menu_location);
void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path, const QString& game_name);
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path,
const QString& game_name);
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected,
bool show_hidden_action = true);
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
void AddFavoritesPopup(QMenu& context_menu);

View File

@@ -11,16 +11,22 @@
#include <utility>
#include <vector>
#include <QCryptographicHash>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QSettings>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QSettings>
#include <QStandardPaths>
#include <QCryptographicHash>
#include "citron/compatibility_list.h"
#include "citron/game_list.h"
#include "citron/game_list_p.h"
#include "citron/game_list_worker.h"
#include "citron/uisettings.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "core/core.h"
@@ -33,11 +39,7 @@
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/loader/loader.h"
#include "citron/compatibility_list.h"
#include "citron/game_list.h"
#include "citron/game_list_p.h"
#include "citron/game_list_worker.h"
#include "citron/uisettings.h"
namespace {
@@ -76,9 +78,8 @@ std::string GetCacheKey(const std::string& file_path) {
}
const auto path_str = Common::FS::PathToUTF8String(normalized_path);
const auto hash = QCryptographicHash::hash(
QByteArray::fromStdString(path_str),
QCryptographicHash::Sha256);
const auto hash =
QCryptographicHash::hash(QByteArray::fromStdString(path_str), QCryptographicHash::Sha256);
return hash.toHex().toStdString();
}
@@ -90,7 +91,8 @@ void LoadGameMetadataCache() {
game_metadata_cache.clear();
const auto cache_dir = Common::FS::GetCitronPath(Common::FS::CitronPath::CacheDir) / "game_list";
const auto cache_dir =
Common::FS::GetCitronPath(Common::FS::CitronPath::CacheDir) / "game_list";
const auto cache_file = Common::FS::PathToUTF8String(cache_dir / "game_metadata_cache.json");
if (!Common::FS::Exists(cache_file)) {
@@ -115,15 +117,19 @@ void LoadGameMetadataCache() {
const std::string key = entry[QStringLiteral("key")].toString().toStdString();
CachedGameMetadata metadata;
metadata.program_id = entry[QStringLiteral("program_id")].toString().toULongLong(nullptr, 16);
metadata.file_type = static_cast<Loader::FileType>(entry[QStringLiteral("file_type")].toInt());
metadata.file_size = static_cast<std::size_t>(entry[QStringLiteral("file_size")].toVariant().toULongLong());
metadata.program_id =
entry[QStringLiteral("program_id")].toString().toULongLong(nullptr, 16);
metadata.file_type =
static_cast<Loader::FileType>(entry[QStringLiteral("file_type")].toInt());
metadata.file_size =
static_cast<std::size_t>(entry[QStringLiteral("file_size")].toVariant().toULongLong());
metadata.title = entry[QStringLiteral("title")].toString().toStdString();
metadata.file_path = entry[QStringLiteral("file_path")].toString().toStdString();
metadata.modification_time = entry[QStringLiteral("modification_time")].toVariant().toLongLong();
metadata.modification_time =
entry[QStringLiteral("modification_time")].toVariant().toLongLong();
const QByteArray icon_data = QByteArray::fromBase64(
entry[QStringLiteral("icon")].toString().toUtf8());
const QByteArray icon_data =
QByteArray::fromBase64(entry[QStringLiteral("icon")].toString().toUtf8());
metadata.icon.assign(icon_data.begin(), icon_data.end());
if (metadata.IsValid()) {
@@ -138,7 +144,8 @@ void SaveGameMetadataCache() {
return;
}
const auto cache_dir = Common::FS::GetCitronPath(Common::FS::CitronPath::CacheDir) / "game_list";
const auto cache_dir =
Common::FS::GetCitronPath(Common::FS::CitronPath::CacheDir) / "game_list";
const auto cache_file = Common::FS::PathToUTF8String(cache_dir / "game_metadata_cache.json");
void(Common::FS::CreateParentDirs(cache_file));
@@ -154,7 +161,8 @@ void SaveGameMetadataCache() {
entry[QStringLiteral("file_size")] = static_cast<qint64>(metadata.file_size);
entry[QStringLiteral("title")] = QString::fromStdString(metadata.title);
entry[QStringLiteral("file_path")] = QString::fromStdString(metadata.file_path);
entry[QStringLiteral("modification_time")] = static_cast<qint64>(metadata.modification_time);
entry[QStringLiteral("modification_time")] =
static_cast<qint64>(metadata.modification_time);
const QByteArray icon_data(reinterpret_cast<const char*>(metadata.icon.data()),
static_cast<int>(metadata.icon.size()));
@@ -189,8 +197,8 @@ const CachedGameMetadata* GetCachedGameMetadata(const std::string& file_path) {
return nullptr;
}
const auto mod_time_seconds = std::chrono::duration_cast<std::chrono::seconds>(
mod_time.time_since_epoch()).count();
const auto mod_time_seconds =
std::chrono::duration_cast<std::chrono::seconds>(mod_time.time_since_epoch()).count();
const std::string key = GetCacheKey(file_path);
const auto it = game_metadata_cache.find(key);
@@ -211,7 +219,8 @@ const CachedGameMetadata* GetCachedGameMetadata(const std::string& file_path) {
// Store game metadata in cache
void CacheGameMetadata(const std::string& file_path, u64 program_id, Loader::FileType file_type,
std::size_t file_size, const std::string& title, const std::vector<u8>& icon) {
std::size_t file_size, const std::string& title,
const std::vector<u8>& icon) {
if (!UISettings::values.cache_game_list) {
return;
}
@@ -222,8 +231,8 @@ void CacheGameMetadata(const std::string& file_path, u64 program_id, Loader::Fil
return;
}
const auto mod_time_seconds = std::chrono::duration_cast<std::chrono::seconds>(
mod_time.time_since_epoch()).count();
const auto mod_time_seconds =
std::chrono::duration_cast<std::chrono::seconds>(mod_time.time_since_epoch()).count();
const std::string key = GetCacheKey(file_path);
@@ -411,12 +420,13 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
return out;
}
QList<QStandardItem*> MakeGameListEntry(
const std::string& path, const std::string& name, const std::size_t size,
const std::vector<u8>& icon, Loader::AppLoader& loader, u64 program_id,
const CompatibilityList& compatibility_list, const PlayTime::PlayTimeManager& play_time_manager,
const FileSys::PatchManager& patch,
const std::map<u64, std::pair<int, int>>& online_stats) {
QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
const std::size_t size, const std::vector<u8>& icon,
Loader::AppLoader& loader, u64 program_id,
const CompatibilityList& compatibility_list,
const PlayTime::PlayTimeManager& play_time_manager,
const FileSys::PatchManager& patch,
const std::map<u64, std::pair<int, int>>& online_stats) {
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
// The game list uses this as compatibility number for untested games
@@ -432,17 +442,18 @@ QList<QStandardItem*> MakeGameListEntry(
auto it_stats = online_stats.find(program_id);
if (it_stats != online_stats.end()) {
const auto& stats = it_stats->second;
online_text = QStringLiteral("Players: %1 | Servers: %2").arg(stats.first).arg(stats.second);
online_text =
QStringLiteral("Players: %1 | Servers: %2").arg(stats.first).arg(stats.second);
}
QList<QStandardItem*> list{
new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name),
file_type_string, program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
new GameListItemSize(size),
new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
new GameListItemOnline(online_text)};
QList<QStandardItem*> list{new GameListItemPath(FormatGameName(path), icon,
QString::fromStdString(name), file_type_string,
program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
new GameListItemSize(size),
new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
new GameListItemOnline(online_text)};
const auto patch_versions = GetGameListCachedObject(
fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
@@ -510,7 +521,8 @@ void GameListWorker::RecordEvent(F&& func) {
emit DataAvailable();
}
void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir, const std::map<u64, std::pair<int, int>>& online_stats) {
void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir,
const std::map<u64, std::pair<int, int>>& online_stats) {
using namespace FileSys;
const auto& cache = system.GetContentProviderUnion();
@@ -556,17 +568,21 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir, const std::map
GetMetadataFromControlNCA(patch, *control, icon, name);
}
auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
program_id, compatibility_list, play_time_manager, patch, online_stats);
auto entry =
MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, program_id,
compatibility_list, play_time_manager, patch, online_stats);
RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
}
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
GameListDir* parent_dir, const std::map<u64, std::pair<int, int>>& online_stats,
GameListDir* parent_dir,
const std::map<u64, std::pair<int, int>>& online_stats,
int& processed_files, int total_files) {
const auto callback = [this, target, parent_dir, &online_stats, &processed_files, total_files](const std::filesystem::path& path) -> bool {
if (stop_requested) return false;
const auto callback = [this, target, parent_dir, &online_stats, &processed_files,
total_files](const std::filesystem::path& path) -> bool {
if (stop_requested)
return false;
const auto physical_name = Common::FS::PathToUTF8String(path);
@@ -583,19 +599,49 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
// Cache Check
const auto* cached = GetCachedGameMetadata(physical_name);
if (cached && cached->IsValid() && (target == ScanTarget::PopulateGameList || target == ScanTarget::Both)) {
if ((cached->program_id & 0xFFF) == 0) {
const FileSys::PatchManager patch{cached->program_id, system.GetFileSystemController(), system.GetContentProvider()};
auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read);
if (file) {
if (cached && cached->IsValid() &&
(target == ScanTarget::PopulateGameList || target == ScanTarget::Both)) {
auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read);
if (file) {
// 1. Register with provider if requested
if (target == ScanTarget::FillManualContentProvider || target == ScanTarget::Both) {
if (cached->file_type == Loader::FileType::NCA) {
provider->AddEntry(
FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
cached->program_id, file);
} else if (cached->file_type == Loader::FileType::XCI ||
cached->file_type == Loader::FileType::NSP) {
const auto nsp = cached->file_type == Loader::FileType::NSP
? std::make_shared<FileSys::NSP>(file)
: FileSys::XCI{file}.GetSecurePartitionNSP();
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
provider->AddEntry(entry.first.first, entry.first.second,
title.first, entry.second->GetBaseFile());
}
}
}
}
// 2. Populate UI if requested (only for base games)
if ((cached->program_id & 0xFFF) == 0 &&
(target == ScanTarget::PopulateGameList || target == ScanTarget::Both)) {
const FileSys::PatchManager patch{cached->program_id,
system.GetFileSystemController(),
system.GetContentProvider()};
auto loader = Loader::GetLoader(system, file);
if (loader) {
auto entry = MakeGameListEntry(physical_name, cached->title, cached->file_size, cached->icon, *loader,
cached->program_id, compatibility_list, play_time_manager, patch, online_stats);
RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
auto entry = MakeGameListEntry(physical_name, cached->title,
cached->file_size, cached->icon, *loader,
cached->program_id, compatibility_list,
play_time_manager, patch, online_stats);
RecordEvent(
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
}
}
processed_files++;
emit ProgressUpdated(std::min(100, (processed_files * 100) / total_files));
return true;
@@ -622,12 +668,18 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
if (target == ScanTarget::FillManualContentProvider || target == ScanTarget::Both) {
if (file_type == Loader::FileType::NCA) {
provider->AddEntry(FileSys::TitleType::Application, FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, file);
} else if (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP) {
const auto nsp = file_type == Loader::FileType::NSP ? std::make_shared<FileSys::NSP>(file) : FileSys::XCI{file}.GetSecurePartitionNSP();
provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
program_id, file);
} else if (file_type == Loader::FileType::XCI ||
file_type == Loader::FileType::NSP) {
const auto nsp = file_type == Loader::FileType::NSP
? std::make_shared<FileSys::NSP>(file)
: FileSys::XCI{file}.GetSecurePartitionNSP();
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
provider->AddEntry(entry.first.first, entry.first.second, title.first, entry.second->GetBaseFile());
provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
@@ -644,9 +696,13 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
CacheGameMetadata(physical_name, program_id, file_type, file_size, name, icon);
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()};
auto entry = MakeGameListEntry(physical_name, name, file_size, icon, *loader, program_id, compatibility_list, play_time_manager, patch, online_stats);
RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
auto entry = MakeGameListEntry(physical_name, name, file_size, icon, *loader,
program_id, compatibility_list,
play_time_manager, patch, online_stats);
RecordEvent(
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
}
}
}
@@ -657,7 +713,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
};
if (deep_scan) {
Common::FS::IterateDirEntriesRecursively(dir_path, callback, Common::FS::DirEntryFilter::File);
Common::FS::IterateDirEntriesRecursively(dir_path, callback,
Common::FS::DirEntryFilter::File);
} else {
Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File);
}
@@ -685,7 +742,8 @@ void GameListWorker::run() {
int processed_files = 0;
for (const auto& game_dir : game_dirs) {
if (game_dir.path == "SDMC" || game_dir.path == "UserNAND" || game_dir.path == "SysNAND") continue;
if (game_dir.path == "SDMC" || game_dir.path == "UserNAND" || game_dir.path == "SysNAND")
continue;
auto count_callback = [&](const std::filesystem::path& path) -> bool {
const std::string physical_name = Common::FS::PathToUTF8String(path);
@@ -696,28 +754,34 @@ void GameListWorker::run() {
};
if (game_dir.deep_scan) {
Common::FS::IterateDirEntriesRecursively(game_dir.path, count_callback, Common::FS::DirEntryFilter::File);
Common::FS::IterateDirEntriesRecursively(game_dir.path, count_callback,
Common::FS::DirEntryFilter::File);
} else {
Common::FS::IterateDirEntries(game_dir.path, count_callback, Common::FS::DirEntryFilter::File);
Common::FS::IterateDirEntries(game_dir.path, count_callback,
Common::FS::DirEntryFilter::File);
}
}
if (total_files <= 0) total_files = 1;
if (total_files <= 0)
total_files = 1;
const auto DirEntryReady = [&](GameListDir* game_list_dir) {
RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); });
};
for (UISettings::GameDir& game_dir : game_dirs) {
if (stop_requested) break;
if (stop_requested)
break;
if (game_dir.path == "SDMC" || game_dir.path == "UserNAND" || game_dir.path == "SysNAND") continue;
if (game_dir.path == "SDMC" || game_dir.path == "UserNAND" || game_dir.path == "SysNAND")
continue;
watch_list.append(QString::fromStdString(game_dir.path));
auto* const game_list_dir = new GameListDir(game_dir);
DirEntryReady(game_list_dir);
ScanFileSystem(ScanTarget::Both, game_dir.path, game_dir.deep_scan, game_list_dir, online_stats, processed_files, total_files);
ScanFileSystem(ScanTarget::Both, game_dir.path, game_dir.deep_scan, game_list_dir,
online_stats, processed_files, total_files);
}
RecordEvent([this](GameList* game_list) { game_list->DonePopulating(watch_list); });

File diff suppressed because it is too large Load Diff

View File

@@ -4,23 +4,24 @@
#pragma once
#include <filesystem>
#include <memory>
#include <optional>
#include <filesystem>
#include <QMainWindow>
#include <QMessageBox>
#include <QPushButton>
#include <QTimer>
#include <QTranslator>
#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
#include "configuration/qt_config.h"
#include "frontend_common/content_manager.h"
#include "input_common/drivers/tas_input.h"
#include "citron/compatibility_list.h"
#include "citron/hotkeys.h"
#include "citron/util/controller_navigation.h"
#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
#include "configuration/qt_config.h"
#include "core/perf_stats.h"
#include "frontend_common/content_manager.h"
#include "input_common/drivers/tas_input.h"
#ifdef __unix__
#include <QVariant>
@@ -61,23 +62,61 @@ class QtControllerSelectorDialog;
class QtProfileSelectionDialog;
class QtSoftwareKeyboardDialog;
class QtNXWebEngineView;
namespace Updater { class UpdaterDialog; }
namespace Updater {
class UpdaterDialog;
}
enum class StartGameType { Normal, Global };
namespace Core { enum class SystemResultStatus : u32; class System; }
namespace Core::Frontend { struct CabinetParameters; struct ControllerParameters; struct InlineAppearParameters; struct InlineTextParameters; struct KeyboardInitializeParameters; struct ProfileSelectParameters; }
namespace DiscordRPC { class DiscordInterface; }
namespace PlayTime { class PlayTimeManager; }
namespace FileSys { class ContentProvider; class ManualContentProvider; class VfsFilesystem; }
namespace InputCommon { class InputSubsystem; }
namespace Service::AM { struct FrontendAppletParameters; enum class AppletId : u32; }
namespace Service::AM::Frontend { enum class SwkbdResult : u32; enum class SwkbdTextCheckResult : u32; enum class SwkbdReplyType : u32; enum class WebExitReason : u32; }
namespace Service::NFC { class NfcDevice; }
namespace Service::NFP { enum class CabinetMode : u8; }
namespace Ui { class MainWindow; }
namespace Core {
enum class SystemResultStatus : u32;
class System;
} // namespace Core
namespace Core::Frontend {
struct CabinetParameters;
struct ControllerParameters;
struct InlineAppearParameters;
struct InlineTextParameters;
struct KeyboardInitializeParameters;
struct ProfileSelectParameters;
} // namespace Core::Frontend
namespace DiscordRPC {
class DiscordInterface;
}
namespace PlayTime {
class PlayTimeManager;
}
namespace FileSys {
class ContentProvider;
class ManualContentProvider;
class VfsFilesystem;
} // namespace FileSys
namespace InputCommon {
class InputSubsystem;
}
namespace Service::AM {
struct FrontendAppletParameters;
enum class AppletId : u32;
} // namespace Service::AM
namespace Service::AM::Frontend {
enum class SwkbdResult : u32;
enum class SwkbdTextCheckResult : u32;
enum class SwkbdReplyType : u32;
enum class WebExitReason : u32;
} // namespace Service::AM::Frontend
namespace Service::NFC {
class NfcDevice;
}
namespace Service::NFP {
enum class CabinetMode : u8;
}
namespace Ui {
class MainWindow;
}
enum class EmulatedDirectoryTarget { NAND, SDMC };
namespace VkDeviceInfo { class Record; }
namespace VkDeviceInfo {
class Record;
}
class VolumeButton : public QPushButton {
Q_OBJECT
@@ -87,10 +126,12 @@ public:
}
signals:
void VolumeChanged();
protected:
void wheelEvent(QWheelEvent* event) override;
private slots:
void ResetMultiplier();
private:
int scroll_multiplier;
QTimer scroll_timer;
@@ -102,24 +143,47 @@ class GMainWindow : public QMainWindow {
static const int max_recent_files_item = 10;
friend class PerformanceOverlay;
friend class VramOverlay;
enum { CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, CREATE_SHORTCUT_MSGBOX_SUCCESS, CREATE_SHORTCUT_MSGBOX_ERROR, CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING };
enum {
CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES,
CREATE_SHORTCUT_MSGBOX_SUCCESS,
CREATE_SHORTCUT_MSGBOX_ERROR,
CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING
};
public:
void filterBarSetChecked(bool state);
void UpdateUITheme();
bool IsConfiguring() const { return m_is_configuring; }
bool IsConfiguring() const {
return m_is_configuring;
}
explicit GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulkan);
~GMainWindow() override;
bool DropAction(QDropEvent* event);
void AcceptDropEvent(QDropEvent* event);
MultiplayerState* GetMultiplayerState() { return multiplayer_state; }
Core::System* GetSystem() { return system.get(); }
const std::shared_ptr<FileSys::VfsFilesystem>& GetVFS() const { return vfs; }
bool IsEmulationRunning() const { return emulation_running; }
MultiplayerState* GetMultiplayerState() {
return multiplayer_state;
}
Core::System* GetSystem() {
return system.get();
}
const std::shared_ptr<FileSys::VfsFilesystem>& GetVFS() const {
return vfs;
}
bool IsEmulationRunning() const {
return emulation_running;
}
void RefreshGameList();
GRenderWindow* GetRenderWindow() const { return render_window; }
bool ExtractZipToDirectoryPublic(const std::filesystem::path& zip_path, const std::filesystem::path& extract_path);
[[nodiscard]] bool HasPerformedInitialSync() const { return has_performed_initial_sync; }
void SetPerformedInitialSync(bool synced) { has_performed_initial_sync = synced; }
GRenderWindow* GetRenderWindow() const {
return render_window;
}
bool ExtractZipToDirectoryPublic(const std::filesystem::path& zip_path,
const std::filesystem::path& extract_path);
[[nodiscard]] bool HasPerformedInitialSync() const {
return has_performed_initial_sync;
}
void SetPerformedInitialSync(bool synced) {
has_performed_initial_sync = synced;
}
signals:
void EmulationStarting(EmuThread* emu_thread);
void EmulationStopping();
@@ -130,8 +194,10 @@ signals:
void ControllerSelectorReconfigureFinished(bool is_success);
void ErrorDisplayFinished();
void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
void SoftwareKeyboardSubmitNormalText(Service::AM::Frontend::SwkbdResult result, std::u16string submitted_text, bool confirmed);
void SoftwareKeyboardSubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type, std::u16string submitted_text, s32 cursor_position);
void SoftwareKeyboardSubmitNormalText(Service::AM::Frontend::SwkbdResult result,
std::u16string submitted_text, bool confirmed);
void SoftwareKeyboardSubmitInlineText(Service::AM::Frontend::SwkbdReplyType reply_type,
std::u16string submitted_text, s32 cursor_position);
void WebBrowserExtractOfflineRomFS();
void WebBrowserClosed(Service::AM::Frontend::WebExitReason exit_reason, std::string last_url);
void SigInterrupt();
@@ -141,13 +207,18 @@ public slots:
void OnExecuteProgram(std::size_t program_index);
void OnExit();
void OnSaveConfig();
void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, std::shared_ptr<Service::NFC::NfcDevice> nfp_device);
void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters,
std::shared_ptr<Service::NFC::NfcDevice> nfp_device);
void AmiiboSettingsRequestExit();
void ControllerSelectorReconfigureControllers(const Core::Frontend::ControllerParameters& parameters);
void ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters);
void ControllerSelectorRequestExit();
void SoftwareKeyboardInitialize(bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters);
void SoftwareKeyboardInitialize(
bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters);
void SoftwareKeyboardShowNormal();
void SoftwareKeyboardShowTextCheck(Service::AM::Frontend::SwkbdTextCheckResult text_check_result, std::u16string text_check_message);
void SoftwareKeyboardShowTextCheck(
Service::AM::Frontend::SwkbdTextCheckResult text_check_result,
std::u16string text_check_message);
void SoftwareKeyboardShowInline(Core::Frontend::InlineAppearParameters appear_parameters);
void SoftwareKeyboardHideInline();
void SoftwareKeyboardInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters);
@@ -156,13 +227,16 @@ public slots:
void ErrorDisplayRequestExit();
void ProfileSelectorSelectProfile(const Core::Frontend::ProfileSelectParameters& parameters);
void ProfileSelectorRequestExit();
void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args, bool is_local);
void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args,
bool is_local);
void WebBrowserRequestExit();
void OnAppFocusStateChanged(Qt::ApplicationState state);
void OnTasStateChanged();
void IncrementInstallProgress();
private:
void LinkActionShortcut(QAction* action, const QString& action_name, const bool tas_allowed = false);
void LinkActionShortcut(QAction* action, const QString& action_name,
const bool tas_allowed = false);
void RegisterMetaTypes();
void RegisterAutoloaderContents();
void InitializeWidgets();
@@ -177,7 +251,8 @@ private:
void PreventOSSleep();
void AllowOSSleep();
bool LoadROM(const QString& filename, Service::AM::FrontendAppletParameters params);
void BootGame(const QString& filename, Service::AM::FrontendAppletParameters params, StartGameType with_config = StartGameType::Normal);
void BootGame(const QString& filename, Service::AM::FrontendAppletParameters params,
StartGameType with_config = StartGameType::Normal);
void BootGameFromList(const QString& filename, StartGameType with_config);
void ShutdownGame();
void ShowTelemetryCallout();
@@ -192,17 +267,20 @@ private:
void RequestGameExit();
void changeEvent(QEvent* event) override;
void closeEvent(QCloseEvent* event) override;
std::string CreateTASFramesString(std::array<size_t, InputCommon::TasInput::PLAYER_NUMBER> frames) const;
#ifdef __unix__
std::string CreateTASFramesString(
std::array<size_t, InputCommon::TasInput::PLAYER_NUMBER> frames) const;
#ifdef __unix__
void SetupSigInterrupts();
static void HandleSigInterrupt(int);
void OnSigInterruptNotifierActivated();
void SetGamemodeEnabled(bool state);
#endif
#endif
Core::PerfStatsResults last_perf_stats{};
Service::AM::FrontendAppletParameters ApplicationAppletParameters();
Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, Service::AM::AppletId applet_id);
Service::AM::FrontendAppletParameters SystemAppletParameters(u64 program_id, Service::AM::AppletId applet_id);
Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id,
Service::AM::AppletId applet_id);
Service::AM::FrontendAppletParameters SystemAppletParameters(u64 program_id,
Service::AM::AppletId applet_id);
void SetupHomeMenuCallback();
std::unique_ptr<FileSys::ManualContentProvider> autoloader_provider;
u64 current_title_id{0};
@@ -216,16 +294,20 @@ private slots:
void OnMenuReportCompatibility();
void OnOpenSupport();
void OnGameListLoadFile(QString game_path, u64 program_id);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, const std::string& game_path);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target,
const std::string& game_path);
void OnTransferableShaderCacheOpenFile(u64 program_id);
void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, const std::string& game_path);
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
void OnGameListRemovePlayTimeData(u64 program_id);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void OnGameListVerifyIntegrity(const std::string& game_path);
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list);
void OnGameListCreateShortcut(u64 program_id, const std::string& game_path, GameListShortcutTarget target);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
void OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
GameListShortcutTarget target);
void OnGameListOpenDirectory(const QString& directory);
void OnGameListAddDirectory();
void OnGameListShowList(bool show);
@@ -252,10 +334,12 @@ private slots:
void OnConfigurePerGame();
void OnLoadAmiibo();
void OnOpenCitronFolder();
void OnOpenLogFolder();
void OnVerifyInstalledContents();
void OnInstallFirmware();
void OnInstallFirmwareFromZip();
bool ExtractZipToDirectory(const std::filesystem::path& zip_path, const std::filesystem::path& extract_path);
bool ExtractZipToDirectory(const std::filesystem::path& zip_path,
const std::filesystem::path& extract_path);
void OnInstallDecryptionKeys();
void OnAbout();
void OnCheckForUpdates();
@@ -300,6 +384,7 @@ private slots:
void OnShutdownBeginDialog();
void OnEmulationStopped();
void OnEmulationStopTimeExpired();
private:
QString GetGameListErrorRemoving(InstalledEntryType type) const;
void RemoveBaseContent(u64 program_id, InstalledEntryType type);
@@ -311,10 +396,12 @@ private:
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
void RemovePlayTimeData(u64 program_id);
void RemoveCacheStorage(u64 program_id);
bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, u64* selected_title_id, u8* selected_content_record_type);
bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
u64* selected_title_id, u8* selected_content_record_type);
ContentManager::InstallResult InstallNCA(const QString& filename);
void MigrateConfigFiles();
void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, std::string_view gpu_vendor = {});
void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
std::string_view gpu_vendor = {});
void UpdateDockedButton();
void UpdateAPIText();
void UpdateFilterText();
@@ -337,9 +424,17 @@ private:
bool ConfirmShutdownGame();
QString GetTasStateDescription() const;
bool CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title);
bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, std::filesystem::path& out_icon_path);
bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, const std::filesystem::path& icon_path, const std::filesystem::path& command, const std::string& arguments, const std::string& categories, const std::string& keywords, const std::string& name);
bool question(QWidget* parent, const QString& title, const QString& text, QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
std::filesystem::path& out_icon_path);
bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment,
const std::filesystem::path& icon_path,
const std::filesystem::path& command, const std::string& arguments,
const std::string& categories, const std::string& keywords,
const std::string& name);
bool question(QWidget* parent, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons =
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
std::unique_ptr<Core::System> system;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
std::unique_ptr<Ui::MainWindow> ui;
@@ -410,10 +505,10 @@ private:
bool m_is_updating_theme = false;
bool m_is_configuring = false;
bool has_performed_initial_sync = false;
#ifdef __unix__
#ifdef __unix__
QSocketNotifier* sig_interrupt_notifier;
static std::array<int, 3> sig_interrupt_fds;
#endif
#endif
protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;

View File

@@ -192,6 +192,8 @@
<addaction name="action_Report_Compatibility"/>
<addaction name="action_Open_Support"/>
<addaction name="separator"/>
<addaction name="action_Open_Log_Folder"/>
<addaction name="separator"/>
<addaction name="action_Check_For_Updates"/>
<addaction name="separator"/>
<addaction name="action_About"/>
@@ -450,6 +452,11 @@
<string>Open &amp;citron Folder</string>
</property>
</action>
<action name="action_Open_Log_Folder">
<property name="text">
<string>Open &amp;Log Folder</string>
</property>
</action>
<action name="action_Capture_Screenshot">
<property name="enabled">
<bool>false</bool>

View File

@@ -459,6 +459,8 @@ add_library(core STATIC
hle/service/am/process_holder.h
hle/service/am/service/all_system_applet_proxies_service.cpp
hle/service/am/service/all_system_applet_proxies_service.h
hle/service/am/service/applet_alternative_functions.cpp
hle/service/am/service/applet_alternative_functions.h
hle/service/am/service/applet_common_functions.cpp
hle/service/am/service/applet_common_functions.h
hle/service/am/service/application_accessor.cpp
@@ -499,6 +501,8 @@ add_library(core STATIC
hle/service/am/service/lock_accessor.h
hle/service/am/service/overlay_applet_proxy.cpp
hle/service/am/service/overlay_applet_proxy.h
hle/service/am/service/overlay_functions.cpp
hle/service/am/service/overlay_functions.h
hle/service/am/service/process_winding_controller.cpp
hle/service/am/service/process_winding_controller.h
hle/service/am/service/self_controller.cpp
@@ -1257,7 +1261,8 @@ target_link_libraries(core
nlohmann_json::nlohmann_json
mbedtls
RenderDoc::API
)
stb::headers
)
# Conditionally link against Boost::process ONLY if it was found by the main CMakeLists.txt.
if(Boost_PROCESS_FOUND)

View File

@@ -373,6 +373,12 @@ std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
const auto res1 = CheckMapForContentRecord(citron_meta, title_id, type);
if (res1)
return res1;
const auto res2 = CheckMapForContentRecord(legacy_meta, title_id, type);
if (res2) {
LOG_INFO(Loader, "Found content {:016X} type {:02X} in legacy_meta", title_id,
static_cast<u8>(type));
return res2;
}
return CheckMapForContentRecord(meta, title_id, type);
}
@@ -461,6 +467,7 @@ void RegisteredCache::Refresh() {
const auto ids = AccumulateFiles();
ProcessFiles(ids);
AccumulateCitronMeta();
AccumulateLegacyMeta();
}
RegisteredCache::RegisteredCache(VirtualDir dir_, ContentProviderParsingFunction parsing_function)
@@ -490,6 +497,11 @@ std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
return citron_meta_iter->second.GetTitleVersion();
}
const auto legacy_meta_iter = legacy_meta.find(title_id);
if (legacy_meta_iter != legacy_meta.cend()) {
return legacy_meta_iter->second.GetTitleVersion();
}
return std::nullopt;
}
@@ -527,6 +539,14 @@ void RegisteredCache::IterateAllMetadata(
}
}
}
for (const auto& kv : legacy_meta) {
const auto& cnmt = kv.second;
for (const auto& rec : cnmt.GetContentRecords()) {
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
out.push_back(proc(cnmt, rec));
}
}
}
}
std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter(
@@ -758,6 +778,15 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
}
}
// If patch entries for any program exist in yuzu meta, remove them
for (u8 i = 0; i < 0x10; i++) {
const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta");
const auto filename = GetCNMTName(TitleType::Update, title_id + i);
if (meta_dir->GetFile(filename)) {
removed_data |= meta_dir->DeleteFile(filename);
}
}
return removed_data;
}
@@ -790,7 +819,9 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti
if (GetFileAtID(id) != nullptr) {
LOG_WARNING(Loader, "Overwriting existing NCA...");
VirtualDir c_dir;
{ c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
{
c_dir = dir->GetFileRelative(path)->GetContainingDirectory();
}
c_dir->DeleteFile(Common::FS::GetFilename(path));
}
@@ -1033,4 +1064,36 @@ std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
void RegisteredCache::AccumulateLegacyMeta() {
LOG_INFO(Loader, "AccumulateLegacyMeta: Scanning directory '{}' for yuzu_meta",
dir->GetFullPath());
const auto meta_dir = dir->GetSubdirectory("yuzu_meta");
if (meta_dir == nullptr) {
LOG_INFO(Loader, "AccumulateLegacyMeta: yuzu_meta directory not found in '{}'",
dir->GetFullPath());
return;
}
LOG_INFO(Loader, "Accumulating legacy meta from yuzu_meta at '{}'", meta_dir->GetFullPath());
const auto files = meta_dir->GetFiles();
LOG_INFO(Loader, "yuzu_meta contains {} files", files.size());
for (const auto& file : files) {
LOG_INFO(Loader, "Scanning file: name={} extension={}", file->GetName(),
file->GetExtension());
if (file->GetExtension() != "cnmt") {
continue;
}
CNMT cnmt(file);
LOG_INFO(Loader, "Loaded legacy CNMT: {:016X}", cnmt.GetTitleID());
legacy_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
}
const auto subdirs = meta_dir->GetSubdirectories();
LOG_INFO(Loader, "yuzu_meta contains {} subdirectories", subdirs.size());
}
} // namespace FileSys

View File

@@ -165,8 +165,8 @@ public:
const VfsCopyFunction& copy = &VfsRawCopy);
// Due to the fact that we must use Meta-type NCAs to determine the existence of files, this
// poses quite a challenge. Instead of creating a new meta NCA for this file, citron will create a
// dir inside the NAND called 'citron_meta' and store the raw CNMT there.
// poses quite a challenge. Instead of creating a new meta NCA for this file, citron will create
// a dir inside the NAND called 'citron_meta' and store the raw CNMT there.
// TODO(DarkLordZach): Author real meta-type NCAs and install those.
InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
@@ -186,6 +186,7 @@ private:
std::vector<NcaID> AccumulateFiles() const;
void ProcessFiles(const std::vector<NcaID>& ids);
void AccumulateCitronMeta();
void AccumulateLegacyMeta();
std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
VirtualFile GetFileAtID(NcaID id) const;
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& open_dir, std::string_view path) const;
@@ -202,6 +203,8 @@ private:
std::map<u64, CNMT> meta;
// maps tid -> meta for CNMT in citron_meta
std::map<u64, CNMT> citron_meta;
// maps tid -> meta for CNMT in yuzu_meta (legacy)
std::map<u64, CNMT> legacy_meta;
};
enum class ContentProviderUnionSlot {
@@ -209,7 +212,8 @@ enum class ContentProviderUnionSlot {
UserNAND, ///< User NAND
SDMC, ///< SD Card
FrontendManual, ///< Frontend-defined game list or similar
Autoloader, ///< Separate functionality for multiple Updates/DLCs without being overwritten by NAND.
Autoloader, ///< Separate functionality for multiple Updates/DLCs without being overwritten by
///< NAND.
};
// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.

View File

@@ -60,13 +60,13 @@ Result ResetSignal(Core::System& system, Handle handle) {
}
}
// Handle not found - log once and return success to prevent infinite loops
// Handle not found
if (should_log) {
LOG_WARNING(Kernel_SVC, "ResetSignal called with invalid handle 0x{:08X}, returning success to prevent hang", handle);
LOG_WARNING(Kernel_SVC, "ResetSignal called with invalid handle 0x{:08X}", handle);
logged_handles.insert(handle);
}
R_SUCCEED(); // Return success instead of throwing to prevent infinite loops
R_RETURN(ResultInvalidHandle);
}
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds

View File

@@ -32,6 +32,7 @@
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/glue/glue_manager.h"
#include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/server_manager.h"
#include "core/loader/loader.h"
@@ -108,6 +109,7 @@ public:
{150, nullptr, "CreateAuthorizationRequest"},
{160, nullptr, "RequiresUpdateNetworkServiceAccountIdTokenCache"},
{161, nullptr, "RequireReauthenticationOfNetworkServiceAccount"},
{143, D<&IManagerForSystemService::GetNetworkServiceLicenseCacheEx>, "GetNetworkServiceLicenseCacheEx"}, // 15.0.0+
};
// clang-format on
@@ -136,6 +138,15 @@ private:
R_SUCCEED();
}
Result GetNetworkServiceLicenseCacheEx(Out<u32> out_license, Out<s64> out_expiration) {
LOG_INFO(Service_ACC, "called");
*out_license = 0;
*out_expiration = 0;
R_SUCCEED();
}
Common::UUID account_id;
};
@@ -333,6 +344,9 @@ public:
{1, &IProfileCommon::GetBase, "GetBase"},
{10, &IProfileCommon::GetImageSize, "GetImageSize"},
{11, &IProfileCommon::LoadImage, "LoadImage"},
{20, &IProfileCommon::Unknown20, "Unknown20"},
{21, &IProfileCommon::Unknown21, "Unknown21"},
{30, &IProfileCommon::Unknown30, "Unknown30"},
};
RegisterHandlers(functions);
@@ -341,6 +355,7 @@ public:
static const FunctionInfo editor_functions[] = {
{100, &IProfileCommon::Store, "Store"},
{101, &IProfileCommon::StoreWithImage, "StoreWithImage"},
{110, &IProfileCommon::Unknown110, "Unknown110"},
};
RegisterHandlers(editor_functions);
@@ -432,6 +447,34 @@ protected:
rb.Push(static_cast<u32>(buffer.size()));
}
void Unknown20(HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "(STUBBED) called.");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Unknown21(HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "(STUBBED) called.");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Unknown30(HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "(STUBBED) called.");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Unknown110(HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "(STUBBED) called.");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Store(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto base = rp.PopRaw<ProfileBase>();
@@ -602,7 +645,8 @@ protected:
class CheckNetworkServiceAvailabilityAsyncInterface final : public IAsyncContext {
public:
explicit CheckNetworkServiceAvailabilityAsyncInterface(Core::System& system_) : IAsyncContext{system_} {
explicit CheckNetworkServiceAvailabilityAsyncInterface(Core::System& system_)
: IAsyncContext{system_} {
MarkComplete();
}
~CheckNetworkServiceAvailabilityAsyncInterface() = default;
@@ -621,7 +665,8 @@ protected:
class EnsureSignedDeviceIdentifierCacheAsyncInterface final : public IAsyncContext {
public:
explicit EnsureSignedDeviceIdentifierCacheAsyncInterface(Core::System& system_) : IAsyncContext{system_} {
explicit EnsureSignedDeviceIdentifierCacheAsyncInterface(Core::System& system_)
: IAsyncContext{system_} {
MarkComplete();
}
~EnsureSignedDeviceIdentifierCacheAsyncInterface() = default;
@@ -659,7 +704,8 @@ protected:
class SynchronizeNetworkServiceAccountsSnapshotAsyncInterface final : public IAsyncContext {
public:
explicit SynchronizeNetworkServiceAccountsSnapshotAsyncInterface(Core::System& system_) : IAsyncContext{system_} {
explicit SynchronizeNetworkServiceAccountsSnapshotAsyncInterface(Core::System& system_)
: IAsyncContext{system_} {
MarkComplete();
}
~SynchronizeNetworkServiceAccountsSnapshotAsyncInterface() = default;
@@ -1302,7 +1348,8 @@ void Module::Interface::ActivateOpenContextRetention(HLERequestContext& ctx) {
rb.PushIpcInterface<ISessionObject>(system, dummy_uuid);
}
void Module::Interface::EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync(HLERequestContext& ctx) {
void Module::Interface::EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync(
HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -1366,7 +1413,8 @@ void Module::Interface::SetUserPosition(HLERequestContext& ctx) {
const auto position = rp.Pop<u32>();
const auto uuid = rp.PopRaw<Common::UUID>();
LOG_WARNING(Service_ACC, "(STUBBED) called, position={}, uuid=0x{}", position, uuid.RawString());
LOG_WARNING(Service_ACC, "(STUBBED) called, position={}, uuid=0x{}", position,
uuid.RawString());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -1429,7 +1477,8 @@ void Module::Interface::ResumeProcedureToCreateUserWithNintendoAccount(HLEReques
rb.PushIpcInterface<IOAuthProcedureForUserRegistration>(system, dummy_uuid);
}
void Module::Interface::ResumeProcedureToCreateUserWithNintendoAccountAfterApplyResponse(HLERequestContext& ctx) {
void Module::Interface::ResumeProcedureToCreateUserWithNintendoAccountAfterApplyResponse(
HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
const Common::UUID dummy_uuid{};
@@ -1474,7 +1523,8 @@ void Module::Interface::ProxyProcedureForGuestLoginWithNintendoAccount(HLEReques
rb.PushIpcInterface<IOAuthProcedureForExternalNsa>(system, dummy_uuid);
}
void Module::Interface::ProxyProcedureForFloatingRegistrationWithNintendoAccount(HLERequestContext& ctx) {
void Module::Interface::ProxyProcedureForFloatingRegistrationWithNintendoAccount(
HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
const Common::UUID dummy_uuid{};
@@ -1483,7 +1533,8 @@ void Module::Interface::ProxyProcedureForFloatingRegistrationWithNintendoAccount
rb.PushIpcInterface<IOAuthProcedureForExternalNsa>(system, dummy_uuid);
}
void Module::Interface::ProxyProcedureForDeviceMigrationAuthenticatingOperatingUser(HLERequestContext& ctx) {
void Module::Interface::ProxyProcedureForDeviceMigrationAuthenticatingOperatingUser(
HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
const Common::UUID dummy_uuid{};
@@ -1591,8 +1642,8 @@ void Module::Interface::DebugSetUserStateOpen(HLERequestContext& ctx) {
Module::Interface::Interface(std::shared_ptr<Module> module_,
std::shared_ptr<ProfileManager> profile_manager_,
Core::System& system_, const char* name)
: ServiceFramework{system_, name}, module{std::move(module_)}, profile_manager{std::move(
profile_manager_)} {}
: ServiceFramework{system_, name}, module{std::move(module_)},
profile_manager{std::move(profile_manager_)} {}
Module::Interface::~Interface() = default;
@@ -1605,18 +1656,17 @@ void LoopProcess(Core::System& system) {
std::make_shared<ACC_AA>(module, profile_manager, system));
server_manager->RegisterNamedService("acc:e",
std::make_shared<ACC_E>(module, profile_manager, system));
server_manager->RegisterNamedService("acc:e:u1",
std::make_shared<ACC_E_U1>(module, profile_manager, system));
server_manager->RegisterNamedService("acc:e:u2",
std::make_shared<ACC_E_U2>(module, profile_manager, system));
server_manager->RegisterNamedService(
"acc:e:u1", std::make_shared<ACC_E_U1>(module, profile_manager, system));
server_manager->RegisterNamedService(
"acc:e:u2", std::make_shared<ACC_E_U2>(module, profile_manager, system));
server_manager->RegisterNamedService("acc:su",
std::make_shared<ACC_SU>(module, profile_manager, system));
server_manager->RegisterNamedService("acc:u0",
std::make_shared<ACC_U0>(module, profile_manager, system));
server_manager->RegisterNamedService("acc:u1",
std::make_shared<ACC_U1>(module, profile_manager, system));
server_manager->RegisterNamedService("dauth:0",
std::make_shared<DAUTH_0>(system));
server_manager->RegisterNamedService("dauth:0", std::make_shared<DAUTH_0>(system));
ServerManager::RunServer(std::move(server_manager));
}

View File

@@ -15,6 +15,7 @@ class FrontendApplet;
enum class AppletType {
Application,
LibraryApplet,
OverlayApplet,
SystemApplet,
};
@@ -212,7 +213,7 @@ struct AppletIdentityInfo {
};
static_assert(sizeof(AppletIdentityInfo) == 0x10, "AppletIdentityInfo has incorrect size.");
struct AppletAttribute {
struct alignas(8) AppletAttribute {
u8 flag;
INSERT_PADDING_BYTES_NOINIT(0x7F);
};

View File

@@ -113,6 +113,7 @@ struct Applet {
bool is_activity_runnable{};
bool is_interactible{true};
bool window_visible{true};
bool overlay_in_foreground{};
// Events
Event gpu_error_detected_event;

View File

@@ -6,6 +6,7 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/process_creation.h"
#include "core/hle/service/am/applet_data_broker.h"
#include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/frontend/applet_cabinet.h"
@@ -262,6 +263,22 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) {
m_cv.wait(lk, [&] { return m_pending_process != nullptr; });
if (true && m_window_system->GetOverlayDisplayApplet() == nullptr) {
if (auto overlay_process = CreateProcess(m_system, static_cast<u64>(AppletProgramId::OverlayDisplay), 0, 0)) {
auto overlay_applet = std::make_shared<Applet>(m_system, std::move(overlay_process), false);
overlay_applet->program_id = static_cast<u64>(AppletProgramId::OverlayDisplay);
overlay_applet->applet_id = AppletId::OverlayDisplay;
overlay_applet->type = AppletType::OverlayApplet;
overlay_applet->library_applet_mode = LibraryAppletMode::PartialForeground;
overlay_applet->window_visible = true;
overlay_applet->home_button_short_pressed_blocked = false;
overlay_applet->home_button_long_pressed_blocked = false;
m_window_system->TrackApplet(overlay_applet, false);
overlay_applet->process->Run();
LOG_INFO(Service_AM, "called, Overlay applet launched before application (initially hidden, watching home button)");
}
}
const auto& params = m_pending_parameters;
auto applet = std::make_shared<Applet>(m_system, std::move(m_pending_process),
params.applet_id == AppletId::Application);

View File

@@ -46,6 +46,10 @@ public:
void OperationModeChanged();
public:
WindowSystem* GetWindowSystem() const {
return m_window_system;
}
void SetWindowSystem(WindowSystem* window_system);
void SetHomeMenuRequestCallback(std::function<void()> callback);

View File

@@ -69,6 +69,17 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
if (m_applet_id != AppletId::Application) {
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
if (m_applet_id == AppletId::OverlayDisplay) {
(void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
} else {
(void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
}
}
m_managed_display_layers.emplace(*out_layer_id);
R_SUCCEED();
@@ -105,7 +116,16 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
// We succeeded, so set up remaining state.
m_buffer_sharing_enabled = true;
// Ensure the overlay layer is visible
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
s32 initial_z = 1;
if (m_applet_id == AppletId::OverlayDisplay) {
initial_z = -1;
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
}
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
R_SUCCEED();
}
@@ -128,10 +148,14 @@ void DisplayLayerManager::SetWindowVisibility(bool visible) {
if (m_manager_display_service) {
if (m_system_shared_layer_id) {
LOG_INFO(Service_VI, "shared_layer={} visible={} applet_id={}",
m_system_shared_layer_id, m_visible, static_cast<u32>(m_applet_id));
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
}
for (const auto layer_id : m_managed_display_layers) {
LOG_INFO(Service_VI, "managed_layer={} visible={} applet_id={}", layer_id, m_visible,
static_cast<u32>(m_applet_id));
m_manager_display_service->SetLayerVisibility(m_visible, layer_id);
}
}
@@ -141,6 +165,22 @@ bool DisplayLayerManager::GetWindowVisibility() const {
return m_visible;
}
void DisplayLayerManager::SetOverlayZIndex(s32 z_index) {
if (!m_manager_display_service) {
return;
}
if (m_system_shared_layer_id) {
m_manager_display_service->SetLayerZIndex(z_index, m_system_shared_layer_id);
LOG_INFO(Service_VI, "called, shared_layer={} z={}", m_system_shared_layer_id, z_index);
}
for (const auto layer_id : m_managed_display_layers) {
m_manager_display_service->SetLayerZIndex(z_index, layer_id);
LOG_INFO(Service_VI, "called, managed_layer={} z={}", layer_id, z_index);
}
}
Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written,
s32* out_fbshare_layer_index) {
R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);

View File

@@ -43,6 +43,8 @@ public:
void SetWindowVisibility(bool visible);
bool GetWindowVisibility() const;
void SetOverlayZIndex(s32 z_index);
Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index);
private:

View File

@@ -190,6 +190,10 @@ void PhotoViewer::Execute() {
case PhotoViewerAppletMode::AllApps:
frontend.ShowAllPhotos(callback);
break;
case PhotoViewerAppletMode::ShowAllFiles:
// For now, treat ShowAllFiles the same as AllApps in the HLE stub
frontend.ShowAllPhotos(callback);
break;
default:
UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode);
break;

View File

@@ -46,6 +46,7 @@ private:
enum class PhotoViewerAppletMode : u8 {
CurrentApp = 0,
AllApps = 1,
ShowAllFiles = 2,
};
class PhotoViewer final : public FrontendApplet {

View File

@@ -5,6 +5,7 @@
#include "core/core.h"
#include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/service/all_system_applet_proxies_service.h"
#include "core/hle/service/am/service/applet_alternative_functions.h"
#include "core/hle/service/am/service/debug_functions.h"
#include "core/hle/service/am/service/library_applet_creator.h"
#include "core/hle/service/am/service/library_applet_proxy.h"
@@ -31,7 +32,7 @@ IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& sys
{400, D<&IAllSystemAppletProxiesService::CreateSelfLibraryAppletCreatorForDevelop>, "CreateSelfLibraryAppletCreatorForDevelop"},
{410, D<&IAllSystemAppletProxiesService::GetSystemAppletControllerForDebug>, "GetSystemAppletControllerForDebug"},
{450, D<&IAllSystemAppletProxiesService::GetSystemProcessCommonFunctions>, "GetSystemProcessCommonFunctions"},
{460, D<&IAllSystemAppletProxiesService::Cmd460>, "Cmd460"},
{460, D<&IAllSystemAppletProxiesService::GetAppletAlternativeFunctions>, "GetAppletAlternativeFunctions"},
{1000, D<&IAllSystemAppletProxiesService::GetDebugFunctions>, "GetDebugFunctions"},
};
// clang-format on
@@ -73,9 +74,8 @@ Result IAllSystemAppletProxiesService::OpenHomeMenuProxy(
Result IAllSystemAppletProxiesService::OpenLibraryAppletProxy(
Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy, ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle,
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute) {
LOG_DEBUG(Service_AM, "called");
InCopyHandle<Kernel::KProcess> process_handle, AppletAttribute attribute) {
LOG_WARNING(Service_AM, "called");
if (const auto applet = this->GetAppletFromProcessId(pid); applet) {
*out_library_applet_proxy = std::make_shared<ILibraryAppletProxy>(
@@ -104,8 +104,7 @@ std::shared_ptr<Applet> IAllSystemAppletProxiesService::GetAppletFromProcessId(
Result IAllSystemAppletProxiesService::OpenOverlayAppletProxy(
Out<SharedPointer<IOverlayAppletProxy>> out_overlay_applet_proxy, ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle,
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute) {
InCopyHandle<Kernel::KProcess> process_handle, AppletAttribute attribute) {
LOG_DEBUG(Service_AM, "called");
if (const auto applet = GetAppletFromProcessId(pid)) {
*out_overlay_applet_proxy = std::make_shared<IOverlayAppletProxy>(
@@ -137,7 +136,8 @@ Result IAllSystemAppletProxiesService::CreateSelfLibraryAppletCreatorForDevelop(
LOG_WARNING(Service_AM, "(STUBBED) called");
if (const auto applet = this->GetAppletFromProcessId(pid); applet) {
*out_library_applet_creator = std::make_shared<ILibraryAppletCreator>(system, applet, m_window_system);
*out_library_applet_creator =
std::make_shared<ILibraryAppletCreator>(system, applet, m_window_system);
R_SUCCEED();
} else {
R_THROW(ResultUnknown);
@@ -156,8 +156,11 @@ Result IAllSystemAppletProxiesService::GetSystemProcessCommonFunctions(
R_SUCCEED();
}
Result IAllSystemAppletProxiesService::Cmd460() {
LOG_WARNING(Service_AM, "(STUBBED) called");
Result IAllSystemAppletProxiesService::GetAppletAlternativeFunctions(
Out<SharedPointer<IAppletAlternativeFunctions>> out_applet_alternative_functions) {
LOG_WARNING(Service_AM, "called");
*out_applet_alternative_functions =
std::make_shared<IAppletAlternativeFunctions>(system, m_window_system);
R_SUCCEED();
}

View File

@@ -4,6 +4,7 @@
#pragma once
#include "core/hle/service/am/am_types.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
@@ -19,7 +20,9 @@ class ILibraryAppletProxy;
class IOverlayAppletProxy;
class ISystemAppletProxy;
class ISystemApplicationProxy;
class ISystemApplicationProxy;
class ISystemProcessCommonFunctions;
class IAppletAlternativeFunctions;
class WindowSystem;
class IAllSystemAppletProxiesService final
@@ -33,29 +36,29 @@ private:
ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle);
Result OpenHomeMenuProxy(Out<SharedPointer<ISystemAppletProxy>> out_system_applet_proxy,
ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle);
ClientProcessId pid, InCopyHandle<Kernel::KProcess> process_handle);
Result OpenLibraryAppletProxy(Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy,
ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle,
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute);
AppletAttribute attribute);
Result OpenLibraryAppletProxyOld(
Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy, ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle);
Result OpenOverlayAppletProxy(Out<SharedPointer<IOverlayAppletProxy>> out_overlay_applet_proxy,
ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle,
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute);
Result OpenSystemApplicationProxy(Out<SharedPointer<ISystemApplicationProxy>> out_system_application_proxy,
ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle);
Result CreateSelfLibraryAppletCreatorForDevelop(Out<SharedPointer<ILibraryAppletCreator>> out_library_applet_creator,
ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle);
AppletAttribute attribute);
Result OpenSystemApplicationProxy(
Out<SharedPointer<ISystemApplicationProxy>> out_system_application_proxy,
ClientProcessId pid, InCopyHandle<Kernel::KProcess> process_handle);
Result CreateSelfLibraryAppletCreatorForDevelop(
Out<SharedPointer<ILibraryAppletCreator>> out_library_applet_creator, ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle);
Result GetSystemAppletControllerForDebug();
Result GetSystemProcessCommonFunctions(
Out<SharedPointer<ISystemProcessCommonFunctions>> out_system_process_common_functions);
Result Cmd460();
Result GetAppletAlternativeFunctions(
Out<SharedPointer<IAppletAlternativeFunctions>> out_applet_alternative_functions);
Result GetDebugFunctions(Out<SharedPointer<IDebugFunctions>> out_debug_functions);
private:

View File

@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/am/service/applet_alternative_functions.h"
#include "core/hle/service/am/window_system.h"
#include "core/hle/service/cmif_serialization.h"
namespace Service::AM {
IAppletAlternativeFunctions::IAppletAlternativeFunctions(Core::System& system_,
WindowSystem& window_system)
: ServiceFramework{system_, "IAppletAlternativeFunctions"}, m_window_system{window_system} {
// clang-format off
static const FunctionInfo functions[] = {
{10, D<&IAppletAlternativeFunctions::GetAlternativeTarget>, "GetAlternativeTarget"},
};
// clang-format on
RegisterHandlers(functions);
}
IAppletAlternativeFunctions::~IAppletAlternativeFunctions() = default;
Result IAppletAlternativeFunctions::GetAlternativeTarget(Out<u64> out_target) {
LOG_WARNING(Service_AM, "(STUBBED) called");
*out_target = 0; // Return 0? Or some applet ID?
R_SUCCEED();
}
} // namespace Service::AM

View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
namespace Service::AM {
class WindowSystem;
class IAppletAlternativeFunctions final : public ServiceFramework<IAppletAlternativeFunctions> {
public:
explicit IAppletAlternativeFunctions(Core::System& system_, WindowSystem& window_system);
~IAppletAlternativeFunctions() override;
private:
Result GetAlternativeTarget(Out<u64> out_target);
WindowSystem& m_window_system;
};
} // namespace Service::AM

View File

@@ -20,7 +20,7 @@ IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_,
{20, nullptr, "PushToAppletBoundChannel"},
{21, nullptr, "TryPopFromAppletBoundChannel"},
{40, nullptr, "GetDisplayLogicalResolution"},
{42, nullptr, "SetDisplayMagnification"},
{42, D<&IAppletCommonFunctions::SetDisplayMagnification>, "SetDisplayMagnification"},
{50, D<&IAppletCommonFunctions::SetHomeButtonDoubleClickEnabled>, "SetHomeButtonDoubleClickEnabled"},
{51, D<&IAppletCommonFunctions::GetHomeButtonDoubleClickEnabled>, "GetHomeButtonDoubleClickEnabled"},
{52, nullptr, "IsHomeButtonShortPressedBlocked"},
@@ -70,6 +70,13 @@ Result IAppletCommonFunctions::GetHomeButtonDoubleClickEnabled(
R_SUCCEED();
}
Result IAppletCommonFunctions::SetDisplayMagnification(f32 x, f32 y, f32 width, f32 height) {
LOG_DEBUG(Service_AM, "(STUBBED) called, x={}, y={}, width={}, height={}", x, y, width, height);
std::scoped_lock lk{applet->lock};
applet->display_magnification = Common::Rectangle<f32>{x, y, x + width, y + height};
R_SUCCEED();
}
Result IAppletCommonFunctions::SetCpuBoostRequestPriority(s32 priority) {
LOG_WARNING(Service_AM, "(STUBBED) called");
std::scoped_lock lk{applet->lock};

View File

@@ -18,6 +18,7 @@ public:
private:
Result SetHomeButtonDoubleClickEnabled(bool home_button_double_click_enabled);
Result GetHomeButtonDoubleClickEnabled(Out<bool> out_home_button_double_click_enabled);
Result SetDisplayMagnification(f32 x, f32 y, f32 width, f32 height);
Result SetCpuBoostRequestPriority(s32 priority);
Result GetCurrentApplicationId(Out<u64> out_application_id);
Result IsSystemAppletHomeMenu(Out<bool> out_is_home_menu);

View File

@@ -39,7 +39,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<Ap
{30, nullptr, "GetHomeButtonReaderLockAccessor"},
{31, D<&ICommonStateGetter::GetReaderLockAccessorEx>, "GetReaderLockAccessorEx"},
{32, D<&ICommonStateGetter::GetWriterLockAccessorEx>, "GetWriterLockAccessorEx"},
{40, nullptr, "GetCradleFwVersion"},
{40, D<&ICommonStateGetter::GetCradleFwVersion>, "GetCradleFwVersion"},
{50, D<&ICommonStateGetter::IsVrModeEnabled>, "IsVrModeEnabled"},
{51, D<&ICommonStateGetter::SetVrModeEnabled>, "SetVrModeEnabled"},
{52, D<&ICommonStateGetter::SetLcdBacklighOffEnabled>, "SetLcdBacklighOffEnabled"},
@@ -265,6 +265,17 @@ Result ICommonStateGetter::GetOperationModeSystemInfo(Out<u32> out_operation_mod
R_SUCCEED();
}
Result ICommonStateGetter::GetCradleFwVersion(Out<u32> out_major, Out<u32> out_minor,
Out<u32> out_micro) {
LOG_WARNING(Service_AM, "(STUBBED) called");
*out_major = 0;
*out_minor = 0;
*out_micro = 0;
R_SUCCEED();
}
Result ICommonStateGetter::GetAppletLaunchedHistory(
Out<s32> out_count, OutArray<AppletId, BufferAttr_HipcMapAlias> out_applet_ids) {
LOG_INFO(Service_AM, "called");

View File

@@ -37,6 +37,7 @@ private:
Result GetDefaultDisplayResolutionChangeEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetOperationMode(Out<OperationMode> out_operation_mode);
Result GetPerformanceMode(Out<APM::PerformanceMode> out_performance_mode);
Result GetCradleFwVersion(Out<u32> out_major, Out<u32> out_minor, Out<u32> out_micro);
Result GetBootMode(Out<PM::SystemBootMode> out_boot_mode);
Result IsVrModeEnabled(Out<bool> out_is_vr_mode_enabled);
Result SetVrModeEnabled(bool is_vr_mode_enabled);

View File

@@ -106,15 +106,11 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
return {};
}
// TODO: enable other versions of applets
enum : u8 {
Firmware1400 = 14,
Firmware1500 = 15,
Firmware1600 = 16,
Firmware1700 = 17,
};
auto process = CreateProcess(system, program_id, Firmware1400, Firmware1700);
// We allow any firmware generation to be loaded as a guest process if possible.
// If the emulator lacks the necessary keys, it will fail later and fallback to a stub.
const u8 min_gen = 1;
const u8 max_gen = 255;
auto process = CreateProcess(system, program_id, min_gen, max_gen);
if (!process) {
// Couldn't initialize the guest process
return {};
@@ -166,8 +162,8 @@ std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(Core::System& syste
ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_, std::shared_ptr<Applet> applet,
WindowSystem& window_system)
: ServiceFramework{system_, "ILibraryAppletCreator"},
m_window_system{window_system}, m_applet{std::move(applet)} {
: ServiceFramework{system_, "ILibraryAppletCreator"}, m_window_system{window_system},
m_applet{std::move(applet)} {
static const FunctionInfo functions[] = {
{0, D<&ILibraryAppletCreator::CreateLibraryApplet>, "CreateLibraryApplet"},
{1, nullptr, "TerminateAllLibraryApplets"},

View File

@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/am/service/applet_common_functions.h"
#include "core/hle/service/am/service/audio_controller.h"
#include "core/hle/service/am/service/common_state_getter.h"
#include "core/hle/service/am/service/debug_functions.h"
@@ -9,55 +10,33 @@
#include "core/hle/service/am/service/home_menu_functions.h"
#include "core/hle/service/am/service/library_applet_creator.h"
#include "core/hle/service/am/service/overlay_applet_proxy.h"
#include "core/hle/service/am/service/overlay_functions.h"
#include "core/hle/service/am/service/process_winding_controller.h"
#include "core/hle/service/am/service/self_controller.h"
#include "core/hle/service/am/service/window_controller.h"
#include "core/hle/service/cmif_serialization.h"
namespace Service::AM {
// Forward declaration for IOverlayAppletFunctions
class IOverlayAppletFunctions final : public ServiceFramework<IOverlayAppletFunctions> {
public:
explicit IOverlayAppletFunctions(Core::System& system_) : ServiceFramework{system_, "IOverlayAppletFunctions"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "BeginToWatchShortHomeButtonMessage"},
{1, nullptr, "EndToWatchShortHomeButtonMessage"},
{2, nullptr, "GetApplicationIdForLogo"},
{3, nullptr, "SetGpuTimeSliceBoost"},
{4, nullptr, "SetAutoSleepTimeAndDimmingTimeEnabled"},
{5, nullptr, "SetHandlingHomeButtonShortPressedEnabled"},
{6, nullptr, "SetHandlingCaptureButtonShortPressedEnabledForOverlayApplet"},
{7, nullptr, "SetHandlingCaptureButtonLongPressedEnabledForOverlayApplet"},
{8, nullptr, "GetShortHomeButtonMessage"},
{9, nullptr, "IsHomeButtonShortPressedBlocked"},
{10, nullptr, "IsVrModeCurtainRequired"},
{11, nullptr, "SetInputDetectionPolicy"},
{20, nullptr, "SetCpuBoostRequestPriority"},
};
// clang-format on
RegisterHandlers(functions);
}
};
namespace Service::AM {
IOverlayAppletProxy::IOverlayAppletProxy(Core::System& system_, std::shared_ptr<Applet> applet,
Kernel::KProcess* process, WindowSystem& window_system)
: ServiceFramework{system_, "IOverlayAppletProxy"},
m_window_system{window_system}, m_process{process}, m_applet{std::move(applet)} {
: ServiceFramework{system_, "IOverlayAppletProxy"}, m_window_system{window_system},
m_process{process}, m_applet{std::move(applet)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&IOverlayAppletProxy::GetCommonStateGetter>, "GetCommonStateGetter"},
{1, D<&IOverlayAppletProxy::GetSelfController>, "GetSelfController"},
{2, D<&IOverlayAppletProxy::GetWindowController>, "GetWindowController"},
{3, D<&IOverlayAppletProxy::GetAudioController>, "GetAudioController"},
{4, D<&IOverlayAppletProxy::GetDisplayController>, "GetDisplayController"},
{11, D<&IOverlayAppletProxy::GetLibraryAppletCreator>, "GetLibraryAppletCreator"},
{20, D<&IOverlayAppletProxy::GetOverlayAppletFunctions>, "GetOverlayAppletFunctions"},
{21, D<&IOverlayAppletProxy::GetHomeMenuFunctions>, "GetHomeMenuFunctions"},
{22, D<&IOverlayAppletProxy::GetGlobalStateController>, "GetGlobalStateController"},
{1000, D<&IOverlayAppletProxy::GetDebugFunctions>, "GetDebugFunctions"},
};
{0, D<&IOverlayAppletProxy::GetCommonStateGetter>, "GetCommonStateGetter"},
{1, D<&IOverlayAppletProxy::GetSelfController>, "GetSelfController"},
{2, D<&IOverlayAppletProxy::GetWindowController>, "GetWindowController"},
{3, D<&IOverlayAppletProxy::GetAudioController>, "GetAudioController"},
{4, D<&IOverlayAppletProxy::GetDisplayController>, "GetDisplayController"},
{10, D<&IOverlayAppletProxy::GetProcessWindingController>, "GetProcessWindingController"},
{11, D<&IOverlayAppletProxy::GetLibraryAppletCreator>, "GetLibraryAppletCreator"},
{20, D<&IOverlayAppletProxy::GetOverlayFunctions>, "GetOverlayFunctions"},
{21, D<&IOverlayAppletProxy::GetAppletCommonFunctions>, "GetAppletCommonFunctions"},
{23, D<&IOverlayAppletProxy::GetGlobalStateController>, "GetGlobalStateController"},
{1000, D<&IOverlayAppletProxy::GetDebugFunctions>, "GetDebugFunctions"},
};
// clang-format on
RegisterHandlers(functions);
@@ -100,13 +79,6 @@ Result IOverlayAppletProxy::GetDisplayController(
R_SUCCEED();
}
Result IOverlayAppletProxy::GetProcessWindingController(
Out<SharedPointer<IProcessWindingController>> out_process_winding_controller) {
LOG_DEBUG(Service_AM, "called");
*out_process_winding_controller = std::make_shared<IProcessWindingController>(system, m_applet);
R_SUCCEED();
}
Result IOverlayAppletProxy::GetLibraryAppletCreator(
Out<SharedPointer<ILibraryAppletCreator>> out_library_applet_creator) {
LOG_DEBUG(Service_AM, "called");
@@ -115,21 +87,6 @@ Result IOverlayAppletProxy::GetLibraryAppletCreator(
R_SUCCEED();
}
Result IOverlayAppletProxy::GetOverlayAppletFunctions(
Out<SharedPointer<IOverlayAppletFunctions>> out_overlay_applet_functions) {
LOG_DEBUG(Service_AM, "called");
*out_overlay_applet_functions = std::make_shared<IOverlayAppletFunctions>(system);
R_SUCCEED();
}
Result IOverlayAppletProxy::GetHomeMenuFunctions(
Out<SharedPointer<IHomeMenuFunctions>> out_home_menu_functions) {
LOG_DEBUG(Service_AM, "called");
*out_home_menu_functions =
std::make_shared<IHomeMenuFunctions>(system, m_applet, m_window_system);
R_SUCCEED();
}
Result IOverlayAppletProxy::GetGlobalStateController(
Out<SharedPointer<IGlobalStateController>> out_global_state_controller) {
LOG_DEBUG(Service_AM, "called");
@@ -144,4 +101,25 @@ Result IOverlayAppletProxy::GetDebugFunctions(
R_SUCCEED();
}
} // namespace Service::AM
Result IOverlayAppletProxy::GetProcessWindingController(
Out<SharedPointer<IProcessWindingController>> out_process_winding_controller) {
LOG_DEBUG(Service_AM, "called");
*out_process_winding_controller = std::make_shared<IProcessWindingController>(system, m_applet);
R_SUCCEED();
}
Result IOverlayAppletProxy::GetOverlayFunctions(
Out<SharedPointer<IOverlayFunctions>> out_overlay_functions) {
LOG_DEBUG(Service_AM, "called");
*out_overlay_functions = std::make_shared<IOverlayFunctions>(system, m_applet);
R_SUCCEED();
}
Result IOverlayAppletProxy::GetAppletCommonFunctions(
Out<SharedPointer<IAppletCommonFunctions>> out_applet_common_functions) {
LOG_DEBUG(Service_AM, "called");
*out_applet_common_functions = std::make_shared<IAppletCommonFunctions>(system, m_applet);
R_SUCCEED();
}
} // namespace Service::AM

View File

@@ -5,6 +5,8 @@
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
#include "core/hle/service/am/service/applet_common_functions.h"
#include "core/hle/service/am/service/overlay_functions.h"
namespace Kernel {
class KProcess;
@@ -25,6 +27,8 @@ class IProcessWindingController;
class ISelfController;
class IWindowController;
class WindowSystem;
class IOverlayFunctions;
class IAppletCommonFunctions;
class IOverlayAppletProxy final : public ServiceFramework<IOverlayAppletProxy> {
public:
@@ -38,11 +42,15 @@ private:
Result GetWindowController(Out<SharedPointer<IWindowController>> out_window_controller);
Result GetAudioController(Out<SharedPointer<IAudioController>> out_audio_controller);
Result GetDisplayController(Out<SharedPointer<IDisplayController>> out_display_controller);
Result GetProcessWindingController(Out<SharedPointer<IProcessWindingController>> out_process_winding_controller);
Result GetLibraryAppletCreator(Out<SharedPointer<ILibraryAppletCreator>> out_library_applet_creator);
Result GetOverlayAppletFunctions(Out<SharedPointer<IOverlayAppletFunctions>> out_overlay_applet_functions);
Result GetHomeMenuFunctions(Out<SharedPointer<IHomeMenuFunctions>> out_home_menu_functions);
Result GetGlobalStateController(Out<SharedPointer<IGlobalStateController>> out_global_state_controller);
Result GetProcessWindingController(
Out<SharedPointer<IProcessWindingController>> out_process_winding_controller);
Result GetLibraryAppletCreator(
Out<SharedPointer<ILibraryAppletCreator>> out_library_applet_creator);
Result GetOverlayFunctions(Out<SharedPointer<IOverlayFunctions>> out_overlay_functions);
Result GetAppletCommonFunctions(
Out<SharedPointer<IAppletCommonFunctions>> out_applet_common_functions);
Result GetGlobalStateController(
Out<SharedPointer<IGlobalStateController>> out_global_state_controller);
Result GetDebugFunctions(Out<SharedPointer<IDebugFunctions>> out_debug_functions);
WindowSystem& m_window_system;
@@ -50,4 +58,4 @@ private:
const std::shared_ptr<Applet> m_applet;
};
} // namespace Service::AM
} // namespace Service::AM

View File

@@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: Copyright 2026 Citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/am/applet.h"
#include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/service/overlay_functions.h"
#include "core/hle/service/am/window_system.h"
#include "core/hle/service/cmif_serialization.h"
namespace Service::AM {
IOverlayFunctions::IOverlayFunctions(Core::System& system_, std::shared_ptr<Applet> applet)
: ServiceFramework{system_, "IOverlayFunctions"}, m_applet{std::move(applet)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&IOverlayFunctions::BeginToWatchShortHomeButtonMessage>, "BeginToWatchShortHomeButtonMessage"},
{1, D<&IOverlayFunctions::EndToWatchShortHomeButtonMessage>, "EndToWatchShortHomeButtonMessage"},
{2, D<&IOverlayFunctions::GetApplicationIdForLogo>, "GetApplicationIdForLogo"},
{3, nullptr, "SetGpuTimeSliceBoost"},
{4, D<&IOverlayFunctions::SetAutoSleepTimeAndDimmingTimeEnabled>, "SetAutoSleepTimeAndDimmingTimeEnabled"},
{5, nullptr, "TerminateApplicationAndSetReason"},
{6, nullptr, "SetScreenShotPermissionGlobally"},
{10, nullptr, "StartShutdownSequenceForOverlay"},
{11, nullptr, "StartRebootSequenceForOverlay"},
{20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"},
{21, nullptr, "SetHandlingTouchScreenInputEnabled"},
{30, nullptr, "SetHealthWarningShowingState"},
{31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"},
{40, nullptr, "GetApplicationNintendoLogo"},
{41, nullptr, "GetApplicationStartupMovie"},
{50, nullptr, "SetGpuTimeSliceBoostForApplication"},
{60, nullptr, "Unknown60"},
{70, D<&IOverlayFunctions::Unknown70>, "Unknown70"},
{90, nullptr, "SetRequiresGpuResourceUse"},
{101, nullptr, "BeginToObserveHidInputForDevelop"},
};
// clang-format on
RegisterHandlers(functions);
}
IOverlayFunctions::~IOverlayFunctions() = default;
Result IOverlayFunctions::BeginToWatchShortHomeButtonMessage() {
LOG_DEBUG(Service_AM, "called");
m_applet->overlay_in_foreground = true;
m_applet->home_button_short_pressed_blocked = false;
if (auto* window_system = system.GetAppletManager().GetWindowSystem()) {
window_system->RequestUpdate();
}
R_SUCCEED();
}
Result IOverlayFunctions::EndToWatchShortHomeButtonMessage() {
LOG_DEBUG(Service_AM, "called");
m_applet->overlay_in_foreground = false;
m_applet->home_button_short_pressed_blocked = false;
if (auto* window_system = system.GetAppletManager().GetWindowSystem()) {
window_system->RequestUpdate();
}
R_SUCCEED();
}
Result IOverlayFunctions::GetApplicationIdForLogo(Out<u64> out_application_id) {
LOG_DEBUG(Service_AM, "called");
std::shared_ptr<Applet> target_applet;
auto* window_system = system.GetAppletManager().GetWindowSystem();
if (window_system) {
target_applet = window_system->GetMainApplet();
if (target_applet) {
std::scoped_lock lk{target_applet->lock};
LOG_DEBUG(Service_AM, "applet_id={}, program_id={:016X}, type={}",
static_cast<u32>(target_applet->applet_id), target_applet->program_id,
static_cast<u32>(target_applet->type));
u64 id = target_applet->screen_shot_identity.application_id;
if (id == 0) {
id = target_applet->program_id;
}
LOG_DEBUG(Service_AM, "application_id={:016X}", id);
*out_application_id = id;
R_SUCCEED();
}
}
std::scoped_lock lk{m_applet->lock};
u64 id = m_applet->screen_shot_identity.application_id;
if (id == 0) {
id = m_applet->program_id;
}
LOG_DEBUG(Service_AM, "application_id={:016X} (fallback)", id);
*out_application_id = id;
R_SUCCEED();
}
Result IOverlayFunctions::SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled) {
LOG_WARNING(Service_AM, "(STUBBED) called, enabled={}", enabled);
std::scoped_lock lk{m_applet->lock};
m_applet->auto_sleep_disabled = !enabled;
R_SUCCEED();
}
Result IOverlayFunctions::IsHealthWarningRequired(Out<bool> is_required) {
LOG_DEBUG(Service_AM, "called");
std::scoped_lock lk{m_applet->lock};
*is_required = false;
R_SUCCEED();
}
Result IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled(bool enabled) {
LOG_DEBUG(Service_AM, "called, enabled={}", enabled);
std::scoped_lock lk{m_applet->lock};
m_applet->home_button_short_pressed_blocked = !enabled;
R_SUCCEED();
}
Result IOverlayFunctions::Unknown70() {
LOG_DEBUG(Service_AM, "called");
R_SUCCEED();
}
} // namespace Service::AM

View File

@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2026 Citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/service.h"
namespace Service::AM {
struct Applet;
class IOverlayFunctions final : public ServiceFramework<IOverlayFunctions> {
public:
explicit IOverlayFunctions(Core::System& system_, std::shared_ptr<Applet> applet_);
~IOverlayFunctions() override;
private:
Result BeginToWatchShortHomeButtonMessage();
Result EndToWatchShortHomeButtonMessage();
Result GetApplicationIdForLogo(Out<u64> out_application_id);
Result SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled);
Result IsHealthWarningRequired(Out<bool> is_required);
Result SetHandlingHomeButtonShortPressedEnabled(bool enabled);
Result Unknown70();
private:
const std::shared_ptr<Applet> m_applet;
};
} // namespace Service::AM

View File

@@ -21,6 +21,12 @@ void WindowSystem::SetEventObserver(EventObserver* observer) {
m_system.GetAppletManager().SetWindowSystem(this);
}
void WindowSystem::RequestUpdate() {
if (m_event_observer) {
m_event_observer->RequestUpdate();
}
}
void WindowSystem::Update() {
std::scoped_lock lk{m_lock};
@@ -40,7 +46,10 @@ void WindowSystem::Update() {
void WindowSystem::TrackApplet(std::shared_ptr<Applet> applet, bool is_application) {
std::scoped_lock lk{m_lock};
if (applet->applet_id == AppletId::QLaunch) {
if (applet->type == AppletType::OverlayApplet) {
ASSERT(overlay_display_applet == nullptr);
overlay_display_applet = applet;
} else if (applet->applet_id == AppletId::QLaunch) {
ASSERT(m_home_menu == nullptr);
m_home_menu = applet.get();
} else if (is_application) {
@@ -320,4 +329,8 @@ void WindowSystem::SetHomeMenuRequestCallback(HomeMenuRequestCallback callback)
m_home_menu_request_callback = std::move(callback);
}
std::shared_ptr<Applet> WindowSystem::GetOverlayDisplayApplet() {
return overlay_display_applet;
}
} // namespace Service::AM

View File

@@ -35,11 +35,13 @@ public:
public:
void SetEventObserver(EventObserver* event_observer);
void Update();
void RequestUpdate();
public:
void TrackApplet(std::shared_ptr<Applet> applet, bool is_application);
std::shared_ptr<Applet> GetByAppletResourceUserId(u64 aruid);
std::shared_ptr<Applet> GetMainApplet();
std::shared_ptr<Applet> GetOverlayDisplayApplet();
public:
void RequestHomeMenuToGetForeground();
@@ -74,6 +76,9 @@ private:
// Lock.
std::mutex m_lock{};
// Overlay Display Applet.
std::shared_ptr<Applet> overlay_display_applet;
// Home menu state.
bool m_home_menu_foreground_locked{};
Applet* m_foreground_requested_applet{};

View File

@@ -45,6 +45,11 @@ static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
Loader::ResultStatus::Success;
}),
add_on_content.end());
LOG_WARNING(Service_AOC, "Accumulated {} AOC title IDs", add_on_content.size());
for (const auto& tid : add_on_content) {
LOG_WARNING(Service_AOC, "Found AOC: {:016X}", tid);
}
return add_on_content;
}
@@ -53,8 +58,8 @@ IAddOnContentManager::IAddOnContentManager(Core::System& system_)
service_context{system_, "aoc:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CountAddOnContentByApplicationId"},
{1, nullptr, "ListAddOnContentByApplicationId"},
{0, D<&IAddOnContentManager::CountAddOnContentByApplicationId>, "CountAddOnContentByApplicationId"},
{1, D<&IAddOnContentManager::ListAddOnContentByApplicationId>, "ListAddOnContentByApplicationId"},
{2, D<&IAddOnContentManager::CountAddOnContent>, "CountAddOnContent"},
{3, D<&IAddOnContentManager::ListAddOnContent>, "ListAddOnContent"},
{4, nullptr, "GetAddOnContentBaseIdByApplicationId"},
@@ -108,21 +113,85 @@ Result IAddOnContentManager::CountAddOnContent(Out<u32> out_count, ClientProcess
Result IAddOnContentManager::ListAddOnContent(Out<u32> out_count,
OutBuffer<BufferAttr_HipcMapAlias> out_addons,
u32 offset, u32 count, ClientProcessId process_id) {
LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count,
process_id.pid);
LOG_WARNING(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count,
process_id.pid);
const auto current = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID());
std::vector<u32> out;
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
LOG_WARNING(Service_AOC, "Filtering AOCs for base title ID: {:016X}", current);
for (u64 content_id : add_on_content) {
if (FileSys::GetBaseTitleID(content_id) != current) {
const auto aoc_base = FileSys::GetBaseTitleID(content_id);
if (aoc_base != current) {
// LOG_WARNING(Service_AOC, "Skipping AOC {:016X} (Base: {:016X})", content_id,
// aoc_base);
continue;
}
LOG_WARNING(Service_AOC, "Match! AOC {:016X} belongs to current app. Adding to list.",
content_id);
out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id)));
}
} else {
LOG_WARNING(Service_AOC, "DLCs are disabled for this title {:016X}", current);
}
// TODO(DarkLordZach): Find the correct error code.
R_UNLESS(out.size() >= offset, ResultUnknown);
*out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
std::rotate(out.begin(), out.begin() + offset, out.end());
std::memcpy(out_addons.data(), out.data(), *out_count * sizeof(u32));
R_SUCCEED();
}
Result IAddOnContentManager::CountAddOnContentByApplicationId(Out<u32> out_count,
u64 application_id) {
LOG_WARNING(Service_AOC, "called. application_id={:016X}", application_id);
const auto current = application_id;
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
*out_count = 0;
R_SUCCEED();
}
*out_count = static_cast<u32>(
std::count_if(add_on_content.begin(), add_on_content.end(),
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); }));
R_SUCCEED();
}
Result IAddOnContentManager::ListAddOnContentByApplicationId(
Out<u32> out_count, OutBuffer<BufferAttr_HipcMapAlias> out_addons, u32 offset, u32 count,
u64 application_id) {
LOG_WARNING(Service_AOC, "called with offset={}, count={}, application_id={:016X}", offset,
count, application_id);
const auto current = FileSys::GetBaseTitleID(application_id);
std::vector<u32> out;
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
LOG_WARNING(Service_AOC, "Filtering AOCs for base title ID: {:016X}", current);
for (u64 content_id : add_on_content) {
const auto aoc_base = FileSys::GetBaseTitleID(content_id);
if (aoc_base != current) {
continue;
}
LOG_WARNING(Service_AOC, "Match! AOC {:016X} belongs to current app. Adding to list.",
content_id);
out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id)));
}
} else {
LOG_WARNING(Service_AOC, "DLCs are disabled for this title {:016X}", current);
}
// TODO(DarkLordZach): Find the correct error code.

View File

@@ -29,6 +29,10 @@ public:
Result CountAddOnContent(Out<u32> out_count, ClientProcessId process_id);
Result ListAddOnContent(Out<u32> out_count, OutBuffer<BufferAttr_HipcMapAlias> out_addons,
u32 offset, u32 count, ClientProcessId process_id);
Result CountAddOnContentByApplicationId(Out<u32> out_count, u64 application_id);
Result ListAddOnContentByApplicationId(Out<u32> out_count,
OutBuffer<BufferAttr_HipcMapAlias> out_addons,
u32 offset, u32 count, u64 application_id);
Result GetAddOnContentBaseId(Out<u64> out_title_id, ClientProcessId process_id);
Result PrepareAddOnContent(s32 addon_index, ClientProcessId process_id);
Result GetAddOnContentListChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);

View File

@@ -53,6 +53,7 @@ IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
{8021, nullptr, "GetAlbumEntryFromApplicationAlbumEntryAruid"},
{10011, nullptr, "SetInternalErrorConversionEnabled"},
{50000, nullptr, "LoadMakerNoteInfoForDebug"},
{50011, C<&IAlbumAccessorService::SetShimLibraryVersion>, "SetShimLibraryVersion"},
{60002, nullptr, "OpenAccessorSession"},
};
// clang-format on
@@ -137,6 +138,14 @@ Result IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(
R_RETURN(TranslateResult(result));
}
Result IAlbumAccessorService::SetShimLibraryVersion(u64 shim_library_version,
u64 applet_resource_user_id) {
LOG_WARNING(Service_Capture,
"(STUBBED) called, shim_library_version={}, applet_resource_user_id={}",
shim_library_version, applet_resource_user_id);
R_SUCCEED();
}
Result IAlbumAccessorService::TranslateResult(Result in_result) {
if (in_result.IsSuccess()) {
return in_result;

View File

@@ -50,6 +50,8 @@ private:
OutArray<u8, BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> out_image,
OutArray<u8, BufferAttr_HipcMapAlias> out_buffer);
Result SetShimLibraryVersion(u64 shim_library_version, u64 applet_resource_user_id);
Result TranslateResult(Result in_result);
std::shared_ptr<AlbumManager> manager = nullptr;

View File

@@ -72,7 +72,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
{25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
{26, nullptr, "FormatSdCardDryRun"},
{27, nullptr, "IsExFatSupported"},
{27, D<&FSP_SRV::IsExFatSupported>, "IsExFatSupported"},
{28, nullptr, "DeleteSaveDataFileSystemBySaveDataAttribute"},
{30, D<&FSP_SRV::OpenGameCardStorage>, "OpenGameCardStorage"},
{31, D<&FSP_SRV::OpenGameCardFileSystem>, "OpenGameCardFileSystem"},
@@ -81,6 +81,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{34, D<&FSP_SRV::GetCacheStorageSize>, "GetCacheStorageSize"},
{35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
{36, nullptr, "OpenHostFileSystemWithOption"},
{37, D<&FSP_SRV::OpenSaveDataTransferManager>, "OpenSaveDataTransferManager"},
{51, D<&FSP_SRV::OpenSaveDataFileSystem>, "OpenSaveDataFileSystem"},
{52, D<&FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId>, "OpenSaveDataFileSystemBySystemSaveDataId"},
{53, D<&FSP_SRV::OpenReadOnlySaveDataFileSystem>, "OpenReadOnlySaveDataFileSystem"},
@@ -252,8 +253,8 @@ Result FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(
Result FSP_SRV::OpenSaveDataFileSystem(OutInterface<IFileSystem> out_interface,
FileSys::SaveDataSpaceId space_id,
FileSys::SaveDataAttribute attribute) {
LOG_INFO(Service_FS, "called, space_id={:02X}, program_id={:016X}",
static_cast<u8>(space_id), attribute.program_id);
LOG_INFO(Service_FS, "called, space_id={:02X}, program_id={:016X}", static_cast<u8>(space_id),
attribute.program_id);
FileSys::VirtualDir dir{};
// This triggers the 'Smart Pull' (Ryujinx -> Citron) in savedata_factory.cpp
@@ -278,9 +279,9 @@ Result FSP_SRV::OpenSaveDataFileSystem(OutInterface<IFileSystem> out_interface,
// Wrap the directory in the IFileSystem interface.
// We pass 'save_data_controller->GetFactory()' so the Commit function can find the Mirror.
*out_interface = std::make_shared<IFileSystem>(
system, std::move(dir), SizeGetter::FromStorageId(fsc, id),
save_data_controller->GetFactory(), space_id, attribute);
*out_interface =
std::make_shared<IFileSystem>(system, std::move(dir), SizeGetter::FromStorageId(fsc, id),
save_data_controller->GetFactory(), space_id, attribute);
R_SUCCEED();
}
@@ -338,8 +339,7 @@ Result FSP_SRV::WriteSaveDataFileSystemExtraData(InBuffer<BufferAttr_HipcMapAlia
FileSys::SaveDataExtraData extra_data{};
std::memcpy(&extra_data, buffer.data(), sizeof(FileSys::SaveDataExtraData));
R_RETURN(save_data_controller->WriteSaveDataExtraData(extra_data, space_id,
extra_data.attr));
R_RETURN(save_data_controller->WriteSaveDataExtraData(extra_data, space_id, extra_data.attr));
}
Result FSP_SRV::WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(
@@ -362,8 +362,8 @@ Result FSP_SRV::WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(
std::memcpy(&extra_data, buffer.data(), sizeof(FileSys::SaveDataExtraData));
std::memcpy(&mask, mask_buffer.data(), sizeof(FileSys::SaveDataExtraData));
R_RETURN(
save_data_controller->WriteSaveDataExtraDataWithMask(extra_data, mask, space_id, attribute));
R_RETURN(save_data_controller->WriteSaveDataExtraDataWithMask(extra_data, mask, space_id,
attribute));
}
Result FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(
@@ -415,7 +415,7 @@ Result FSP_SRV::ReadSaveDataFileSystemExtraData(OutBuffer<BufferAttr_HipcMapAlia
FileSys::SaveDataExtraData extra_data{};
R_TRY(save_data_controller->ReadSaveDataExtraData(&extra_data, FileSys::SaveDataSpaceId::User,
attribute));
attribute));
std::memcpy(out_buffer.data(), &extra_data, sizeof(FileSys::SaveDataExtraData));
R_SUCCEED();
@@ -660,7 +660,8 @@ Result FSP_SRV::OpenSaveDataTransferManager(OutInterface<ISaveDataTransferManage
R_SUCCEED();
}
Result FSP_SRV::OpenSaveDataTransferManagerVersion2(OutInterface<ISaveDataTransferManager> out_interface) {
Result FSP_SRV::OpenSaveDataTransferManagerVersion2(
OutInterface<ISaveDataTransferManager> out_interface) {
LOG_DEBUG(Service_FS, "called");
*out_interface = std::make_shared<ISaveDataTransferManager>(system);
@@ -690,18 +691,28 @@ Result FSP_SRV::DeleteSaveDataFileSystem(u64 save_data_id) {
R_SUCCEED();
}
Result FSP_SRV::OpenGameCardStorage(OutInterface<IStorage> out_interface, u32 handle, u32 partition_id) {
Result FSP_SRV::OpenGameCardStorage(OutInterface<IStorage> out_interface, u32 handle,
u32 partition_id) {
LOG_WARNING(Service_FS, "(STUBBED) called, handle={}, partition_id={}", handle, partition_id);
// Would need to open game card storage for the specified handle and partition
R_THROW(FileSys::ResultTargetNotFound);
}
Result FSP_SRV::OpenGameCardFileSystem(OutInterface<IFileSystem> out_interface, u32 handle, u32 partition_id) {
Result FSP_SRV::OpenGameCardFileSystem(OutInterface<IFileSystem> out_interface, u32 handle,
u32 partition_id) {
LOG_WARNING(Service_FS, "(STUBBED) called, handle={}, partition_id={}", handle, partition_id);
// Would need to open game card filesystem for the specified handle and partition
R_THROW(FileSys::ResultTargetNotFound);
}
Result FSP_SRV::IsExFatSupported(Out<bool> out_is_supported) {
LOG_WARNING(Service_FS, "(STUBBED) called");
*out_is_supported = true;
R_SUCCEED();
}
} // namespace Service::FileSystem

View File

@@ -116,12 +116,15 @@ private:
Result OpenSdCardDetectionEventNotifier(OutInterface<IEventNotifier> out_interface);
Result OpenGameCardDetectionEventNotifier(OutInterface<IEventNotifier> out_interface);
Result OpenSaveDataTransferManager(OutInterface<ISaveDataTransferManager> out_interface);
Result OpenSaveDataTransferManagerVersion2(OutInterface<ISaveDataTransferManager> out_interface);
Result OpenSaveDataTransferManagerVersion2(
OutInterface<ISaveDataTransferManager> out_interface);
Result OpenBisWiper(OutInterface<IWiper> out_interface);
Result OpenBisStorage(OutInterface<IStorage> out_interface, u32 partition_id);
Result DeleteSaveDataFileSystem(u64 save_data_id);
Result OpenGameCardStorage(OutInterface<IStorage> out_interface, u32 handle, u32 partition_id);
Result OpenGameCardFileSystem(OutInterface<IFileSystem> out_interface, u32 handle, u32 partition_id);
Result OpenGameCardFileSystem(OutInterface<IFileSystem> out_interface, u32 handle,
u32 partition_id);
Result IsExFatSupported(Out<bool> out_is_supported);
FileSystemController& fsc;
const FileSys::ContentProvider& content_provider;

View File

@@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
@@ -172,8 +173,8 @@ constexpr Result ResultNetworkCommunicationDisabled{ErrorModule::NIFM, 1111};
class IScanRequest final : public ServiceFramework<IScanRequest> {
public:
explicit IScanRequest(Core::System& system_) : ServiceFramework{system_, "IScanRequest"},
service_context{system_, "IScanRequest"} {
explicit IScanRequest(Core::System& system_)
: ServiceFramework{system_, "IScanRequest"}, service_context{system_, "IScanRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IScanRequest::Submit, "Submit"},
@@ -604,6 +605,41 @@ void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
void IGeneralService::EnumerateNetworkInterfaces(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
const auto adapters = Network::GetAvailableNetworkInterfaces();
constexpr size_t kEntrySize = 0x3F0; // From official
std::vector<u8> blob(adapters.size() * kEntrySize, 0);
for (size_t i = 0; i < adapters.size(); ++i) {
const auto& host = adapters[i];
u8* const base = blob.data() + i * kEntrySize;
// Match expected structure
// Citron NetworkInterface doesn't have type/kind, so we use 2u (Ethernet) as a safe default
*reinterpret_cast<u32*>(base + 0x0) = 2u;
*reinterpret_cast<u32*>(base + 0x4) = 1u; // Status?
std::memcpy(base + 0x18, &host.ip_address, sizeof(host.ip_address));
std::memcpy(base + 0x1C, &host.subnet_mask, sizeof(host.subnet_mask));
std::memcpy(base + 0x20, &host.gateway, sizeof(host.gateway));
std::string name_utf8 = host.name;
name_utf8.resize(0x110, '\0');
std::memcpy(base + 0x2E0, name_utf8.data(), 0x110);
}
if (ctx.GetWriteBufferSize() > 0 && !blob.empty()) {
ctx.WriteBuffer(blob.data(), std::min(ctx.GetWriteBufferSize(), blob.size()));
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(adapters.size()));
}
void IGeneralService::RemoveNetworkProfile(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
@@ -691,6 +727,31 @@ void IGeneralService::GetCurrentIpConfigInfo(HLERequestContext& ctx) {
rb.PushRaw<IpConfigInfo>(ip_config_info);
}
void IGeneralService::EnumerateNetworkProfiles(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
// Return 0 profiles for now (stub)
ctx.WriteBuffer(std::span<u8>{});
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u32>(0); // Number of profiles
}
void IGeneralService::ConfirmSystemAvailability(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IGeneralService::SetBackgroundRequestEnabled(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IGeneralService::IsWirelessCommunicationEnabled(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
@@ -750,6 +811,16 @@ void IGeneralService::IsAnyForegroundRequestAccepted(HLERequestContext& ctx) {
rb.Push<u8>(is_accepted);
}
void IGeneralService::GetSsidListVersion(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
constexpr u32 ssid_list_version = 0;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(ssid_list_version);
}
void IGeneralService::GetNetworkProfile(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
@@ -846,7 +917,8 @@ void IGeneralService::IsNetworkEmulationFeatureEnabled(HLERequestContext& ctx) {
}
void IGeneralService::SelectActiveNetworkEmulationProfileIdForDebug(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called SelectActiveNetworkEmulationProfileIdForDebug [18.0.0+]");
LOG_WARNING(Service_NIFM,
"(STUBBED) called SelectActiveNetworkEmulationProfileIdForDebug [18.0.0+]");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -898,7 +970,8 @@ void IGeneralService::DestroyRewriteRule(HLERequestContext& ctx) {
}
void IGeneralService::IsActiveNetworkEmulationProfileIdSelected(HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called IsActiveNetworkEmulationProfileIdSelected [20.0.0+]");
LOG_WARNING(Service_NIFM,
"(STUBBED) called IsActiveNetworkEmulationProfileIdSelected [20.0.0+]");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -935,8 +1008,8 @@ IGeneralService::IGeneralService(Core::System& system_)
{2, &IGeneralService::CreateScanRequest, "CreateScanRequest"},
{4, &IGeneralService::CreateRequest, "CreateRequest"},
{5, &IGeneralService::GetCurrentNetworkProfile, "GetCurrentNetworkProfile"},
{6, nullptr, "EnumerateNetworkInterfaces"},
{7, nullptr, "EnumerateNetworkProfiles"},
{6, &IGeneralService::EnumerateNetworkInterfaces, "EnumerateNetworkInterfaces"},
{7, &IGeneralService::EnumerateNetworkProfiles, "EnumerateNetworkProfiles"},
{8, &IGeneralService::GetNetworkProfile, "GetNetworkProfile"},
{9, &IGeneralService::SetNetworkProfile, "SetNetworkProfile"},
{10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
@@ -954,7 +1027,7 @@ IGeneralService::IGeneralService(Core::System& system_)
{22, &IGeneralService::IsAnyForegroundRequestAccepted, "IsAnyForegroundRequestAccepted"},
{23, nullptr, "PutToSleep"},
{24, nullptr, "WakeUp"},
{25, nullptr, "GetSsidListVersion"},
{25, &IGeneralService::GetSsidListVersion, "GetSsidListVersion"},
{26, nullptr, "SetExclusiveClient"},
{27, nullptr, "GetDefaultIpSetting"},
{28, nullptr, "SetDefaultIpSetting"},
@@ -962,8 +1035,8 @@ IGeneralService::IGeneralService(Core::System& system_)
{30, nullptr, "SetEthernetCommunicationEnabledForTest"},
{31, nullptr, "GetTelemetorySystemEventReadableHandle"},
{32, nullptr, "GetTelemetryInfo"},
{33, nullptr, "ConfirmSystemAvailability"},
{34, nullptr, "SetBackgroundRequestEnabled"},
{33, &IGeneralService::ConfirmSystemAvailability, "ConfirmSystemAvailability"},
{34, &IGeneralService::SetBackgroundRequestEnabled, "SetBackgroundRequestEnabled"},
{35, &IGeneralService::GetScanData, "GetScanData"},
{36, &IGeneralService::GetCurrentAccessPoint, "GetCurrentAccessPoint"},
{37, &IGeneralService::Shutdown, "Shutdown"},

View File

@@ -28,6 +28,7 @@ private:
void CreateScanRequest(HLERequestContext& ctx);
void CreateRequest(HLERequestContext& ctx);
void GetCurrentNetworkProfile(HLERequestContext& ctx);
void EnumerateNetworkInterfaces(HLERequestContext& ctx);
void RemoveNetworkProfile(HLERequestContext& ctx);
void GetCurrentIpAddress(HLERequestContext& ctx);
void CreateTemporaryNetworkProfile(HLERequestContext& ctx);
@@ -37,6 +38,7 @@ private:
void IsEthernetCommunicationEnabled(HLERequestContext& ctx);
void IsAnyInternetRequestAccepted(HLERequestContext& ctx);
void IsAnyForegroundRequestAccepted(HLERequestContext& ctx);
void GetSsidListVersion(HLERequestContext& ctx);
void SetWowlDelayedWakeTime(HLERequestContext& ctx);
void GetNetworkProfile(HLERequestContext& ctx);
void SetNetworkProfile(HLERequestContext& ctx);
@@ -47,6 +49,9 @@ private:
void SetAcceptableNetworkTypeFlag(HLERequestContext& ctx);
void GetAcceptableNetworkTypeFlag(HLERequestContext& ctx);
void NotifyConnectionStateChanged(HLERequestContext& ctx);
void EnumerateNetworkProfiles(HLERequestContext& ctx);
void ConfirmSystemAvailability(HLERequestContext& ctx);
void SetBackgroundRequestEnabled(HLERequestContext& ctx);
void SetWowlTcpKeepAliveTimeout(HLERequestContext& ctx);
void IsWiredConnectionAvailable(HLERequestContext& ctx);
void IsNetworkEmulationFeatureEnabled(HLERequestContext& ctx);

View File

@@ -26,7 +26,7 @@ public:
{5, C<&INpnsSystem::GetReceiveEvent>, "GetReceiveEvent"},
{6, nullptr, "ListenUndelivered"},
{7, nullptr, "GetStateChangeEvent"},
{8, nullptr, "ListenToByName"},
{8, C<&INpnsSystem::ListenToByName>, "ListenToByName"},
{11, nullptr, "SubscribeTopic"},
{12, nullptr, "UnsubscribeTopic"},
{13, nullptr, "QueryIsTopicExist"},
@@ -64,10 +64,10 @@ public:
{70, nullptr, "UnknownCmd70"},
{101, nullptr, "Suspend"},
{102, nullptr, "Resume"},
{103, nullptr, "GetState"},
{103, C<&INpnsSystem::GetState>, "GetState"},
{104, nullptr, "GetStatistics"},
{105, nullptr, "GetPlayReportRequestEvent"},
{106, nullptr, "GetLastNotifiedTime"},
{106, C<&INpnsSystem::GetLastNotifiedTime>, "GetLastNotifiedTime"},
{107, nullptr, "SetLastNotifiedTime"},
{111, nullptr, "GetJid"},
{112, nullptr, "CreateJid"},
@@ -88,7 +88,7 @@ public:
{154, nullptr, "CreateTokenAsync"},
{155, nullptr, "CreateTokenAsyncWithApplicationId"},
{156, nullptr, "CreateTokenWithNameAsync"},
{161, nullptr, "GetRequestChangeStateCancelEvent"},
{161, C<&INpnsSystem::GetRequestChangeStateCancelEvent>, "GetRequestChangeStateCancelEvent"},
{162, nullptr, "RequestChangeStateForceTimedWithCancelEvent"},
{201, nullptr, "RequestChangeStateForceTimed"},
{202, nullptr, "RequestChangeStateForceAsync"},
@@ -105,10 +105,13 @@ public:
RegisterHandlers(functions);
get_receive_event = service_context.CreateEvent("npns:s:GetReceiveEvent");
get_request_change_state_cancel_event =
service_context.CreateEvent("npns:s:GetRequestChangeStateCancelEvent");
}
~INpnsSystem() override {
service_context.CloseEvent(get_receive_event);
service_context.CloseEvent(get_request_change_state_cancel_event);
}
private:
@@ -124,29 +127,54 @@ private:
R_SUCCEED();
}
Result GetState(Out<u32> out_state) {
LOG_INFO(Service_NPNS, "called");
*out_state = 0;
R_SUCCEED();
}
Result GetLastNotifiedTime(Out<s64> out_last_notified_time) {
LOG_INFO(Service_NPNS, "called");
*out_last_notified_time = 0;
R_SUCCEED();
}
Result GetRequestChangeStateCancelEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_INFO(Service_NPNS, "called");
*out_event = &get_request_change_state_cancel_event->GetReadableEvent();
R_SUCCEED();
}
Result ListenToByName() {
LOG_DEBUG(Service_NPNS, "(STUBBED) called.");
R_SUCCEED();
}
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* get_receive_event;
Kernel::KEvent* get_request_change_state_cancel_event;
};
class INpnsUser final : public ServiceFramework<INpnsUser> {
public:
explicit INpnsUser(Core::System& system_) : ServiceFramework{system_, "npns:u"} {
explicit INpnsUser(Core::System& system_)
: ServiceFramework{system_, "npns:u"}, service_context{system, "npns:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "ListenAll"},
{2, nullptr, "ListenTo"},
{3, nullptr, "Receive"},
{4, nullptr, "ReceiveRaw"},
{5, nullptr, "GetReceiveEvent"},
{5, C<&INpnsUser::GetReceiveEvent>, "GetReceiveEvent"},
{7, nullptr, "GetStateChangeEvent"},
{8, nullptr, "ListenToByName"},
{8, C<&INpnsUser::ListenToByName>, "ListenToByName"},
{21, nullptr, "CreateToken"},
{23, nullptr, "DestroyToken"},
{25, nullptr, "QueryIsTokenValid"},
{26, nullptr, "ListenToMyApplicationId"},
{101, nullptr, "Suspend"},
{102, nullptr, "Resume"},
{103, nullptr, "GetState"},
{103, C<&INpnsUser::GetState>, "GetState"},
{104, nullptr, "GetStatistics"},
{111, nullptr, "GetJid"},
{120, nullptr, "CreateNotificationReceiver"},
@@ -158,7 +186,37 @@ public:
// clang-format on
RegisterHandlers(functions);
get_receive_event = service_context.CreateEvent("npns:u:GetReceiveEvent");
}
~INpnsUser() override {
service_context.CloseEvent(get_receive_event);
}
private:
Result ListenToByName(InBuffer<BufferAttr_HipcMapAlias> name_buffer) {
const std::string name(reinterpret_cast<const char*>(name_buffer.data()),
name_buffer.size());
LOG_DEBUG(Service_NPNS, "called, name={}", name);
R_SUCCEED();
}
Result GetReceiveEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_DEBUG(Service_NPNS, "called");
*out_event = &get_receive_event->GetReadableEvent();
R_SUCCEED();
}
Result GetState(Out<u32> out_state) {
LOG_INFO(Service_NPNS, "called");
*out_state = 0;
R_SUCCEED();
}
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* get_receive_event;
};
class INotificationReceiver : public ServiceFramework<INotificationReceiver> {
@@ -198,8 +256,7 @@ public:
class IFuture : public ServiceFramework<IFuture> {
public:
explicit IFuture(Core::System& system_, const char* name)
: ServiceFramework{system_, name} {
explicit IFuture(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// TODO: Implement functions based on documentation
// Cmd 1: (No name)
// Cmd 2: (No name)

View File

@@ -1,17 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/application_manager_interface.h"
#include "core/hle/service/ns/content_management_interface.h"
#include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/ns/read_only_application_control_data_interface.h"
namespace Service::NS {
IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_)
: ServiceFramework{system_, "IApplicationManagerInterface"},
service_context{system, "IApplicationManagerInterface"},
record_update_system_event{service_context}, sd_card_mount_status_event{service_context},
@@ -20,7 +22,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&IApplicationManagerInterface::ListApplicationRecord>, "ListApplicationRecord"},
{1, nullptr, "GenerateApplicationRecordCount"},
{1, D<&IApplicationManagerInterface::GenerateApplicationRecordCount>, "GenerateApplicationRecordCount"},
{2, D<&IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent>, "GetApplicationRecordUpdateSystemEvent"},
{3, nullptr, "GetApplicationViewDeprecated"},
{4, nullptr, "DeleteApplicationEntity"},
@@ -123,8 +125,9 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{404, nullptr, "InvalidateApplicationControlCache"},
{405, nullptr, "ListApplicationControlCacheEntryInfo"},
{406, nullptr, "GetApplicationControlProperty"},
{407, nullptr, "ListApplicationTitle"},
{407, &IApplicationManagerInterface::ListApplicationTitle, "ListApplicationTitle"},
{408, nullptr, "ListApplicationIcon"},
{419, D<&IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground>, "RequestDownloadApplicationControlDataInBackground"},
{502, nullptr, "RequestCheckGameCardRegistration"},
{503, nullptr, "RequestGameCardRegistrationGoldPoint"},
{504, nullptr, "RequestRegisterGameCard"},
@@ -134,15 +137,17 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{508, nullptr, "GetLastGameCardMountFailureResult"},
{509, nullptr, "ListApplicationIdOnGameCard"},
{510, nullptr, "GetGameCardPlatformRegion"},
{600, nullptr, "CountApplicationContentMeta"},
{600, D<&IApplicationManagerInterface::CountApplicationContentMeta>, "CountApplicationContentMeta"},
{601, nullptr, "ListApplicationContentMetaStatus"},
{602, nullptr, "ListAvailableAddOnContent"},
{602, D<&IApplicationManagerInterface::ListAvailableAddOnContent>, "ListAvailableAddOnContent"},
{603, nullptr, "GetOwnedApplicationContentMetaStatus"},
{604, nullptr, "RegisterContentsExternalKey"},
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
{606, nullptr, "GetContentMetaStorage"},
{607, nullptr, "ListAvailableAddOnContent"},
{607, D<&IApplicationManagerInterface::ListAvailableAddOnContent>, "ListAvailableAddOnContent"},
{609, nullptr, "ListAvailabilityAssuredAddOnContent"},
{610, nullptr, "GetInstalledContentMetaStorage"},
{611, nullptr, "PrepareAddOnContent"},
{700, nullptr, "PushDownloadTaskList"},
@@ -207,6 +212,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{1703, nullptr, "GetApplicationViewDownloadErrorContext"},
{1704, D<&IApplicationManagerInterface::GetApplicationViewWithPromotionInfo>, "GetApplicationViewWithPromotionInfo"},
{1705, nullptr, "IsPatchAutoDeletableApplication"},
{1706, D<&IApplicationManagerInterface::GetApplicationViewDeprecated>, "GetApplicationView"},
{1800, nullptr, "IsNotificationSetupCompleted"},
{1801, nullptr, "GetLastNotificationInfoCount"},
{1802, nullptr, "ListLastNotificationInfo"},
@@ -338,7 +344,9 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{4039, nullptr, "Cmd4039"},
{4040, nullptr, "Cmd4040"},
{4041, nullptr, "Cmd4041"},
{4042, nullptr, "Cmd4042"},
{4041, nullptr, "Cmd4041"},
{4042, D<&IApplicationManagerInterface::Cmd4042>, "Cmd4042"},
{4043, nullptr, "Cmd4043"},
{4043, nullptr, "Cmd4043"},
{4044, nullptr, "Cmd4044"},
{4045, nullptr, "Cmd4045"},
@@ -347,7 +355,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{4050, nullptr, "Cmd4050"},
{4051, nullptr, "Cmd4051"},
{4052, nullptr, "Cmd4052"},
{4053, nullptr, "Cmd4053"},
{4053, D<&IApplicationManagerInterface::Cmd4053>, "Cmd4053"},
{4054, nullptr, "Cmd4054"},
{4055, nullptr, "Cmd4055"},
{4056, nullptr, "Cmd4056"},
@@ -403,58 +411,89 @@ IApplicationManagerInterface::~IApplicationManagerInterface() = default;
Result IApplicationManagerInterface::GetApplicationControlData(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
ApplicationControlSource application_control_source, u64 application_id) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
R_RETURN(IReadOnlyApplicationControlDataInterface(system).GetApplicationControlData(
out_buffer, out_actual_size, application_control_source, application_id));
}
Result IApplicationManagerInterface::GetApplicationDesiredLanguage(
Out<ApplicationLanguage> out_desired_language, u32 supported_languages) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
R_RETURN(IReadOnlyApplicationControlDataInterface(system).GetApplicationDesiredLanguage(
out_desired_language, supported_languages));
}
Result IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode(
Out<u64> out_language_code, ApplicationLanguage application_language) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
R_RETURN(
IReadOnlyApplicationControlDataInterface(system).ConvertApplicationLanguageToLanguageCode(
out_language_code, application_language));
}
Result IApplicationManagerInterface::GenerateApplicationRecordCount(Out<s32> out_count) {
const auto& cache = system.GetContentProviderUnion();
const auto installed_games = cache.ListEntriesFilterOrigin(
std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
s32 count = 0;
for (const auto& [slot, game] : installed_games) {
if (game.title_id == 0 || game.title_id < 0x0100000000001FFFull) {
continue;
}
if ((game.title_id & 0xFFF) != 0) {
continue; // skip sub-programs
}
count++;
}
LOG_INFO(Service_NS, "called, found {} application records", count);
*out_count = count;
R_SUCCEED();
}
Result IApplicationManagerInterface::ListApplicationRecord(
OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> out_count,
s32 offset) {
const auto limit = out_records.size();
LOG_WARNING(Service_NS, "(STUBBED) called");
const auto& cache = system.GetContentProviderUnion();
const auto installed_games = cache.ListEntriesFilterOrigin(
std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
size_t i = 0;
u8 ii = 24;
std::vector<ApplicationRecord> records;
records.reserve(installed_games.size());
for (const auto& [slot, game] : installed_games) {
if (i >= limit) {
break;
}
if (game.title_id == 0 || game.title_id < 0x0100000000001FFFull) {
continue;
}
if (offset > 0) {
offset--;
continue;
if ((game.title_id & 0xFFF) != 0) {
continue; // skip sub-programs
}
ApplicationRecord record{};
record.application_id = game.title_id;
record.type = ApplicationRecordType::Installed;
record.unknown = 0; // 2 = needs update
record.unknown2 = ii++;
record.attributes = 0;
record.last_updated = 0; // TODO: Implement launch timestamp tracking
out_records[i++] = record;
records.push_back(record);
}
LOG_INFO(Service_NS, "called, offset={} limit={} total_found={}", offset, limit,
records.size());
// Sort by Title ID for now as a stable order
std::sort(records.begin(), records.end(),
[](const ApplicationRecord& lhs, const ApplicationRecord& rhs) {
return lhs.application_id < rhs.application_id;
});
size_t i = 0;
const size_t start = static_cast<size_t>(std::max(0, offset));
for (size_t j = start; j < records.size() && i < limit; ++j) {
out_records[i++] = records[j];
}
*out_count = static_cast<s32>(i);
@@ -463,7 +502,7 @@ Result IApplicationManagerInterface::ListApplicationRecord(
Result IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent(
OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_NS, "(STUBBED) called");
LOG_INFO(Service_NS, "called");
record_update_system_event.Signal();
*out_event = record_update_system_event.GetHandle();
@@ -473,29 +512,52 @@ Result IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent(
Result IApplicationManagerInterface::GetGameCardMountFailureEvent(
OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_NS, "(STUBBED) called");
LOG_INFO(Service_NS, "called");
*out_event = gamecard_mount_failure_event.GetHandle();
R_SUCCEED();
}
Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled(
Out<bool> out_is_any_application_entity_installed) {
LOG_WARNING(Service_NS, "(STUBBED) called");
LOG_INFO(Service_NS, "called");
*out_is_any_application_entity_installed = true;
R_SUCCEED();
}
Result IApplicationManagerInterface::GetApplicationView(
OutArray<ApplicationViewV20, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
const auto size = std::min(out_application_views.size(), application_ids.size());
LOG_INFO(Service_NS, "called, size={}", application_ids.size());
for (size_t i = 0; i < size; i++) {
ApplicationViewV20 view{};
view.application_id = application_ids[i];
view.version = 0; // TODO: Get actual version
view.flags = 0x401f17; // Typical flags for installed app
view.unk = 0;
view.download_state = {0, 0, 0, 0, 0, {0, 0}, 0};
view.download_progress = {0, 0, 0, 0, 0, {0, 0}, 0};
out_application_views[i] = view;
}
R_SUCCEED();
}
Result IApplicationManagerInterface::GetApplicationViewDeprecated(
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
const auto size = std::min(out_application_views.size(), application_ids.size());
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
LOG_INFO(Service_NS, "called (deprecated v19), size={}", application_ids.size());
for (size_t i = 0; i < size; i++) {
ApplicationView view{};
view.application_id = application_ids[i];
view.unk = 0x70000;
view.version = 0;
view.flags = 0x401f17;
view.download_state = {0, 0, 0, 0, 0, {0, 0}, 0};
view.download_progress = {0, 0, 0, 0, 0, {0, 0}, 0};
out_application_views[i] = view;
}
@@ -504,21 +566,39 @@ Result IApplicationManagerInterface::GetApplicationView(
}
Result IApplicationManagerInterface::GetApplicationViewWithPromotionInfo(
OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_count,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
const auto size = std::min(out_application_views.size(), application_ids.size());
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
const auto size = application_ids.size();
LOG_INFO(Service_NS, "called, size={}", size);
for (size_t i = 0; i < size; i++) {
ApplicationViewWithPromotionInfo view{};
view.view.application_id = application_ids[i];
view.view.unk = 0x70000;
view.view.flags = 0x401f17;
view.promotion = {};
// Using 0x78 per entry (V20 + PromotionInfo)
constexpr size_t entry_size = 0x58 + 0x20;
const size_t limit = out_buffer.size() / entry_size;
const size_t actual_count = std::min(size, limit);
out_application_views[i] = view;
for (size_t i = 0; i < actual_count; i++) {
ApplicationViewV20 view{};
view.application_id = application_ids[i];
view.version = 0;
view.flags = 0x401f17;
view.unk = 0;
PromotionInfo promotion{};
const size_t offset = i * entry_size;
std::memcpy(out_buffer.data() + offset, &view, sizeof(view));
std::memcpy(out_buffer.data() + offset + sizeof(view), &promotion, sizeof(promotion));
}
*out_count = static_cast<u32>(actual_count);
R_SUCCEED();
}
Result IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground(
u64 control_source, u64 application_id) {
LOG_INFO(Service_NS, "called, control_source={}, application_id={:016X}", control_source,
application_id);
// Success allows QLaunch to continue even if we don't actually download anything
R_SUCCEED();
}
@@ -545,32 +625,32 @@ Result IApplicationManagerInterface::GetApplicationRightsOnClient(
}
Result IApplicationManagerInterface::CheckSdCardMountStatus() {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
R_RETURN(IContentManagementInterface(system).CheckSdCardMountStatus());
}
Result IApplicationManagerInterface::GetSdCardMountStatusChangedEvent(
OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_NS, "(STUBBED) called");
LOG_INFO(Service_NS, "called");
*out_event = sd_card_mount_status_event.GetHandle();
R_SUCCEED();
}
Result IApplicationManagerInterface::GetFreeSpaceSize(Out<s64> out_free_space_size,
FileSys::StorageId storage_id) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
R_RETURN(IContentManagementInterface(system).GetFreeSpaceSize(out_free_space_size, storage_id));
}
Result IApplicationManagerInterface::GetGameCardUpdateDetectionEvent(
OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_NS, "(STUBBED) called");
LOG_INFO(Service_NS, "called");
*out_event = gamecard_update_detection_event.GetHandle();
R_SUCCEED();
}
Result IApplicationManagerInterface::ResumeAll() {
LOG_WARNING(Service_NS, "(STUBBED) called");
LOG_INFO(Service_NS, "called");
R_SUCCEED();
}
@@ -624,4 +704,85 @@ Result IApplicationManagerInterface::Cmd4088(Out<u64> out_result) {
R_SUCCEED();
}
Result IApplicationManagerInterface::Cmd4042(Out<u64> out_result) {
LOG_DEBUG(Service_NS, "(STUBBED) called [20.0.0+]");
*out_result = 0;
R_SUCCEED();
}
Result IApplicationManagerInterface::CountApplicationContentMeta(Out<u32> out_count,
u64 application_id) {
LOG_DEBUG(Service_NS, "called, application_id={:016X}", application_id);
const auto& cache = system.GetContentProviderUnion();
const auto installed_aocs = cache.ListEntriesFilterOrigin(std::nullopt, FileSys::TitleType::AOC,
FileSys::ContentRecordType::Data);
u32 count = 0;
for (const auto& [slot, aoc] : installed_aocs) {
if (FileSys::GetBaseTitleID(aoc.title_id) == application_id) {
count++;
}
}
LOG_DEBUG(Service_NS, "CountApplicationContentMeta found {} AOCS for app {:016X}", count,
application_id);
*out_count = count;
R_SUCCEED();
}
Result IApplicationManagerInterface::ListAvailableAddOnContent(
OutArray<u32, BufferAttr_HipcMapAlias> out_addons, Out<u32> out_count, u32 offset,
u64 application_id) {
const auto limit = out_addons.size();
LOG_DEBUG(Service_NS, "called, offset={}, limit={}, application_id={:016X}", offset, limit,
application_id);
const auto& cache = system.GetContentProviderUnion();
const auto installed_aocs = cache.ListEntriesFilterOrigin(std::nullopt, FileSys::TitleType::AOC,
FileSys::ContentRecordType::Data);
std::vector<u32> aocs;
aocs.reserve(installed_aocs.size());
LOG_DEBUG(Service_NS, "Scanning {} installed AOCs for application {:016X}",
installed_aocs.size(), application_id);
for (const auto& [slot, aoc] : installed_aocs) {
const auto base_id = FileSys::GetBaseTitleID(aoc.title_id);
if (base_id == application_id) {
LOG_DEBUG(Service_NS, "Found match! AOC ID: {:016X}, Base ID: {:016X}", aoc.title_id,
base_id);
aocs.push_back(static_cast<u32>(FileSys::GetAOCID(aoc.title_id)));
} else {
// Uncomment for even more verbose logging if needed
// LOG_DEBUG(Service_NS, "Skipping AOC ID: {:016X} (Base: {:016X})", aoc.title_id,
// base_id);
}
}
std::sort(aocs.begin(), aocs.end());
size_t i = 0;
const size_t start = static_cast<size_t>(std::max<u32>(0, offset));
for (size_t j = start; j < aocs.size() && i < limit; ++j) {
out_addons[i++] = aocs[j];
}
*out_count = static_cast<u32>(i);
R_SUCCEED();
}
Result IApplicationManagerInterface::Cmd4053() {
LOG_WARNING(Service_NS, "(STUBBED) called.");
R_SUCCEED();
}
void IApplicationManagerInterface::ListApplicationTitle(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
IReadOnlyApplicationControlDataInterface(system).ListApplicationTitle(ctx);
}
} // namespace Service::NS

View File

@@ -24,17 +24,23 @@ public:
u32 supported_languages);
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
ApplicationLanguage application_language);
Result GenerateApplicationRecordCount(Out<s32> out_count);
Result ListApplicationRecord(OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records,
Out<s32> out_count, s32 offset);
Result GetApplicationRecordUpdateSystemEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetGameCardMountFailureEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed);
Result GetApplicationView(
OutArray<ApplicationViewV20, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result GetApplicationViewDeprecated(
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result GetApplicationViewWithPromotionInfo(
OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_count,
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result RequestDownloadApplicationControlDataInBackground(u64 control_source,
u64 application_id);
Result GetApplicationRightsOnClient(
OutArray<ApplicationRightsOnClient, BufferAttr_HipcMapAlias> out_rights, Out<u32> out_count,
u32 flags, u64 application_id, Uid account_id);
@@ -53,7 +59,15 @@ public:
// [20.0.0+] Stub functions for QLaunch compatibility
Result Cmd4022(Out<u64> out_result);
Result Cmd4023(Out<u64> out_result);
Result Cmd4042(Out<u64> out_result);
Result Cmd4053();
Result Cmd4088(Out<u64> out_result);
// [1.0.0+]
Result CountApplicationContentMeta(Out<u32> out_count, u64 application_id);
Result ListAvailableAddOnContent(OutArray<u32, BufferAttr_HipcMapAlias> out_addons,
Out<u32> out_count, u32 offset, u64 application_id);
void ListApplicationTitle(HLERequestContext& ctx);
private:
KernelHelpers::ServiceContext service_context;

View File

@@ -29,31 +29,48 @@ enum class BackgroundNetworkUpdateState : u8 {
Ready,
};
/// ApplicationDownloadState
struct ApplicationDownloadState {
u64 downloaded_size;
u64 total_size;
u32 unk_x10;
u8 state;
u8 unk_x15;
std::array<u8, 0x2> unk_x16;
u64 unk_x18;
};
static_assert(sizeof(ApplicationDownloadState) == 0x20,
"ApplicationDownloadState has incorrect size.");
struct ApplicationRecord {
u64 application_id;
ApplicationRecordType type;
u8 unknown;
u8 attributes;
INSERT_PADDING_BYTES_NOINIT(0x6);
u8 unknown2;
INSERT_PADDING_BYTES_NOINIT(0x7);
s64 last_updated;
};
static_assert(sizeof(ApplicationRecord) == 0x18, "ApplicationRecord has incorrect size.");
/// ApplicationView
struct ApplicationView {
u64 application_id; ///< ApplicationId.
u32 unk; ///< Unknown.
u32 flags; ///< Flags.
std::array<u8, 0x10> unk_x10; ///< Unknown.
u32 unk_x20; ///< Unknown.
u16 unk_x24; ///< Unknown.
std::array<u8, 0x2> unk_x26; ///< Unknown.
std::array<u8, 0x8> unk_x28; ///< Unknown.
std::array<u8, 0x10> unk_x30; ///< Unknown.
u32 unk_x40; ///< Unknown.
u8 unk_x44; ///< Unknown.
std::array<u8, 0xb> unk_x45; ///< Unknown.
struct ApplicationViewV19 {
u64 application_id;
u32 version;
u32 flags;
ApplicationDownloadState download_state;
ApplicationDownloadState download_progress;
};
static_assert(sizeof(ApplicationViewV19) == 0x50, "ApplicationViewV19 has incorrect size.");
struct ApplicationViewV20 {
u64 application_id;
u32 version;
u32 flags;
u32 unk;
ApplicationDownloadState download_state;
ApplicationDownloadState download_progress;
};
static_assert(sizeof(ApplicationViewV20) == 0x58, "ApplicationViewV20 has incorrect size.");
using ApplicationView = ApplicationViewV19;
static_assert(sizeof(ApplicationView) == 0x50, "ApplicationView has incorrect size.");
struct ApplicationRightsOnClient {

View File

@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/logging/log.h"
#include "common/uuid.h"
#include "core/core.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ns/query_service.h"
#include "core/hle/service/service.h"
@@ -26,12 +30,12 @@ IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_,
{11, nullptr, "QueryAccountPlayEvent"},
{12, nullptr, "GetAvailableAccountPlayEventRange"},
{13, nullptr, "QueryApplicationPlayStatisticsForSystemV0"},
{14, nullptr, "QueryRecentlyPlayedApplication"},
{14, D<&IQueryService::QueryRecentlyPlayedApplication>, "QueryRecentlyPlayedApplication"},
{15, nullptr, "GetRecentlyPlayedApplicationUpdateEvent"},
{16, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystemV0"},
{17, nullptr, "QueryLastPlayTime"},
{18, nullptr, "QueryApplicationPlayStatisticsForSystem"},
{19, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"},
{18, D<&IQueryService::QueryApplicationPlayStatisticsForSystem>, "QueryApplicationPlayStatisticsForSystem"},
{19, D<&IQueryService::QueryApplicationPlayStatisticsByUserAccountIdForSystem>, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"},
};
// clang-format on
@@ -53,4 +57,66 @@ Result IQueryService::QueryPlayStatisticsByApplicationIdAndUserAccountId(
R_SUCCEED();
}
Result IQueryService::QueryRecentlyPlayedApplication(
Out<s32> out_count, OutArray<u64, BufferAttr_HipcMapAlias> out_applications, Uid user_id) {
const auto limit = out_applications.size();
LOG_INFO(Service_NS, "called. user_id={}, limit={}", user_id.uuid.FormattedString(), limit);
const auto& cache = system.GetContentProviderUnion();
auto installed_games =
cache.ListEntriesFilter(std::nullopt, FileSys::ContentRecordType::Program, std::nullopt);
// Filter and sort
std::vector<u64> applications;
for (const auto& entry : installed_games) {
if (entry.title_id == 0 || entry.title_id < 0x0100000000001FFFull) {
continue;
}
applications.push_back(entry.title_id);
}
// Sort by Title ID for now as a stable order
std::sort(applications.begin(), applications.end());
applications.erase(std::unique(applications.begin(), applications.end()), applications.end());
const auto count = std::min(limit, applications.size());
LOG_INFO(Service_NS, "Returning {} applications out of {} found.", count, applications.size());
for (size_t i = 0; i < count; i++) {
LOG_INFO(Service_NS, " [{}] TitleID: {:016X}", i, applications[i]);
out_applications[i] = applications[i];
}
*out_count = static_cast<s32>(count);
R_SUCCEED();
}
Result IQueryService::QueryApplicationPlayStatisticsForSystem(
Out<s32> out_entries, u8 flag,
OutArray<ApplicationPlayStatistics, BufferAttr_HipcMapAlias> out_stats,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
const size_t count = std::min(out_stats.size(), application_ids.size());
s32 written = 0;
for (size_t i = 0; i < count; ++i) {
const u64 app_id = application_ids[i];
ApplicationPlayStatistics stats{};
stats.application_id = app_id;
stats.play_time_ns = 0; // TODO: Implement play time tracking
stats.launch_count = 1; // Default to 1 for now
out_stats[i] = stats;
++written;
}
*out_entries = written;
LOG_INFO(Service_NS, "called, entries={} flag={}", written, flag);
R_SUCCEED();
}
Result IQueryService::QueryApplicationPlayStatisticsByUserAccountIdForSystem(
Out<s32> out_entries, u8 flag, Common::UUID user_id,
OutArray<ApplicationPlayStatistics, BufferAttr_HipcMapAlias> out_stats,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
LOG_INFO(Service_NS, "called, user_id={}", user_id.FormattedString());
return QueryApplicationPlayStatisticsForSystem(out_entries, flag, out_stats, application_ids);
}
} // namespace Service::NS

View File

@@ -23,6 +23,18 @@ struct PlayStatistics {
};
static_assert(sizeof(PlayStatistics) == 0x28, "PlayStatistics is an invalid size");
struct LastPlayTime {
INSERT_PADDING_BYTES_NOINIT(0x8); // Likely needs padding for buffer alignment if used
};
struct ApplicationPlayStatistics {
u64 application_id{};
u64 play_time_ns{};
u64 launch_count{};
};
static_assert(sizeof(ApplicationPlayStatistics) == 0x18,
"ApplicationPlayStatistics is an invalid size");
class IQueryService final : public ServiceFramework<IQueryService> {
public:
explicit IQueryService(Core::System& system_);
@@ -31,6 +43,20 @@ public:
private:
Result QueryPlayStatisticsByApplicationIdAndUserAccountId(
Out<PlayStatistics> out_play_statistics, bool unknown, u64 application_id, Uid account_id);
Result QueryRecentlyPlayedApplication(Out<s32> out_count,
OutArray<u64, BufferAttr_HipcMapAlias> out_applications,
Uid user_id);
Result QueryApplicationPlayStatisticsForSystem(
Out<s32> out_entries, u8 flag,
OutArray<ApplicationPlayStatistics, BufferAttr_HipcMapAlias> out_stats,
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result QueryApplicationPlayStatisticsByUserAccountIdForSystem(
Out<s32> out_entries, u8 flag, Common::UUID user_id,
OutArray<ApplicationPlayStatistics, BufferAttr_HipcMapAlias> out_stats,
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
};
} // namespace Service::NS

View File

@@ -1,11 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_STATIC
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#include <stb_image.h>
#include <stb_image_resize.h>
#include <stb_image_write.h>
#pragma GCC diagnostic pop
#include <algorithm>
#include <vector>
#include "common/settings.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/ns/language.h"
#include "core/hle/service/ns/ns_results.h"
#include "core/hle/service/ns/read_only_application_control_data_interface.h"
@@ -13,6 +32,113 @@
namespace Service::NS {
namespace {
void JPGToMemory(void* context, void* data, int size) {
auto* buffer = static_cast<std::vector<u8>*>(context);
const auto* char_data = static_cast<const u8*>(data);
buffer->insert(buffer->end(), char_data, char_data + size);
}
void SanitizeJPEGImageSize(std::vector<u8>& image) {
constexpr std::size_t max_jpeg_image_size = 0x20000;
constexpr int profile_dimensions = 174; // for grid view thingy
int original_width, original_height, color_channels;
auto* plain_image =
stbi_load_from_memory(image.data(), static_cast<int>(image.size()), &original_width,
&original_height, &color_channels, STBI_rgb);
if (plain_image == nullptr) {
LOG_ERROR(Service_NS, "Failed to load JPEG for sanitization.");
return;
}
if (original_width != profile_dimensions || original_height != profile_dimensions) {
std::vector<u8> out_image(profile_dimensions * profile_dimensions * STBI_rgb);
stbir_resize_uint8_srgb(plain_image, original_width, original_height, 0, out_image.data(),
profile_dimensions, profile_dimensions, 0, STBI_rgb, 0,
STBIR_FILTER_BOX);
image.clear();
if (!stbi_write_jpg_to_func(JPGToMemory, &image, profile_dimensions, profile_dimensions,
STBI_rgb, out_image.data(), 90)) {
LOG_ERROR(Service_NS, "Failed to resize the user provided image.");
}
}
stbi_image_free(plain_image);
if (image.size() > max_jpeg_image_size) {
image.resize(max_jpeg_image_size);
}
}
} // namespace
// IAsyncValue implementation for ListApplicationTitle
// https://switchbrew.org/wiki/NS_services#ListApplicationTitle
class IAsyncValueForListApplicationTitle final
: public ServiceFramework<IAsyncValueForListApplicationTitle> {
public:
explicit IAsyncValueForListApplicationTitle(Core::System& system_, s32 offset, s32 size)
: ServiceFramework{system_, "IAsyncValue"}, service_context{system_, "IAsyncValue"},
data_offset{offset}, data_size{size} {
static const FunctionInfo functions[] = {
{0, &IAsyncValueForListApplicationTitle::GetSize, "GetSize"},
{1, &IAsyncValueForListApplicationTitle::Get, "Get"},
{2, &IAsyncValueForListApplicationTitle::Cancel, "Cancel"},
{3, &IAsyncValueForListApplicationTitle::GetErrorContext, "GetErrorContext"},
};
RegisterHandlers(functions);
completion_event = service_context.CreateEvent("IAsyncValue:Completion");
completion_event->GetReadableEvent().Signal();
}
~IAsyncValueForListApplicationTitle() override {
service_context.CloseEvent(completion_event);
}
Kernel::KReadableEvent& ReadableEvent() const {
return completion_event->GetReadableEvent();
}
private:
void GetSize(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.Push<s64>(data_size);
}
void Get(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
std::vector<u8> buffer(sizeof(s32));
std::memcpy(buffer.data(), &data_offset, sizeof(s32));
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Cancel(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void GetErrorContext(HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* completion_event{};
s32 data_offset;
s32 data_size;
};
IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface(
Core::System& system_)
: ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} {
@@ -23,6 +149,9 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
{2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
{4, nullptr, "SelectApplicationDesiredLanguage"},
{5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlData"},
{13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"},
{19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
};
// clang-format on
@@ -119,4 +248,191 @@ Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLan
R_SUCCEED();
}
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u64> out_total_size,
ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) {
LOG_INFO(Service_NS,
"called with control_source={}, flags=({:02X},{:02X}), application_id={:016X}",
application_control_source, flag1, flag2, application_id);
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
const auto size = out_buffer.size();
const auto nacp_size = sizeof(FileSys::RawNACP);
if (size < nacp_size) {
LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min={:08X})",
size, nacp_size);
R_THROW(ResultUnknown);
}
if (control.first != nullptr) {
const auto bytes = control.first->GetRawBytes();
const auto copy_len =
(std::min)(static_cast<size_t>(bytes.size()), static_cast<size_t>(nacp_size));
std::memcpy(out_buffer.data(), bytes.data(), copy_len);
if (copy_len < nacp_size) {
std::memset(out_buffer.data() + copy_len, 0, nacp_size - copy_len);
}
} else {
LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}", application_id);
std::memset(out_buffer.data(), 0, nacp_size);
}
const auto icon_area_size = size - nacp_size;
std::vector<u8> final_icon_data;
if (control.second != nullptr) {
size_t full_size = control.second->GetSize();
if (full_size > 0) {
final_icon_data.resize(full_size);
control.second->Read(final_icon_data.data(), full_size);
if (flag1 == 1) {
SanitizeJPEGImageSize(final_icon_data);
}
}
}
size_t available_icon_bytes = final_icon_data.size();
if (icon_area_size > 0) {
const size_t to_copy = (std::min)(available_icon_bytes, icon_area_size);
if (to_copy > 0) {
std::memcpy(out_buffer.data() + nacp_size, final_icon_data.data(), to_copy);
}
if (to_copy < icon_area_size) {
std::memset(out_buffer.data() + nacp_size + to_copy, 0, icon_area_size - to_copy);
}
}
const u32 total_available = static_cast<u32>(nacp_size + available_icon_bytes);
*out_total_size = (static_cast<u64>(total_available) << 32) | static_cast<u64>(flag1);
R_SUCCEED();
}
void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestContext& ctx) {
const auto app_ids_buffer = ctx.ReadBuffer();
const size_t app_count = app_ids_buffer.size() / sizeof(u64);
std::vector<u64> application_ids(app_count);
if (app_count > 0) {
std::memcpy(application_ids.data(), app_ids_buffer.data(), app_count * sizeof(u64));
}
auto t_mem_obj = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(0));
auto* t_mem = t_mem_obj.GetPointerUnsafe();
constexpr size_t title_entry_size = sizeof(FileSys::LanguageEntry);
const size_t total_data_size = app_count * title_entry_size;
constexpr s32 data_offset = 0;
if (t_mem != nullptr && app_count > 0) {
auto& memory = system.ApplicationMemory();
const auto t_mem_address = t_mem->GetSourceAddress();
for (size_t i = 0; i < app_count; ++i) {
const u64 app_id = application_ids[i];
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
FileSys::LanguageEntry entry{};
if (control.first != nullptr) {
entry = control.first->GetLanguageEntry();
}
const size_t offset = i * title_entry_size;
memory.WriteBlock(t_mem_address + offset, &entry, title_entry_size);
}
}
auto async_value = std::make_shared<IAsyncValueForListApplicationTitle>(
system, data_offset, static_cast<s32>(total_data_size));
IPC::ResponseBuilder rb{ctx, 2, 1, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(async_value->ReadableEvent());
rb.PushIpcInterface(std::move(async_value));
}
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_flags_a, Out<u32> out_flags_b,
Out<u32> out_actual_size, ApplicationControlSource application_control_source, u8 flag1,
u8 flag2, u64 application_id) {
LOG_INFO(Service_NS,
"called with control_source={}, flags=({:02X},{:02X}), application_id={:016X}",
application_control_source, flag1, flag2, application_id);
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
const auto size = out_buffer.size();
const auto nacp_size = sizeof(FileSys::RawNACP);
if (size < nacp_size) {
LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min={:08X})",
size, nacp_size);
R_THROW(ResultUnknown);
}
if (control.first != nullptr) {
const auto bytes = control.first->GetRawBytes();
const auto copy_len =
(std::min)(static_cast<size_t>(bytes.size()), static_cast<size_t>(nacp_size));
std::memcpy(out_buffer.data(), bytes.data(), copy_len);
if (copy_len < nacp_size) {
std::memset(out_buffer.data() + copy_len, 0, nacp_size - copy_len);
}
} else {
LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}, defaulting to zero",
application_id);
std::memset(out_buffer.data(), 0, nacp_size);
}
const auto icon_area_size = size - nacp_size;
std::vector<u8> final_icon_data;
if (control.second != nullptr) {
size_t full_size = control.second->GetSize();
if (full_size > 0) {
final_icon_data.resize(full_size);
control.second->Read(final_icon_data.data(), full_size);
// TODO: Implement SanitizeJPEGImageSize(final_icon_data) if flag1 == 1
}
}
size_t available_icon_bytes = final_icon_data.size();
if (icon_area_size > 0) {
const size_t to_copy = (std::min)(available_icon_bytes, icon_area_size);
if (to_copy > 0) {
std::memcpy(out_buffer.data() + nacp_size, final_icon_data.data(), to_copy);
}
if (to_copy < icon_area_size) {
std::memset(out_buffer.data() + nacp_size + to_copy, 0, icon_area_size - to_copy);
}
} else {
std::memset(out_buffer.data() + nacp_size, 0, icon_area_size);
}
const u32 actual_total_size = static_cast<u32>(nacp_size + available_icon_bytes);
// Out 1: always 0x10001 (likely presents flags: Bit0=Icon, Bit16=NACP)
// Out 2: reflects flag1 application (0 if flag1=0, 0x10001 if flag1=1)
// Out 3: The actual size of data
*out_flags_a = 0x10001;
*out_flags_b = (flag1 == 1) ? 0x10001 : 0;
*out_actual_size = actual_total_size;
R_SUCCEED();
}
} // namespace Service::NS

View File

@@ -16,7 +16,6 @@ public:
explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
~IReadOnlyApplicationControlDataInterface() override;
public:
Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> out_actual_size,
ApplicationControlSource application_control_source,
@@ -25,6 +24,16 @@ public:
u32 supported_languages);
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
ApplicationLanguage application_language);
Result GetApplicationControlData2(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u64> out_total_size,
ApplicationControlSource application_control_source, u8 flag1,
u8 flag2, u64 application_id);
void ListApplicationTitle(HLERequestContext& ctx);
Result GetApplicationControlData3(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> out_flags_a, Out<u32> out_flags_b,
Out<u32> out_actual_size,
ApplicationControlSource application_control_source, u8 flag1,
u8 flag2, u64 application_id);
};
} // namespace Service::NS

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ns/application_manager_interface.h"
#include "core/hle/service/ns/read_only_application_record_interface.h"
namespace Service::NS {
@@ -13,6 +14,8 @@ IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::S
{1, nullptr, "NotifyApplicationFailure"},
{2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>,
"IsDataCorruptedResult"},
{3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>,
"ListApplicationRecord"},
};
// clang-format on
@@ -35,4 +38,16 @@ Result IReadOnlyApplicationRecordInterface::IsDataCorruptedResult(
R_SUCCEED();
}
Result IReadOnlyApplicationRecordInterface::ListApplicationRecord(
OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> out_count,
s32 entry_offset) {
LOG_INFO(Service_NS,
"delegating to IApplicationManagerInterface::ListApplicationRecord, offset={} "
"limit={}",
entry_offset, out_records.size());
R_RETURN(IApplicationManagerInterface(system).ListApplicationRecord(out_records, out_count,
entry_offset));
}
} // namespace Service::NS

View File

@@ -4,6 +4,7 @@
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/service.h"
namespace Service::NS {
@@ -17,6 +18,8 @@ public:
private:
Result HasApplicationRecord(Out<bool> out_has_application_record, u64 program_id);
Result IsDataCorruptedResult(Out<bool> out_is_data_corrupted_result, Result result);
Result ListApplicationRecord(OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records,
Out<s32> out_count, s32 entry_offset);
};
} // namespace Service::NS

View File

@@ -42,77 +42,77 @@ IServiceGetterInterface::~IServiceGetterInterface() = default;
Result IServiceGetterInterface::GetDynamicRightsInterface(
Out<SharedPointer<IDynamicRightsInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IDynamicRightsInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetReadOnlyApplicationControlDataInterface(
Out<SharedPointer<IReadOnlyApplicationControlDataInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IReadOnlyApplicationControlDataInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetReadOnlyApplicationRecordInterface(
Out<SharedPointer<IReadOnlyApplicationRecordInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IReadOnlyApplicationRecordInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetECommerceInterface(
Out<SharedPointer<IECommerceInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IECommerceInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetApplicationVersionInterface(
Out<SharedPointer<IApplicationVersionInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IApplicationVersionInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetFactoryResetInterface(
Out<SharedPointer<IFactoryResetInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IFactoryResetInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetAccountProxyInterface(
Out<SharedPointer<IAccountProxyInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IAccountProxyInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetApplicationManagerInterface(
Out<SharedPointer<IApplicationManagerInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IApplicationManagerInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetDownloadTaskInterface(
Out<SharedPointer<IDownloadTaskInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IDownloadTaskInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetContentManagementInterface(
Out<SharedPointer<IContentManagementInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IContentManagementInterface>(system);
R_SUCCEED();
}
Result IServiceGetterInterface::GetDocumentInterface(
Out<SharedPointer<IDocumentInterface>> out_interface) {
LOG_DEBUG(Service_NS, "called");
LOG_INFO(Service_NS, "called");
*out_interface = std::make_shared<IDocumentInterface>(system);
R_SUCCEED();
}

View File

@@ -16,8 +16,9 @@
namespace Service::Nvidia::NvCore {
Session::Session(SessionId id_, Kernel::KProcess* process_, Core::Asid asid_)
: id{id_}, process{process_}, asid{asid_}, has_preallocated_area{}, mapper{}, is_active{} {}
Session::Session(SessionId id_, Kernel::KProcess* process_, u64 pid_, Core::Asid asid_)
: id{id_}, process{process_}, pid{pid_}, asid{asid_}, has_preallocated_area{}, mapper{},
is_active{} {}
Session::~Session() = default;
@@ -49,8 +50,39 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
continue;
}
if (session.process == process) {
session.ref_count++;
return session.id;
if (session.pid != process->GetProcessId()) {
LOG_WARNING(Service_NVDRV,
"Container::OpenSession: Stale session detected! PID mismatch (old={}, "
"new={}). Marking as inactive.",
session.pid, process->GetProcessId());
// Force close stale session logic
// NOTE: We do NOT unmap handles or free memory here because it causes
// KSynchronizationObject::UnlinkNode segfaults if threads are still waiting on
// events associated with this session. We effectively leak the old session's
// resources to ensure stability.
// impl->file.UnmapAllHandles(session.id);
// auto& smmu_mgr = impl->host1x.MemoryManager();
/*
if (session.has_preallocated_area) {
const DAddr region_start = session.mapper->GetRegionStart();
const size_t region_size = session.mapper->GetRegionSize();
session.mapper.reset();
smmu_mgr.Free(region_start, region_size);
session.has_preallocated_area = false;
}
*/
session.is_active = false;
session.ref_count = 0;
// smmu_mgr.UnregisterProcess(session.asid);
// impl->id_pool.emplace_front(session.id.id);
// Continue searching to clean up other stale sessions if any?
// Or proceed to create new one. Stale one is now inactive.
continue;
} else {
session.ref_count++;
return session.id;
}
}
}
size_t new_id{};
@@ -60,10 +92,10 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
if (!impl->id_pool.empty()) {
new_id = impl->id_pool.front();
impl->id_pool.pop_front();
impl->sessions[new_id] = Session{SessionId{new_id}, process, asid};
impl->sessions[new_id] = Session{SessionId{new_id}, process, process->GetProcessId(), asid};
} else {
new_id = impl->new_ids++;
impl->sessions.emplace_back(SessionId{new_id}, process, asid);
impl->sessions.emplace_back(SessionId{new_id}, process, process->GetProcessId(), asid);
}
auto& session = impl->sessions[new_id];
session.is_active = true;

View File

@@ -32,7 +32,7 @@ struct SessionId {
};
struct Session {
Session(SessionId id_, Kernel::KProcess* process_, Core::Asid asid_);
Session(SessionId id_, Kernel::KProcess* process_, u64 pid_, Core::Asid asid_);
~Session();
Session(const Session&) = delete;
@@ -42,6 +42,7 @@ struct Session {
SessionId id;
Kernel::KProcess* process;
u64 pid;
Core::Asid asid;
bool has_preallocated_area{};
std::unique_ptr<HeapMapper> mapper{};

View File

@@ -160,7 +160,6 @@ void NVDRV::Initialize(HLERequestContext& ctx) {
};
if (is_initialized) {
// No need to initialize again
return;
}

View File

@@ -12,7 +12,7 @@ struct Layer {
explicit Layer(std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer_,
s32 consumer_id_)
: buffer_item_consumer(std::move(buffer_item_consumer_)), consumer_id(consumer_id_),
blending(LayerBlending::None), visible(true) {}
blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false) {}
~Layer() {
buffer_item_consumer->Abandon();
}
@@ -21,6 +21,8 @@ struct Layer {
s32 consumer_id;
LayerBlending blending;
bool visible;
s32 z_index;
bool is_overlay;
};
struct LayerStack {

View File

@@ -83,7 +83,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
.width = igbp_buffer.Width(),
.height = igbp_buffer.Height(),
.stride = igbp_buffer.Stride(),
.z_index = 0,
.z_index = layer->z_index,
.blending = layer->blending,
.transform = static_cast<android::BufferTransformFlags>(item.transform),
.crop_rect = item.crop,

View File

@@ -121,6 +121,13 @@ std::shared_ptr<Layer> SurfaceFlinger::FindLayer(s32 consumer_binder_id) {
return nullptr;
}
void SurfaceFlinger::SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay) {
if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
layer->is_overlay = is_overlay;
LOG_DEBUG(Service_VI, "Layer {} marked as overlay: {}", consumer_binder_id, is_overlay);
}
}
void SurfaceFlinger::CreateBufferQueue(s32* out_consumer_binder_id, s32* out_producer_binder_id) {
auto& nvmap = nvdrv->GetContainer().GetNvMapFile();
auto core = std::make_shared<android::BufferQueueCore>();

View File

@@ -44,10 +44,12 @@ public:
void SetLayerVisibility(s32 consumer_binder_id, bool visible);
void SetLayerBlending(s32 consumer_binder_id, LayerBlending blending);
void SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay);
std::shared_ptr<Layer> FindLayer(s32 consumer_binder_id);
private:
Display* FindDisplay(u64 display_id);
std::shared_ptr<Layer> FindLayer(s32 consumer_binder_id);
public:
// TODO: these don't belong here

View File

@@ -36,6 +36,7 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili
{1016, nullptr, "ConfirmShowNewsPermission"},
{1017, D<&IParentalControlService::EndFreeCommunication>, "EndFreeCommunication"},
{1018, D<&IParentalControlService::IsFreeCommunicationAvailable>, "IsFreeCommunicationAvailable"},
{1019, D<&IParentalControlService::ConfirmLaunchApplicationPermission>, "ConfirmLaunchApplicationPermission"},
{1031, D<&IParentalControlService::IsRestrictionEnabled>, "IsRestrictionEnabled"},
{1032, D<&IParentalControlService::GetSafetyLevel>, "GetSafetyLevel"},
{1033, nullptr, "SetSafetyLevel"},

View File

@@ -18,4 +18,12 @@ union MessageFlags {
};
static_assert(sizeof(MessageFlags) == 0x8, "MessageFlags has incorrect size");
struct SourceName {
char name[0x16];
const char* GetString() const {
return name;
}
};
} // namespace Service::PSC

View File

@@ -5,32 +5,113 @@
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/psc/ovln/receiver.h"
namespace Service::PSC {
IReceiver::IReceiver(Core::System& system_)
: ServiceFramework{system_, "IReceiver"}, service_context{system_, "IReceiver"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "AddSource"},
{1, nullptr, "RemoveSource"},
{0, D<&IReceiver::AddSource>, "AddSource"},
{1, D<&IReceiver::RemoveSource>, "RemoveSource"},
{2, D<&IReceiver::GetReceiveEventHandle>, "GetReceiveEventHandle"},
{3, nullptr, "Receive"},
{4, nullptr, "ReceiveWithTick"},
{3, D<&IReceiver::Receive>, "Receive"},
{4, D<&IReceiver::ReceiveWithTick>, "ReceiveWithTick"},
};
// clang-format on
RegisterHandlers(functions);
event = service_context.CreateEvent("IReceiver:Event");
receive_event = service_context.CreateEvent("IReceiver::ReceiveEvent");
}
IReceiver::~IReceiver() {
service_context.CloseEvent(event);
service_context.CloseEvent(receive_event);
}
Result IReceiver::GetReceiveEventHandle(OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_DEBUG(Service_PSC, "called");
*out_event = &event->GetReadableEvent();
Result IReceiver::AddSource(SourceName source_name) {
const std::string name = source_name.GetString();
LOG_INFO(Service_PSC, "called: source_name={}", name);
// Add source if it doesn't already exist
if (message_sources.find(name) == message_sources.end()) {
message_sources[name] = {};
}
R_SUCCEED();
}
Result IReceiver::RemoveSource(SourceName source_name) {
const std::string name = source_name.GetString();
LOG_INFO(Service_PSC, "called: source_name={}", name);
// Remove source if it exists
message_sources.erase(name);
R_SUCCEED();
}
Result IReceiver::GetReceiveEventHandle(OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_INFO(Service_PSC, "called");
*out_event = &receive_event->GetReadableEvent();
R_SUCCEED();
}
Result IReceiver::Receive(Out<OverlayNotification> out_notification, Out<MessageFlags> out_flags) {
u64 tick;
return ReceiveWithTick(out_notification, out_flags, Out<u64>(&tick));
}
Result IReceiver::ReceiveWithTick(Out<OverlayNotification> out_notification,
Out<MessageFlags> out_flags, Out<u64> out_tick) {
LOG_DEBUG(Service_PSC, "called");
// Find the message with the lowest ID across all sources
const std::string* target_source = nullptr;
size_t target_index = 0;
for (const auto& [source_name, messages] : message_sources) {
if (!messages.empty()) {
if (target_source == nullptr) {
target_source = &source_name;
target_index = 0;
}
// Note: In the real implementation, we would track message IDs
// For now, just use FIFO order
}
}
if (target_source != nullptr) {
auto& messages = message_sources[*target_source];
*out_notification = messages[target_index].first;
*out_flags = messages[target_index].second;
*out_tick = 0; // TODO: Implement tick tracking
// Remove the message
messages.erase(messages.begin() + target_index);
// Clear event if no more messages
bool has_messages = false;
for (const auto& [_, msgs] : message_sources) {
if (!msgs.empty()) {
has_messages = true;
break;
}
}
if (!has_messages) {
receive_event->Clear();
}
R_SUCCEED();
}
// No messages available
*out_notification = {};
*out_flags = {};
*out_tick = 0;
LOG_WARNING(Service_PSC, "No messages available");
R_THROW(ResultUnknown); // TODO: Use proper OvlnResult::NoMessages when available
}
} // namespace Service::PSC

View File

@@ -3,12 +3,17 @@
#pragma once
#include <map>
#include <memory>
#include <string>
#include "core/hle/result.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/psc/ovln/ovln_types.h"
#include "core/hle/service/service.h"
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
@@ -20,10 +25,18 @@ public:
~IReceiver() override;
private:
Result AddSource(SourceName source_name);
Result RemoveSource(SourceName source_name);
Result GetReceiveEventHandle(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result Receive(Out<OverlayNotification> out_notification, Out<MessageFlags> out_flags);
Result ReceiveWithTick(Out<OverlayNotification> out_notification, Out<MessageFlags> out_flags,
Out<u64> out_tick);
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* event;
Kernel::KEvent* receive_event;
std::map<std::string, std::vector<std::pair<OverlayNotification, MessageFlags>>>
message_sources;
};
} // namespace Service::PSC

View File

@@ -431,6 +431,15 @@ static_assert(sizeof(FirmwareVersionFormat) == 0x100, "FirmwareVersionFormat is
static_assert(std::is_trivial_v<FirmwareVersionFormat>,
"FirmwareVersionFormat type must be trivially copyable.");
/// This is nn::settings::system::RebootlessSystemUpdateVersion
struct RebootlessSystemUpdateVersion {
u32 version;
std::array<char, 0x18> display_version;
INSERT_PADDING_BYTES(4);
};
static_assert(sizeof(RebootlessSystemUpdateVersion) == 0x20,
"RebootlessSystemUpdateVersion is an invalid size");
/// This is nn::settings::system::HomeMenuScheme
struct HomeMenuScheme {
u32 main;

View File

@@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <fstream>
#include "common/assert.h"
@@ -239,7 +240,7 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_)
{146, nullptr, "SetConsoleSixAxisSensorAngularVelocityTimeBias"},
{147, nullptr, "GetConsoleSixAxisSensorAngularAcceleration"},
{148, nullptr, "SetConsoleSixAxisSensorAngularAcceleration"},
{149, nullptr, "GetRebootlessSystemUpdateVersion"},
{149, D<&ISystemSettingsServer::GetRebootlessSystemUpdateVersion>, "GetRebootlessSystemUpdateVersion"},
{150, C<&ISystemSettingsServer::GetDeviceTimeZoneLocationUpdatedTime>, "GetDeviceTimeZoneLocationUpdatedTime"},
{151, C<&ISystemSettingsServer::SetDeviceTimeZoneLocationUpdatedTime>, "SetDeviceTimeZoneLocationUpdatedTime"},
{152, C<&ISystemSettingsServer::GetUserSystemClockAutomaticCorrectionUpdatedTime>, "GetUserSystemClockAutomaticCorrectionUpdatedTime"},
@@ -852,6 +853,17 @@ Result ISystemSettingsServer::SetQuestFlag(QuestFlag quest_flag) {
R_SUCCEED();
}
Result ISystemSettingsServer::GetRebootlessSystemUpdateVersion(
Out<RebootlessSystemUpdateVersion> out_version) {
LOG_WARNING(Service_SET, "(STUBBED) called");
out_version->version = 0;
std::memset(out_version->display_version.data(), 0, out_version->display_version.size());
std::strcpy(out_version->display_version.data(), "0.0.0");
R_SUCCEED();
}
Result ISystemSettingsServer::GetDeviceTimeZoneLocationName(
Out<Service::PSC::Time::LocationName> out_name) {
LOG_INFO(Service_SET, "called");

View File

@@ -92,6 +92,7 @@ public:
Result SetSpeakerAutoMuteFlag(bool force_mute_on_headphone_removed);
Result GetQuestFlag(Out<QuestFlag> out_quest_flag);
Result SetQuestFlag(QuestFlag quest_flag);
Result GetRebootlessSystemUpdateVersion(Out<RebootlessSystemUpdateVersion> out_version);
Result GetDeviceTimeZoneLocationName(Out<Service::PSC::Time::LocationName> out_name);
Result SetDeviceTimeZoneLocationName(const Service::PSC::Time::LocationName& name);
Result SetRegionCode(SystemRegionCode region_code);

View File

@@ -131,6 +131,49 @@ Result Container::SetLayerBlending(u64 layer_id, bool enabled) {
R_SUCCEED();
}
Result Container::SetLayerZIndex(u64 layer_id, s32 z_index) {
std::scoped_lock lk{m_lock};
auto* const layer = m_layers.GetLayerById(layer_id);
R_UNLESS(layer != nullptr, VI::ResultNotFound);
if (auto layer_ref = m_surface_flinger->FindLayer(layer->GetConsumerBinderId())) {
LOG_DEBUG(Service_VI, "called, SetLayerZIndex layer_id={} z={} (cid={})", layer_id, z_index,
layer->GetConsumerBinderId());
layer_ref->z_index = z_index;
} else {
LOG_DEBUG(Service_VI,
"called, SetLayerZIndex failed to find layer for layer_id={} (cid={})", layer_id,
layer->GetConsumerBinderId());
}
R_SUCCEED();
}
Result Container::GetLayerZIndex(u64 layer_id, s32* out_z_index) {
std::scoped_lock lk{m_lock};
auto* const layer = m_layers.GetLayerById(layer_id);
R_UNLESS(layer != nullptr, VI::ResultNotFound);
if (auto layer_ref = m_surface_flinger->FindLayer(layer->GetConsumerBinderId())) {
*out_z_index = layer_ref->z_index;
R_SUCCEED();
}
R_RETURN(VI::ResultNotFound);
}
Result Container::SetLayerIsOverlay(u64 layer_id, bool is_overlay) {
std::scoped_lock lk{m_lock};
auto* const layer = m_layers.GetLayerById(layer_id);
R_UNLESS(layer != nullptr, VI::ResultNotFound);
m_surface_flinger->SetLayerIsOverlay(layer->GetConsumerBinderId(), is_overlay);
R_SUCCEED();
}
void Container::LinkVsyncEvent(u64 display_id, Event* event) {
std::scoped_lock lk{m_lock};
m_conductor->LinkVsyncEvent(display_id, event);

View File

@@ -62,6 +62,9 @@ public:
Result SetLayerVisibility(u64 layer_id, bool visible);
Result SetLayerBlending(u64 layer_id, bool enabled);
Result SetLayerZIndex(u64 layer_id, s32 z_index);
Result GetLayerZIndex(u64 layer_id, s32* out_z_index);
Result SetLayerIsOverlay(u64 layer_id, bool is_overlay);
void LinkVsyncEvent(u64 display_id, Event* event);
void UnlinkVsyncEvent(u64 display_id, Event* event);

View File

@@ -116,6 +116,10 @@ Result IManagerDisplayService::SetLayerBlending(bool enabled, u64 layer_id) {
R_RETURN(m_container->SetLayerBlending(layer_id, enabled));
}
Result IManagerDisplayService::SetLayerZIndex(s32 z_index, u64 layer_id) {
R_RETURN(m_container->SetLayerZIndex(layer_id, z_index));
}
Result IManagerDisplayService::CreateManagedLayer(Out<u64> out_layer_id, u32 flags, u64 display_id,
AppletResourceUserId aruid) {
LOG_DEBUG(Service_VI, "called. flags={}, display={}, aruid={}", flags, display_id, aruid.pid);

View File

@@ -22,6 +22,7 @@ public:
void DestroySharedLayerSession(Kernel::KProcess* owner_process);
Result SetLayerBlending(bool enabled, u64 layer_id);
Result SetLayerZIndex(s32 z_index, u64 layer_id);
public:
Result CreateManagedLayer(Out<u64> out_layer_id, u32 flags, u64 display_id,