Compare commits
4 Commits
xbzk-mci-b
...
jdlo-eden-
| Author | SHA1 | Date | |
|---|---|---|---|
| a71a82d3b7 | |||
| bd1d270e97 | |||
| 664ff7cc85 | |||
| b5c86787ab |
@@ -17,8 +17,6 @@ add_library(core STATIC
|
||||
constants.h
|
||||
core.cpp
|
||||
core.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
cpu_manager.cpp
|
||||
@@ -45,7 +43,11 @@ add_library(core STATIC
|
||||
device_memory.cpp
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
device_memory_manager.inc
|
||||
internal_network/legacy_online.cpp
|
||||
internal_network/legacy_online.h
|
||||
file_sys/bis_factory.cpp
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "core/hle/service/services.h"
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/internal_network/legacy_online.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
@@ -137,6 +138,13 @@ struct System::Impl {
|
||||
kernel.SetMulticore(is_multicore);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
|
||||
if (!legacy_online) {
|
||||
legacy_online = std::make_unique<Network::LegacyOnlineService>();
|
||||
legacy_online->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void ReinitializeIfNecessary(System& system) {
|
||||
@@ -293,6 +301,48 @@ struct System::Impl {
|
||||
return SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
void LoadOverrides(u64 programId) const {
|
||||
std::string vendor = gpu_core->Renderer().GetDeviceVendor();
|
||||
LOG_INFO(Core, "GPU Vendor: {}", vendor);
|
||||
|
||||
// Reset all per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
|
||||
// Insert PC overrides here
|
||||
|
||||
#ifdef ANDROID
|
||||
// Example on how to set a setting based on the program ID and vendor
|
||||
if (programId == 0x010028600EBDA000 && vendor == "Mali") { // Mario 3d World
|
||||
// Settings::values.example = true;
|
||||
}
|
||||
|
||||
// Example array of program IDs
|
||||
const std::array<u64, 10> example_array = {
|
||||
//0xprogramId
|
||||
0x0004000000033400, // Game 1
|
||||
0x0004000000033500 // Game 2
|
||||
// And so on
|
||||
};
|
||||
|
||||
for (auto id : example_array) {
|
||||
if (programId == id) {
|
||||
// Settings::values.example = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Ninja Gaiden Ragebound
|
||||
constexpr u64 ngr = 0x0100781020710000ULL;
|
||||
|
||||
if (programId == ngr) {
|
||||
LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend");
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
}
|
||||
}
|
||||
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath,
|
||||
Service::AM::FrontendAppletParameters& params) {
|
||||
@@ -378,8 +428,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
LoadOverrides(program_id);
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
@@ -428,6 +477,12 @@ struct System::Impl {
|
||||
stop_event = {};
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (legacy_online) {
|
||||
// Keep legacy_online running for the emulator's lifetime
|
||||
// legacy_online->Stop();
|
||||
// legacy_online.reset();
|
||||
}
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
room_member->SendGameInfo(game_info);
|
||||
@@ -516,6 +571,9 @@ struct System::Impl {
|
||||
|
||||
/// Network instance
|
||||
Network::NetworkInstance network_instance;
|
||||
|
||||
/// Legacy Online Service
|
||||
std::unique_ptr<Network::LegacyOnlineService> legacy_online;
|
||||
|
||||
/// Debugger
|
||||
std::unique_ptr<Core::Debugger> debugger;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -55,39 +57,114 @@ std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
return dir->CreateDirectoryRelative(save_directory);
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
u64 target_program_id = meta.program_id;
|
||||
// CRITICAL FIX: If the game requests Cache/Temp with ProgramID 0 (generic),
|
||||
// we MUST redirect it to the actual running TitleID, otherwise it looks in '.../0000000000000000'.
|
||||
if ((meta.type == SaveDataType::Cache || meta.type == SaveDataType::Temporary) && target_program_id == 0) {
|
||||
target_program_id = system.GetApplicationProcessProgramID();
|
||||
LOG_INFO(Service_FS, "Redirecting generic Cache request (ID 0) to active TitleID: {:016X}", target_program_id);
|
||||
}
|
||||
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, target_program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto out = dir->GetDirectoryRelative(save_directory);
|
||||
|
||||
if (out == nullptr) {
|
||||
LOG_WARNING(Service_FS, "Cache/Save path NOT FOUND: '{}'. Auto-create={}", save_directory, auto_create);
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "Cache/Save path FOUND: '{}'", save_directory);
|
||||
}
|
||||
|
||||
if (out == nullptr && (ShouldSaveDataBeAutomaticallyCreated(space, meta) && auto_create)) {
|
||||
LOG_INFO(Service_FS, "Auto-creating save directory...");
|
||||
return Create(space, meta);
|
||||
}
|
||||
|
||||
if (out != nullptr) {
|
||||
// Some emulators (Ryujinx) or even different firmware versions may rely on the commit
|
||||
// directories /0 or /1 being present for cache or save data.
|
||||
// We prioritizing /1 as it usually implies a newer commit if both exist,
|
||||
// but /0 is what's commonly used by Ryujinx for cache.
|
||||
|
||||
// Ryujinx behavior: If 0 exists and 1 does not, copy 0 to 1.
|
||||
auto dir_0 = out->GetSubdirectory("0");
|
||||
auto dir_1 = out->GetSubdirectory("1");
|
||||
|
||||
if (dir_0) LOG_INFO(Service_FS, "Found subdirectory '0' in save path.");
|
||||
if (dir_1) LOG_INFO(Service_FS, "Found subdirectory '1' in save path.");
|
||||
|
||||
if (dir_0 != nullptr && dir_1 == nullptr) {
|
||||
// User requested removal of auto-copy 0->1 logic.
|
||||
// We simply don't create/copy '1' if it's missing. We rely on fallback to '0'.
|
||||
LOG_INFO(Service_FS, "Ryujinx structure detected: '0' exists, '1' missing. Skipping copy (User Request).");
|
||||
// VfsRawCopyD(dir_0, dir_1); // REMOVED
|
||||
}
|
||||
|
||||
// Check for 'Addressables' and 'Addressables2' and delete 'json.cache' if present.
|
||||
// This is a specific workaround for games (e.g. Just Dance) that freeze if they find old cache metadata.
|
||||
// We force them to regenerate it.
|
||||
const auto CleanCache = [](VirtualDir root) {
|
||||
if (root == nullptr) return;
|
||||
const char* subdirs[] = {"Addressables", "Addressables2"};
|
||||
for (const char* subdir_name : subdirs) {
|
||||
auto subdir = root->GetSubdirectory(subdir_name);
|
||||
if (subdir != nullptr) {
|
||||
if (subdir->DeleteFile("json.cache")) {
|
||||
LOG_INFO(Service_FS, "Deleted stale 'json.cache' in '{}'", subdir_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VirtualDir commit_root = nullptr;
|
||||
if (dir_1 != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using subdirectory '1' as Commit Root.");
|
||||
commit_root = dir_1;
|
||||
} else if (dir_0 != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using subdirectory '0' as Commit Root.");
|
||||
commit_root = dir_0;
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "No '0' or '1' subdirectories found. Using parent folder as Commit Root.");
|
||||
commit_root = out;
|
||||
}
|
||||
|
||||
CleanCache(commit_root);
|
||||
|
||||
// Implement SD_Cache.XXXX logic for Cache Storage
|
||||
if (meta.type == SaveDataType::Cache) {
|
||||
const std::string sd_cache_name = fmt::format("SD_Cache.{:04}", meta.index);
|
||||
auto sd_cache_dir = commit_root->GetSubdirectory(sd_cache_name);
|
||||
if (sd_cache_dir != nullptr) {
|
||||
LOG_INFO(Service_FS, "Found SD_Cache directory: '{}'", sd_cache_name);
|
||||
return sd_cache_dir;
|
||||
} else if (auto_create) {
|
||||
LOG_INFO(Service_FS, "Auto-creating SD_Cache directory: '{}'", sd_cache_name);
|
||||
return commit_root->CreateSubdirectory(sd_cache_name);
|
||||
} else {
|
||||
LOG_WARNING(Service_FS, "SD_Cache directory '{}' not found in commit root.", sd_cache_name);
|
||||
// Fallback? Or return nullptr?
|
||||
// If the user strictly wants SD_Cache, we should probably return nullptr if not found/created.
|
||||
// But for now, returning the commit_root might be safer for legacy compatibility IF SD_Cache isn't strictly enforced for existing saves?
|
||||
// User said "SD_Cache now represents which CacheStorage it will be". This implies correct behavior is to use SD_Cache.
|
||||
// If we return commit_root, it's CacheStorage_0 (conceptually) or just "everything".
|
||||
// Let's assume we return nullptr if not found, to trigger creation logic or error.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return commit_root;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
|
||||
return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
const auto path = GetSaveDataSpaceIdPath(space);
|
||||
// Ensure the directory exists, otherwise FindAllSaves fails.
|
||||
return GetOrCreateDirectoryRelative(dir, path);
|
||||
// return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
@@ -96,12 +173,12 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
return "/system/";
|
||||
case SaveDataSpaceId::User:
|
||||
case SaveDataSpaceId::SdUser:
|
||||
case SaveDataSpaceId::Temporary: // Map into User so we can find the save/ folder
|
||||
return "/user/";
|
||||
case SaveDataSpaceId::Temporary:
|
||||
return "/temp/";
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
|
||||
// ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
LOG_WARNING(Service_FS, "Unrecognized SaveDataSpaceId: {:02X}, defaulting to /user/", static_cast<u8>(space));
|
||||
return "/user/";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +214,9 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
case SaveDataType::Temporary:
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
// Unified Cache/Temporary Path: Always use save/cache/{TitleID}
|
||||
// This simplifies user instructions and avoids UUID/Permission issues.
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
case SaveDataType::Cache:
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
default:
|
||||
@@ -147,6 +225,40 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
}
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto created_dir = dir->CreateDirectoryRelative(save_directory);
|
||||
|
||||
// For Cache storage, enforce the new hierarchy: .../1/SD_Cache.XXXX
|
||||
if (meta.type == SaveDataType::Cache && created_dir != nullptr) {
|
||||
// Ensure commit directory '1' exists
|
||||
auto commit_dir = created_dir->GetSubdirectory("1");
|
||||
if (commit_dir == nullptr) {
|
||||
commit_dir = created_dir->CreateSubdirectory("1");
|
||||
}
|
||||
|
||||
if (commit_dir != nullptr) {
|
||||
// Create SD_Cache.XXXX
|
||||
const std::string sd_cache_name = fmt::format("SD_Cache.{:04}", meta.index);
|
||||
return commit_dir->CreateSubdirectory(sd_cache_name);
|
||||
}
|
||||
}
|
||||
|
||||
return created_dir;
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future) {
|
||||
if (future) {
|
||||
Common::UUID uuid;
|
||||
|
||||
@@ -686,6 +686,16 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
||||
using EdenPath = Common::FS::EdenPath;
|
||||
const auto sdmc_dir_path = Common::FS::GetEdenPath(EdenPath::SDMCDir);
|
||||
const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
|
||||
|
||||
// If the NAND user save location doesn't exist but the SDMC contains
|
||||
// Nintendo/save (common portable save structure), create a host-side
|
||||
// symlink so the emulator will see those saves under the expected NAND path.
|
||||
// This helps users who placed saves under `<eden>/user/sdmc/Nintendo/save/...`.
|
||||
// SDMC to NAND sync logic REMOVED as per user request.
|
||||
// The emulator will no longer attempt to symlink or copy "Nintendo/save" or "SD_Cache.0000"
|
||||
// from SDMC to the NAND user save directory.
|
||||
// Users must ensure their save/cache structure is valid within the NAND directory itself
|
||||
// if that is what they intend to use, or rely on the game creating it.
|
||||
const auto rw_mode = FileSys::OpenMode::ReadWrite;
|
||||
|
||||
auto nand_directory =
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Service::FileSystem {
|
||||
|
||||
ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
|
||||
std::shared_ptr<SaveDataController> save_data_controller_,
|
||||
FileSys::SaveDataSpaceId space)
|
||||
FileSys::SaveDataSpaceId space, bool cache_only)
|
||||
: ServiceFramework{system_, "ISaveDataInfoReader"}, save_data_controller{
|
||||
save_data_controller_} {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -22,7 +22,7 @@ ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
FindAllSaves(space);
|
||||
FindAllSaves(space, cache_only);
|
||||
}
|
||||
|
||||
ISaveDataInfoReader::~ISaveDataInfoReader() = default;
|
||||
@@ -63,7 +63,7 @@ Result ISaveDataInfoReader::ReadSaveDataInfo(
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only) {
|
||||
FileSys::VirtualDir save_root{};
|
||||
const auto result = save_data_controller->OpenSaveDataSpace(&save_root, space);
|
||||
|
||||
@@ -74,8 +74,12 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
|
||||
for (const auto& type : save_root->GetSubdirectories()) {
|
||||
if (type->GetName() == "save") {
|
||||
FindNormalSaves(space, type);
|
||||
} else if (space == FileSys::SaveDataSpaceId::Temporary) {
|
||||
if (cache_only) {
|
||||
FindCacheSaves(space, type);
|
||||
} else {
|
||||
FindNormalSaves(space, type);
|
||||
}
|
||||
} else if (space == FileSys::SaveDataSpaceId::Temporary && !cache_only) {
|
||||
FindTemporaryStorageSaves(space, type);
|
||||
}
|
||||
}
|
||||
@@ -84,6 +88,11 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
for (const auto& save_id : type->GetSubdirectories()) {
|
||||
// Skip cache directory in normal scans
|
||||
if (save_id->GetName() == "cache") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& user_id : save_id->GetSubdirectories()) {
|
||||
// Skip non user id subdirectories
|
||||
if (user_id->GetName().size() != 0x20) {
|
||||
@@ -132,6 +141,96 @@ void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
|
||||
}
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindCacheSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
const auto cache_dir = type->GetSubdirectory("cache");
|
||||
if (cache_dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& title_id_dir : cache_dir->GetSubdirectories()) {
|
||||
const auto title_id = stoull_be(title_id_dir->GetName());
|
||||
// Simple validation: TitleID should be non-zero
|
||||
if (title_id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine commit root (priority to "1", then "0", then self)
|
||||
auto commit_root = title_id_dir->GetSubdirectory("1");
|
||||
if (commit_root == nullptr) {
|
||||
commit_root = title_id_dir->GetSubdirectory("0");
|
||||
}
|
||||
// If neither exists, we might fall back to title_id_dir itself if users put SD_Cache directly there,
|
||||
// but based on SaveDataFactory we expect it inside 0 or 1.
|
||||
if (commit_root == nullptr) {
|
||||
// Check if SD_Cache exists directly in title_dir (legacy/fallback)
|
||||
bool has_sd_cache_root = false;
|
||||
for (const auto& sub : title_id_dir->GetSubdirectories()) {
|
||||
if (sub->GetName().find("SD_Cache.") == 0) {
|
||||
has_sd_cache_root = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_sd_cache_root) {
|
||||
commit_root = title_id_dir;
|
||||
} else {
|
||||
continue; // No valid storage found
|
||||
}
|
||||
}
|
||||
|
||||
bool found_any = false;
|
||||
for (const auto& sd_cache_dir : commit_root->GetSubdirectories()) {
|
||||
const std::string& name = sd_cache_dir->GetName();
|
||||
if (name.find("SD_Cache.") == 0) {
|
||||
// Parse index from "SD_Cache.XXXX"
|
||||
u64 index = 0;
|
||||
try {
|
||||
if (name.size() > 9) {
|
||||
index = std::stoull(name.substr(9));
|
||||
}
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
|
||||
info.emplace_back(SaveDataInfo{
|
||||
0,
|
||||
space,
|
||||
FileSys::SaveDataType::Cache,
|
||||
{}, // padding 0x6
|
||||
{}, // user_id (empty array match)
|
||||
0, // save_id
|
||||
title_id,
|
||||
sd_cache_dir->GetSize(),
|
||||
static_cast<u16>(index), // Correct index with cast
|
||||
FileSys::SaveDataRank::Primary,
|
||||
{}, // padding 0x25
|
||||
});
|
||||
found_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for legacy "flat" cache if no SD_Cache folders found?
|
||||
// If the user specific structure IS enforced, maybe we don't fallback.
|
||||
// But if they have existing cache without the folder, it is effectively index 0.
|
||||
if (!found_any) {
|
||||
// Treat the entire commit_root as index 0 (Legacy behavior)
|
||||
info.emplace_back(SaveDataInfo{
|
||||
0,
|
||||
space,
|
||||
FileSys::SaveDataType::Cache,
|
||||
{}, // padding 0x6
|
||||
{}, // user_id (empty array match)
|
||||
0, // save_id
|
||||
title_id,
|
||||
commit_root->GetSize(),
|
||||
0, // index 0
|
||||
FileSys::SaveDataRank::Primary,
|
||||
{}, // padding 0x25
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
for (const auto& user_id : type->GetSubdirectories()) {
|
||||
|
||||
@@ -16,7 +16,7 @@ class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
|
||||
public:
|
||||
explicit ISaveDataInfoReader(Core::System& system_,
|
||||
std::shared_ptr<SaveDataController> save_data_controller_,
|
||||
FileSys::SaveDataSpaceId space);
|
||||
FileSys::SaveDataSpaceId space, bool cache_only = false);
|
||||
~ISaveDataInfoReader() override;
|
||||
|
||||
struct SaveDataInfo {
|
||||
@@ -38,8 +38,9 @@ public:
|
||||
OutArray<SaveDataInfo, BufferAttr_HipcMapAlias> out_entries);
|
||||
|
||||
private:
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space);
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only);
|
||||
void FindNormalSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
void FindCacheSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
void FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
|
||||
std::shared_ptr<SaveDataController> save_data_controller;
|
||||
|
||||
@@ -353,10 +353,10 @@ Result FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(
|
||||
|
||||
Result FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(
|
||||
OutInterface<ISaveDataInfoReader> out_interface) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
*out_interface = std::make_shared<ISaveDataInfoReader>(system, save_data_controller,
|
||||
FileSys::SaveDataSpaceId::Temporary);
|
||||
FileSys::SaveDataSpaceId::User, true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -42,8 +42,16 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co
|
||||
const auto& message_header = context.GetDomainMessageHeader();
|
||||
const auto object_id = message_header.object_id;
|
||||
|
||||
// Some games send magic numbers in the object_id field, which indicates
|
||||
// this is not actually a proper domain request
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
if (object_id == sfci_magic || object_id == sfco_magic) {
|
||||
// This is not a domain request, treat it as a regular session request
|
||||
return session_handler != nullptr;
|
||||
}
|
||||
|
||||
if (object_id > DomainHandlerCount()) {
|
||||
LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
|
||||
return false;
|
||||
}
|
||||
return !DomainHandler(object_id - 1).expired();
|
||||
@@ -91,7 +99,18 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const auto& domain_message_header = context.GetDomainMessageHeader();
|
||||
const u32 object_id{domain_message_header.object_id};
|
||||
const u32 object_id = domain_message_header.object_id;
|
||||
|
||||
// Some games send magic numbers in the object_id field
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
if (object_id == sfci_magic || object_id == sfco_magic) {
|
||||
// This is not a domain request, handle as regular session request
|
||||
LOG_DEBUG(IPC, "Detected magic number 0x{:08X} in object_id, treating as regular session request. Command={}",
|
||||
object_id, context.GetCommand());
|
||||
return session_handler->HandleSyncRequest(*server_session, context);
|
||||
}
|
||||
|
||||
switch (domain_message_header.command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > this->DomainHandlerCount()) {
|
||||
@@ -200,7 +219,17 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
// If this is an incoming message, only CommandType "Request" has a domain header
|
||||
// All outgoing domain messages have the domain header, if only incoming has it
|
||||
if (incoming || domain_message_header) {
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
// Check if the next value is actually a magic number (SFCI/SFCO)
|
||||
// Some games send these in the object_id field, indicating this is not a domain request
|
||||
const u32 possible_object_id = src_cmdbuf[rp.GetCurrentOffset() + 1]; // object_id is second field
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
|
||||
if (possible_object_id != sfci_magic && possible_object_id != sfco_magic) {
|
||||
// This is a proper domain request
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
}
|
||||
// If it's a magic number, skip reading the domain header entirely
|
||||
} else {
|
||||
if (GetManager()->IsDomain()) {
|
||||
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
|
||||
@@ -220,9 +249,15 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
}
|
||||
|
||||
if (incoming) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
|
||||
// Only check magic if we have a valid data payload header
|
||||
// When domain header is skipped (SFCI in object_id), the structure is different
|
||||
if (domain_message_header) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
|
||||
}
|
||||
} else {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
|
||||
if (domain_message_header) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -923,7 +923,10 @@ std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> messa
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
return Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
LOG_CRITICAL(Service, "SendImpl called: fd={}, size={} bytes, flags={}", fd, message.size(), flags);
|
||||
auto result = Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
LOG_CRITICAL(Service, "SendImpl result: sent={} bytes, errno={}", result.first, static_cast<int>(result.second));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, std::span<const u8> message,
|
||||
|
||||
@@ -60,12 +60,29 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service, "Redirecting NSD host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static std::string ResolveImpl(const std::string& fqdn_in) {
|
||||
// The real implementation makes various substitutions.
|
||||
// For now we just return the string as-is, which is good enough when not
|
||||
// connecting to real Nintendo servers.
|
||||
LOG_WARNING(Service, "(STUBBED) called, fqdn_in={}", fqdn_in);
|
||||
return fqdn_in;
|
||||
|
||||
return GetRedirectedHost(fqdn_in);
|
||||
}
|
||||
|
||||
static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) {
|
||||
|
||||
@@ -29,13 +29,13 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
|
||||
{4, nullptr, "GetHostStringErrorRequest"},
|
||||
{5, &SFDNSRES::GetGaiStringErrorRequest, "GetGaiStringErrorRequest"},
|
||||
{6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"},
|
||||
{7, nullptr, "GetNameInfoRequest"},
|
||||
{8, nullptr, "RequestCancelHandleRequest"},
|
||||
{7, &SFDNSRES::GetNameInfoRequest, "GetNameInfoRequest"},
|
||||
{8, &SFDNSRES::RequestCancelHandleRequest, "RequestCancelHandleRequest"},
|
||||
{9, nullptr, "CancelRequest"},
|
||||
{10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"},
|
||||
{11, nullptr, "GetHostByAddrRequestWithOptions"},
|
||||
{12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"},
|
||||
{13, nullptr, "GetNameInfoRequestWithOptions"},
|
||||
{13, &SFDNSRES::GetNameInfoRequestWithOptions, "GetNameInfoRequestWithOptions"},
|
||||
{14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"},
|
||||
{15, nullptr, "ResolverGetOptionRequest"},
|
||||
};
|
||||
@@ -66,6 +66,21 @@ static bool IsBlockedHost(const std::string& host) {
|
||||
[&host](const std::string& domain) { return host.find(domain) != std::string::npos; });
|
||||
}
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service, "Redirecting host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) {
|
||||
// These combinations have been verified on console (but are not
|
||||
// exhaustive).
|
||||
@@ -163,7 +178,8 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
|
||||
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
std::string host = Common::StringFromBuffer(host_buffer);
|
||||
host = GetRedirectedHost(host);
|
||||
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
|
||||
|
||||
// Prevent resolution of Nintendo servers
|
||||
@@ -281,7 +297,8 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
|
||||
// before looking up.
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
std::string host = Common::StringFromBuffer(host_buffer);
|
||||
host = GetRedirectedHost(host);
|
||||
|
||||
// Prevent resolution of Nintendo servers
|
||||
if (IsBlockedHost(host)) {
|
||||
@@ -364,6 +381,120 @@ void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void SFDNSRES::GetNameInfoRequest(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto addr_in = rp.PopRaw<SockAddrIn>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
const u32 cancel_handle = rp.Pop<u32>();
|
||||
const u64 process_id = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service, "called. flags={}, cancel_handle={}, process_id={}", flags, cancel_handle,
|
||||
process_id);
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
const auto res = Network::GetNameInfo(Translate(addr_in));
|
||||
if (res.second != 0) {
|
||||
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
|
||||
const auto service_error = Translate(network_error);
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = 0,
|
||||
.gai_error = service_error,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& host = res.first;
|
||||
const u32 data_size = static_cast<u32>(host.size() + 1);
|
||||
|
||||
ctx.WriteBuffer(host.data(), data_size, 0);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = GetAddrInfoError::SUCCESS,
|
||||
.netdb_error = NetDbError::Success,
|
||||
.bsd_errno = Errno::SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::GetNameInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
struct InputParameters {
|
||||
u32 flags;
|
||||
u32 interface_index;
|
||||
u64 process_id;
|
||||
u32 padding; // 0x14 + 4 = 0x18? No. 0x14 aligned to 8 bytes?
|
||||
// Wait, sizeof(InputParameters) == 0x14.
|
||||
};
|
||||
// Derived from partial snippets:
|
||||
// u32 flags, u32 interface_index, u64 process_id.
|
||||
// 4 + 4 + 8 = 16 bytes (0x10).
|
||||
// The previous prompt had static_assert size 0x14.
|
||||
// Maybe a u32 padding?
|
||||
|
||||
// Let's rely on standard layout.
|
||||
// I will use manual popping for safety if struct definition is unknown.
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto addr_in = rp.PopRaw<SockAddrIn>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
const u32 interface_index = rp.Pop<u32>();
|
||||
const u64 process_id = rp.Pop<u64>();
|
||||
(void)flags;
|
||||
(void)interface_index;
|
||||
(void)process_id;
|
||||
// If there was padding, it might be implicitly popped or ignored.
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
const auto res = Network::GetNameInfo(Translate(addr_in));
|
||||
if (res.second != 0) {
|
||||
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
|
||||
const auto service_error = Translate(network_error);
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = 0,
|
||||
.gai_error = service_error,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& host = res.first;
|
||||
const u32 data_size = static_cast<u32>(host.size() + 1);
|
||||
|
||||
ctx.WriteBuffer(host.data(), data_size, 0);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = GetAddrInfoError::SUCCESS,
|
||||
.netdb_error = NetDbError::Success,
|
||||
.bsd_errno = Errno::SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
@@ -372,4 +503,24 @@ void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<s32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void SFDNSRES::RequestCancelHandleRequest(HLERequestContext& ctx) {
|
||||
// This is just a stub for now.
|
||||
// In a real implementation this would likely cancel a pending request represented by the handle.
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
struct InputParameters {
|
||||
u32 cancel_handle;
|
||||
};
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto input = rp.PopRaw<InputParameters>();
|
||||
|
||||
LOG_DEBUG(Service, "cancel_handle={}", input.cancel_handle);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(0); // cancel_handle (response seems to echo it or return a new one? usually just an error code or unrelated)
|
||||
// Actually based on typical patterns, it probably returns an error code (bsd_errno).
|
||||
rb.Push<s32>(0); // bsd_errno
|
||||
}
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -22,7 +22,10 @@ private:
|
||||
void GetHostByNameRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequest(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetNameInfoRequest(HLERequestContext& ctx);
|
||||
void GetNameInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void ResolverSetOptionRequest(HLERequestContext& ctx);
|
||||
void RequestCancelHandleRequest(HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -16,10 +16,12 @@ enum class Errno : u32 {
|
||||
SUCCESS = 0,
|
||||
BADF = 9,
|
||||
AGAIN = 11,
|
||||
ACCES = 13,
|
||||
INVAL = 22,
|
||||
MFILE = 24,
|
||||
PIPE = 32,
|
||||
MSGSIZE = 90,
|
||||
ADDRINUSE = 98,
|
||||
CONNABORTED = 103,
|
||||
CONNRESET = 104,
|
||||
NOTCONN = 107,
|
||||
|
||||
@@ -37,6 +37,10 @@ Errno Translate(Network::Errno value) {
|
||||
return Errno::CONNRESET;
|
||||
case Network::Errno::INPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case Network::Errno::ACCES:
|
||||
return Errno::ACCES;
|
||||
case Network::Errno::ADDRINUSE:
|
||||
return Errno::ADDRINUSE;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
|
||||
return Errno::SUCCESS;
|
||||
@@ -261,7 +265,9 @@ PollEvents Translate(Network::PollEvents flags) {
|
||||
Network::SockAddrIn Translate(SockAddrIn value) {
|
||||
// Note: 6 is incorrect, but can be passed by homebrew (because libnx sets
|
||||
// sin_len to 6 when deserializing getaddrinfo results).
|
||||
ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6);
|
||||
if (value.len != 0 && value.len != sizeof(value) && value.len != 6) {
|
||||
LOG_WARNING(Service, "Unexpected SockAddrIn length={}", value.len);
|
||||
}
|
||||
|
||||
return {
|
||||
.family = Translate(static_cast<Domain>(value.family)),
|
||||
|
||||
@@ -117,15 +117,20 @@ public:
|
||||
RegisterHandlers(functions);
|
||||
|
||||
shared_data->connection_count++;
|
||||
LOG_CRITICAL(Service_SSL, "ISslConnection created! Total connections: {}", shared_data->connection_count);
|
||||
}
|
||||
|
||||
~ISslConnection() {
|
||||
shared_data->connection_count--;
|
||||
if (fd_to_close.has_value()) {
|
||||
const s32 fd = *fd_to_close;
|
||||
if (!do_not_close_socket) {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"do_not_close_socket was changed after setting socket; is this right?");
|
||||
if (do_not_close_socket) {
|
||||
// If we aren't supposed to close the socket, but we have an fd_to_close,
|
||||
// that means the configuration changed after we took ownership.
|
||||
// This is weird but we should probably honor the flag.
|
||||
// However, the original valid logic seemed to imply we duped the socket
|
||||
// and should close our dup... but let's stick to what the flag says.
|
||||
LOG_INFO(Service_SSL, "do_not_close_socket is true, skipping close of fd {}", fd);
|
||||
} else {
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
|
||||
if (bsd) {
|
||||
@@ -269,16 +274,17 @@ private:
|
||||
}
|
||||
|
||||
Result PendingImpl(s32* out_pending) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
||||
*out_pending = 0;
|
||||
return ResultSuccess;
|
||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||
return backend->Pending(out_pending);
|
||||
}
|
||||
|
||||
void SetSocketDescriptor(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 in_fd = rp.Pop<s32>();
|
||||
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor called with fd={}", in_fd);
|
||||
s32 out_fd{-1};
|
||||
const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
|
||||
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor result: res={}, out_fd={}", res.raw, out_fd);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res);
|
||||
rb.Push<s32>(out_fd);
|
||||
@@ -308,7 +314,9 @@ private:
|
||||
}
|
||||
|
||||
void DoHandshake(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_SSL, "DoHandshake called, socket={}", socket != nullptr);
|
||||
const Result res = DoHandshakeImpl();
|
||||
LOG_INFO(Service_SSL, "DoHandshake result: {}, did_handshake={}", res.raw, did_handshake);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
||||
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
||||
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
|
||||
virtual Result Pending(s32* out_pending) = 0;
|
||||
};
|
||||
|
||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
|
||||
|
||||
@@ -115,6 +115,22 @@ inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service_SSL, "Redirecting SSL host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
|
||||
@@ -157,7 +173,8 @@ public:
|
||||
socket = std::move(socket_in);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname) override {
|
||||
Result SetHostName(const std::string& hostname_in) override {
|
||||
const std::string hostname = GetRedirectedHost(hostname_in);
|
||||
if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification
|
||||
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
||||
return CheckOpenSSLErrors();
|
||||
@@ -247,6 +264,28 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
if (!ssl) {
|
||||
return ResultInternalError;
|
||||
}
|
||||
int pending = SSL_pending(ssl);
|
||||
if (pending > 0) {
|
||||
*out_pending = pending;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
} else {
|
||||
*out_pending = 0;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
~SSLConnectionBackendOpenSSL() {
|
||||
// this is null-tolerant:
|
||||
SSL_free(ssl);
|
||||
|
||||
@@ -489,6 +489,27 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
*out_pending = static_cast<s32>(cleartext_read_buf.size());
|
||||
if (*out_pending > 0) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (!ciphertext_read_buf.empty()) {
|
||||
*out_pending = 1;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
~SSLConnectionBackendSchannel() {
|
||||
if (handshake_state != HandshakeState::Initial) {
|
||||
DeleteSecurityContext(&ctxt);
|
||||
|
||||
@@ -149,6 +149,26 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
size_t bufferSize = 0;
|
||||
OSStatus status = SSLGetBufferedReadSize(context, &bufferSize);
|
||||
if (status == 0 && bufferSize > 0) {
|
||||
*out_pending = static_cast<s32>(bufferSize);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
} else {
|
||||
*out_pending = 0;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
|
||||
return ReadOrWriteCallback(connection, data, dataLength, true);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "core/internal_network/network_interface.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
|
||||
#include <windows.h>
|
||||
#include <wlanapi.h>
|
||||
#ifdef _MSC_VER
|
||||
|
||||
180
src/core/internal_network/legacy_online.cpp
Normal file
180
src/core/internal_network/legacy_online.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/internal_network/legacy_online.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
LegacyOnlineService::LegacyOnlineService() {
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa_data;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
|
||||
LOG_ERROR(Network, "WSAStartup failed with error: {}", WSAGetLastError());
|
||||
winsock_initialized = false;
|
||||
} else {
|
||||
winsock_initialized = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
LegacyOnlineService::~LegacyOnlineService() {
|
||||
Stop();
|
||||
#ifdef _WIN32
|
||||
if (winsock_initialized) {
|
||||
WSACleanup();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LegacyOnlineService::Start() {
|
||||
if (is_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!winsock_initialized) {
|
||||
LOG_ERROR(Network, "Cannot start Legacy Online Service: Winsock not initialized");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
is_running = true;
|
||||
worker_thread = std::thread(&LegacyOnlineService::ServerLoop, this);
|
||||
}
|
||||
|
||||
void LegacyOnlineService::Stop() {
|
||||
if (!is_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_running = false;
|
||||
|
||||
// Close socket to wake up the thread if it's blocked on recvfrom
|
||||
if (socket_fd != ~0ULL) {
|
||||
#ifdef _WIN32
|
||||
closesocket(static_cast<SOCKET>(socket_fd));
|
||||
#else
|
||||
close(static_cast<int>(socket_fd));
|
||||
#endif
|
||||
socket_fd = ~0ULL;
|
||||
}
|
||||
|
||||
if (worker_thread.joinable()) {
|
||||
worker_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void LegacyOnlineService::ServerLoop() {
|
||||
LOG_INFO(Network, "Starting Legacy Online UDP Server on port {}", PORT);
|
||||
|
||||
// WSAStartup is now handled in the constructor
|
||||
|
||||
|
||||
|
||||
auto s = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
#ifdef _WIN32
|
||||
if (s == INVALID_SOCKET) {
|
||||
#else
|
||||
if (s == -1) {
|
||||
#endif
|
||||
LOG_ERROR(Network, "Failed to create socket");
|
||||
is_running = false;
|
||||
return;
|
||||
}
|
||||
socket_fd = static_cast<uintptr_t>(s);
|
||||
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(static_cast<SOCKET>(socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(static_cast<int>(socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
sockaddr_in server_addr{};
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(PORT);
|
||||
|
||||
int res = -1;
|
||||
#ifdef _WIN32
|
||||
res = bind(static_cast<SOCKET>(socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#else
|
||||
res = bind(static_cast<int>(socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Network, "Failed to bind to port {}: {}", PORT, WSAGetLastError());
|
||||
closesocket(static_cast<SOCKET>(socket_fd));
|
||||
#else
|
||||
LOG_ERROR(Network, "Failed to bind to port {}: {}", PORT, strerror(errno));
|
||||
close(static_cast<int>(socket_fd));
|
||||
#endif
|
||||
socket_fd = ~0ULL;
|
||||
is_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Network, "Legacy Online Server waiting for messages...");
|
||||
|
||||
// Set a timeout for recvfrom so check is_running periodically if not closed via socket
|
||||
// Alternatively, closing the socket (as done in Stop) will cause recvfrom to return error
|
||||
|
||||
char buffer[2048];
|
||||
while (is_running) {
|
||||
sockaddr_in client_addr{};
|
||||
#ifdef _WIN32
|
||||
int client_len = sizeof(client_addr);
|
||||
#else
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
#endif
|
||||
|
||||
int len = -1;
|
||||
#ifdef _WIN32
|
||||
len = recvfrom(static_cast<SOCKET>(socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
|
||||
#else
|
||||
len = recvfrom(static_cast<int>(socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
|
||||
#endif
|
||||
|
||||
if (!is_running) break;
|
||||
|
||||
if (len > 0) {
|
||||
// Send ACK
|
||||
const char* ack_msg = "ACK";
|
||||
#ifdef _WIN32
|
||||
sendto(static_cast<SOCKET>(socket_fd), ack_msg, static_cast<int>(strlen(ack_msg)), 0, (sockaddr*)&client_addr, client_len);
|
||||
#else
|
||||
sendto(static_cast<int>(socket_fd), ack_msg, strlen(ack_msg), 0, (sockaddr*)&client_addr, client_len);
|
||||
#endif
|
||||
} else {
|
||||
// Error or closed
|
||||
// If we closed the socket in Stop(), this will likely trigger.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(socket_fd));
|
||||
// WSACleanup is now handled in the destructor
|
||||
#else
|
||||
if (socket_fd != ~0ULL) close(static_cast<int>(socket_fd));
|
||||
#endif
|
||||
socket_fd = ~0ULL;
|
||||
LOG_INFO(Network, "Legacy Online Server stopped");
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
31
src/core/internal_network/legacy_online.h
Normal file
31
src/core/internal_network/legacy_online.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace Network {
|
||||
|
||||
class LegacyOnlineService {
|
||||
public:
|
||||
LegacyOnlineService();
|
||||
~LegacyOnlineService();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void ServerLoop();
|
||||
|
||||
std::atomic_bool is_running{false};
|
||||
std::thread worker_thread;
|
||||
uintptr_t socket_fd{~0ULL}; // ~0ULL is approx -1 equivalent for unsigned
|
||||
bool winsock_initialized{false};
|
||||
static constexpr int PORT = 6000;
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
@@ -75,6 +75,8 @@ SOCKET GetInterruptSocket() {
|
||||
return interrupt_socket;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -83,6 +85,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
#endif
|
||||
|
||||
switch (static_cast<Domain>(input.family)) {
|
||||
case Domain::Unspecified:
|
||||
case Domain::INET:
|
||||
result.sin_family = AF_INET;
|
||||
break;
|
||||
@@ -105,6 +108,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
LINGER MakeLinger(bool enable, u32 linger_value) {
|
||||
ASSERT(linger_value <= (std::numeric_limits<u_short>::max)());
|
||||
|
||||
@@ -124,6 +129,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
case 0:
|
||||
return Errno::SUCCESS;
|
||||
case WSAEBADF:
|
||||
case WSAENOTSOCK:
|
||||
return Errno::BADF;
|
||||
case WSAEINVAL:
|
||||
return Errno::INVAL;
|
||||
@@ -157,6 +163,10 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
return Errno::TIMEDOUT;
|
||||
case WSAEINPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case WSAEACCES:
|
||||
return Errno::ACCES;
|
||||
case WSAEADDRINUSE:
|
||||
return Errno::ADDRINUSE;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
||||
return Errno::OTHER;
|
||||
@@ -212,6 +222,8 @@ SOCKET GetInterruptSocket() {
|
||||
return interrupt_pipe_fd[0];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -234,6 +246,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
|
||||
return poll(fds, static_cast<nfds_t>(nfds), timeout);
|
||||
}
|
||||
@@ -320,6 +334,8 @@ Errno GetAndLogLastError(CallType call_type = CallType::Other) {
|
||||
return err;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
switch (gai_err) {
|
||||
case 0:
|
||||
@@ -373,6 +389,8 @@ GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Domain TranslateDomainFromNative(int domain) {
|
||||
switch (domain) {
|
||||
case 0:
|
||||
@@ -607,6 +625,8 @@ Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
|
||||
const size_t num = pollfds.size();
|
||||
|
||||
@@ -871,8 +891,13 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
|
||||
}
|
||||
|
||||
Errno Socket::Close() {
|
||||
if (fd == INVALID_SOCKET) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
[[maybe_unused]] const int result = closesocket(fd);
|
||||
ASSERT(result == 0);
|
||||
if (result != 0) {
|
||||
GetAndLogLastError();
|
||||
}
|
||||
fd = INVALID_SOCKET;
|
||||
|
||||
return Errno::SUCCESS;
|
||||
|
||||
@@ -48,6 +48,8 @@ enum class Errno {
|
||||
TIMEDOUT,
|
||||
MSGSIZE,
|
||||
INPROGRESS,
|
||||
ACCES,
|
||||
ADDRINUSE,
|
||||
OTHER,
|
||||
};
|
||||
|
||||
@@ -122,8 +124,33 @@ std::optional<IPv4Address> GetHostIPv4Address();
|
||||
std::string IPv4AddressToString(IPv4Address ip_addr);
|
||||
u32 IPv4AddressToInteger(IPv4Address ip_addr);
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
// named to avoid name collision with Windows macro
|
||||
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
const std::string& host, const std::optional<std::string>& service);
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input);
|
||||
|
||||
inline std::pair<std::string, int> GetNameInfo(const SockAddrIn& addr) {
|
||||
sockaddr sa = TranslateFromSockAddrIn(addr);
|
||||
sockaddr_in addr_in{};
|
||||
std::memcpy(&addr_in, &sa, sizeof(sockaddr_in));
|
||||
|
||||
char host[1025]; // NI_MAXHOST
|
||||
int err = getnameinfo(reinterpret_cast<const sockaddr*>(&addr_in), sizeof(addr_in), host, sizeof(host), nullptr, 0, 0);
|
||||
|
||||
if (err != 0) {
|
||||
return {std::string{}, err};
|
||||
}
|
||||
|
||||
return {std::string(host), 0};
|
||||
}
|
||||
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int err);
|
||||
|
||||
} // namespace Network
|
||||
|
||||
@@ -446,10 +446,12 @@ void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst) {
|
||||
|
||||
void EmitSR_WScaleFactorXY(EmitContext& ctx, IR::Inst& inst) {
|
||||
LOG_WARNING(Shader, "(STUBBED) called");
|
||||
ctx.AddU32("{}=0x3c003c00u;", inst);
|
||||
}
|
||||
|
||||
void EmitSR_WScaleFactorZ(EmitContext& ctx, IR::Inst& inst) {
|
||||
LOG_WARNING(Shader, "(STUBBED) called");
|
||||
ctx.AddU32("{}=0x3f800000u;", inst);
|
||||
}
|
||||
|
||||
void EmitYDirection(EmitContext& ctx, IR::Inst& inst) {
|
||||
|
||||
@@ -58,6 +58,9 @@ std::string FormatFloat(std::string_view value, IR::Type type) {
|
||||
if (value == "nan") {
|
||||
return "utof(0x7fc00000)";
|
||||
}
|
||||
if (value == "-nan") {
|
||||
return "utof(0xffc00000)";
|
||||
}
|
||||
if (value == "inf") {
|
||||
return "utof(0x7f800000)";
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ public:
|
||||
if constexpr (can_async_check) {
|
||||
guard.lock();
|
||||
}
|
||||
if (Settings::IsGPULevelLow() || (Settings::IsGPULevelMedium() && !should_flush)) {
|
||||
// if ((Settings::IsGPULevelLow() || Settings::IsGPULevelMedium()) && !should_flush) {
|
||||
if (false) {
|
||||
func();
|
||||
} else {
|
||||
uncommitted_operations.emplace_back(std::move(func));
|
||||
|
||||
@@ -107,9 +107,19 @@ void Vic::Execute() {
|
||||
auto output_height{config.output_surface_config.out_surface_height + 1};
|
||||
output_surface.resize_destructive(output_width * output_height);
|
||||
|
||||
// Initialize the surface with the appropriate black pixel
|
||||
Pixel black_pixel{};
|
||||
if (config.output_surface_config.out_pixel_format == VideoPixelFormat::Y8__V8U8_N420) {
|
||||
// Y=0, U=512, V=512 (10-bit), A=0
|
||||
black_pixel = {0, 512, 512, 0};
|
||||
} else {
|
||||
// R=0, G=0, B=0, A=0
|
||||
black_pixel = {0, 0, 0, 0};
|
||||
}
|
||||
std::fill(output_surface.begin(), output_surface.end(), black_pixel);
|
||||
|
||||
if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Off) [[unlikely]] {
|
||||
// Fill the frame with black, as otherwise they can have random data and be very glitchy.
|
||||
std::fill(output_surface.begin(), output_surface.end(), Pixel{});
|
||||
|
||||
} else {
|
||||
for (size_t i = 0; i < config.slot_structs.size(); i++) {
|
||||
auto& slot_config{config.slot_structs[i]};
|
||||
@@ -122,7 +132,18 @@ void Vic::Execute() {
|
||||
nvdec_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
||||
}
|
||||
|
||||
if (auto frame = frame_queue.GetFrame(nvdec_id, luma_offset); frame) {
|
||||
auto frame = frame_queue.GetFrame(nvdec_id, luma_offset);
|
||||
if (!frame) {
|
||||
// We might've failed to find the frame, or the nvdec id is stale/wrong.
|
||||
// Try to find the nvdec id again.
|
||||
const s32 new_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
||||
if (new_id != -1) {
|
||||
nvdec_id = new_id;
|
||||
frame = frame_queue.GetFrame(nvdec_id, luma_offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
if (frame.get()) {
|
||||
switch (frame->GetPixelFormat()) {
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
@@ -170,6 +191,7 @@ void Vic::ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const P
|
||||
}
|
||||
|
||||
slot_surface.resize_destructive(out_luma_width * out_luma_height);
|
||||
std::fill(slot_surface.begin(), slot_surface.end(), Pixel{0, 512, 512, 0});
|
||||
|
||||
const auto in_luma_width{(std::min)(frame->GetWidth(), s32(out_luma_width))};
|
||||
const auto in_luma_height{(std::min)(frame->GetHeight(), s32(out_luma_height))};
|
||||
@@ -219,6 +241,7 @@ void Vic::ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span<const Pl
|
||||
const auto out_luma_stride{out_luma_width};
|
||||
|
||||
slot_surface.resize_destructive(out_luma_width * out_luma_height);
|
||||
std::fill(slot_surface.begin(), slot_surface.end(), Pixel{0, 512, 512, 0});
|
||||
|
||||
const auto in_luma_width{(std::min)(frame->GetWidth(), s32(out_luma_width))};
|
||||
[[maybe_unused]] const auto in_luma_height{
|
||||
|
||||
@@ -33,6 +33,8 @@ static OGLProgram LinkSeparableProgram(GLuint shader) {
|
||||
glGetProgramInfoLog(program.handle, log_length, nullptr, log.data());
|
||||
if (link_status == GL_FALSE) {
|
||||
LOG_ERROR(Render_OpenGL, "{}", log);
|
||||
glDeleteProgram(program.handle);
|
||||
program.handle = 0;
|
||||
} else {
|
||||
LOG_WARNING(Render_OpenGL, "{}", log);
|
||||
}
|
||||
|
||||
@@ -116,9 +116,14 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
|
||||
glBindTextureUnit(0, textures[i]);
|
||||
glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
|
||||
matrices[i].data());
|
||||
glProgramUniform2ui(frag.handle, ScreenSizeLocation,
|
||||
static_cast<GLuint>(layout.screen.GetWidth()),
|
||||
static_cast<GLuint>(layout.screen.GetHeight()));
|
||||
if (frag.handle != 0) {
|
||||
const GLint screen_size_loc = glGetUniformLocation(frag.handle, "screen_size");
|
||||
if (screen_size_loc != -1) {
|
||||
glProgramUniform2ui(frag.handle, screen_size_loc,
|
||||
static_cast<GLuint>(layout.screen.GetWidth()),
|
||||
static_cast<GLuint>(layout.screen.GetHeight()));
|
||||
}
|
||||
}
|
||||
glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i]));
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
@@ -277,7 +277,10 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
|
||||
Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
|
||||
bool via_header_index, u32 raw) {
|
||||
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
|
||||
ASSERT(handle.first <= tic_limit);
|
||||
if (handle.first > tic_limit) {
|
||||
LOG_WARNING(Shader, "Texture ID {} is out of bounds (limit {})", handle.first, tic_limit);
|
||||
return {};
|
||||
}
|
||||
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
|
||||
Tegra::Texture::TICEntry entry;
|
||||
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
|
||||
|
||||
Reference in New Issue
Block a user