From 664ff7cc851de70f8565cadf3ded7277726aa3ca Mon Sep 17 00:00:00 2001 From: DieguinDG Date: Mon, 29 Dec 2025 18:47:07 -0300 Subject: [PATCH] 2023 Support --- src/core/core.cpp | 5 +- src/core/file_sys/savedata_factory.cpp | 112 ++++++++----- .../hle/service/filesystem/filesystem.cpp | 148 +----------------- .../fsp/fs_i_save_data_info_reader.cpp | 109 ++++++++++++- .../fsp/fs_i_save_data_info_reader.h | 5 +- .../hle/service/filesystem/fsp/fsp_srv.cpp | 4 +- src/core/hle/service/hle_ipc.cpp | 45 +++++- src/core/hle/service/sockets/bsd.cpp | 5 +- src/core/hle/service/sockets/nsd.cpp | 19 ++- src/core/hle/service/sockets/sfdnsres.cpp | 111 ++++++++++++- src/core/hle/service/sockets/sfdnsres.h | 2 + src/core/hle/service/ssl/ssl.cpp | 5 + .../hle/service/ssl/ssl_backend_openssl.cpp | 19 ++- src/core/internal_network/emu_net_state.cpp | 2 +- src/core/internal_network/legacy_online.cpp | 38 +++-- src/core/internal_network/legacy_online.h | 1 + src/core/internal_network/network.cpp | 14 ++ src/core/internal_network/network.h | 25 +++ 18 files changed, 451 insertions(+), 218 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 527d38410e..10e90aec27 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -478,8 +478,9 @@ struct System::Impl { Network::RestartSocketOperations(); if (legacy_online) { - legacy_online->Stop(); - legacy_online.reset(); + // Keep legacy_online running for the emulator's lifetime + // legacy_online->Stop(); + // legacy_online.reset(); } if (auto room_member = Network::GetRoomMember().lock()) { diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 9e062293b0..7ef96cd8e2 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -57,25 +57,7 @@ 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'. @@ -89,12 +71,6 @@ VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& auto out = dir->GetDirectoryRelative(save_directory); - // Fallback for Ryujinx-style cache paths: sdcard/Nintendo/save/{TitleID} (No /cache/ subdir) - // Also include Temporary, as games like Just Dance use Temporary storage for cache-like data (MapBaseCache), - // and Ryujinx stores this in the same simple 'save/{ID}' structure. - // Fallback logic removed. We now enforce standard paths. - // User instructions will direct them to the correct folder. - if (out == nullptr) { LOG_WARNING(Service_FS, "Cache/Save path NOT FOUND: '{}'. Auto-create={}", save_directory, auto_create); } else { @@ -120,12 +96,10 @@ VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& if (dir_1) LOG_INFO(Service_FS, "Found subdirectory '1' in save path."); if (dir_0 != nullptr && dir_1 == nullptr) { - LOG_INFO(Service_FS, "Ryujinx structure detected: '0' exists, '1' missing. Copying 0->1 for compatibility."); - dir_1 = out->CreateSubdirectory("1"); - if (dir_1 != nullptr) { - // Copy contents from 0 to 1 - VfsRawCopyD(dir_0, dir_1); - } + // 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. @@ -144,19 +118,43 @@ VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& } }; + VirtualDir commit_root = nullptr; if (dir_1 != nullptr) { - LOG_INFO(Service_FS, "Returning subdirectory '1' as Save Root."); - CleanCache(dir_1); - return dir_1; + 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; } - if (dir_0 != nullptr) { - LOG_INFO(Service_FS, "Returning subdirectory '0' as Save Root."); - CleanCache(dir_0); - return dir_0; + + 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; + } } - - LOG_INFO(Service_FS, "No '0' or '1' subdirectories found. Returning parent folder as Save Root."); - CleanCache(out); + + return commit_root; } return out; @@ -227,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; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 3023119020..0767cd1014 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -691,149 +691,11 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove // 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 `/user/sdmc/Nintendo/save/...`. - try { - const auto nand_fs_path = Common::FS::GetEdenPath(EdenPath::NANDDir); - const auto sdmc_fs_path = sdmc_dir_path; - const auto nand_user_save_path = nand_fs_path / "user" / "save"; - const auto sdmc_nintendo_save_path = sdmc_fs_path / "Nintendo" / "save"; - - std::error_code ec; - if (!std::filesystem::exists(nand_user_save_path) && - std::filesystem::exists(sdmc_nintendo_save_path)) { - std::filesystem::create_directory_symlink(sdmc_nintendo_save_path, nand_user_save_path, ec); - if (ec) { - LOG_WARNING(Service_FS, "Could not create symlink {} -> {}: {}", - Common::FS::PathToUTF8String(nand_user_save_path), - Common::FS::PathToUTF8String(sdmc_nintendo_save_path), ec.message()); - - // Fallback: copy the SDMC saves into the NAND save folder so the emulator can see them - try { - if (!std::filesystem::exists(nand_user_save_path)) { - std::filesystem::create_directories(nand_user_save_path); - } - std::filesystem::copy(sdmc_nintendo_save_path, nand_user_save_path, - std::filesystem::copy_options::recursive | - std::filesystem::copy_options::skip_existing, - ec); - if (ec) { - LOG_WARNING(Service_FS, "Failed to copy SDMC saves to {}: {}", - Common::FS::PathToUTF8String(nand_user_save_path), ec.message()); - } else { - LOG_INFO(Service_FS, "Copied SDMC saves to NAND save path: {} -> {}", - Common::FS::PathToUTF8String(sdmc_nintendo_save_path), - Common::FS::PathToUTF8String(nand_user_save_path)); - } - } catch (const std::exception& ex) { - LOG_WARNING(Service_FS, "Exception while copying SDMC saves: {}", ex.what()); - } - } else { - LOG_INFO(Service_FS, "Linked NAND save path to SDMC saves: {} -> {}", - Common::FS::PathToUTF8String(nand_user_save_path), - Common::FS::PathToUTF8String(sdmc_nintendo_save_path)); - } - } - - // Also support official-style SD cache directory name used by some Switch setups. - // If users placed saves or cache under `/user/sdmc/Nintendo/SD_Cache.0000`, - // expose it to the NAND user save path as well so the emulator can find them. - const auto sdmc_nintendo_cache_path = sdmc_fs_path / "Nintendo" / "SD_Cache.0000"; - if (!std::filesystem::exists(nand_user_save_path) && - std::filesystem::exists(sdmc_nintendo_cache_path)) { - std::error_code ec2; - std::filesystem::create_directory_symlink(sdmc_nintendo_cache_path, nand_user_save_path, - ec2); - if (ec2) { - LOG_WARNING(Service_FS, - "Could not create symlink {} -> {}: {}", - Common::FS::PathToUTF8String(nand_user_save_path), - Common::FS::PathToUTF8String(sdmc_nintendo_cache_path), ec2.message()); - - // Fallback: copy the SDMC cache into the NAND save folder so the emulator can see them - try { - if (!std::filesystem::exists(nand_user_save_path)) { - std::filesystem::create_directories(nand_user_save_path); - } - std::filesystem::copy(sdmc_nintendo_cache_path, nand_user_save_path, - std::filesystem::copy_options::recursive | - std::filesystem::copy_options::skip_existing, - ec2); - if (ec2) { - LOG_WARNING(Service_FS, "Failed to copy SDMC cache to {}: {}", - Common::FS::PathToUTF8String(nand_user_save_path), ec2.message()); - } else { - LOG_INFO(Service_FS, "Copied SDMC cache to NAND save path: {} -> {}", - Common::FS::PathToUTF8String(sdmc_nintendo_cache_path), - Common::FS::PathToUTF8String(nand_user_save_path)); - } - } catch (const std::exception& ex) { - LOG_WARNING(Service_FS, "Exception while copying SDMC cache: {}", ex.what()); - } - } else { - LOG_INFO(Service_FS, "Linked NAND save path to SDMC cache: {} -> {}", - Common::FS::PathToUTF8String(nand_user_save_path), - Common::FS::PathToUTF8String(sdmc_nintendo_cache_path)); - } - } - - // If the NAND save folder already exists, ensure individual entries from - // SDMC (both `Nintendo/save` and `Nintendo/SD_Cache.0000`) are visible - // inside it: create per-entry symlinks (with copy fallback) for missing - // title/account directories so saves placed in SDMC are reachable. - auto try_merge_sdmc_entries = [&](const std::filesystem::path& sdmc_src) { - try { - if (!std::filesystem::exists(sdmc_src)) - return; - - std::error_code ec; - if (!std::filesystem::exists(nand_user_save_path)) - std::filesystem::create_directories(nand_user_save_path, ec); - - for (auto& ent : std::filesystem::directory_iterator(sdmc_src)) { - const auto name = ent.path().filename(); - const auto target = nand_user_save_path / name; - if (std::filesystem::exists(target)) - continue; - - std::error_code ec2; - std::filesystem::create_directory_symlink(ent.path(), target, ec2); - if (ec2) { - LOG_WARNING(Service_FS, "Could not create symlink {} -> {}: {}", - Common::FS::PathToUTF8String(target), - Common::FS::PathToUTF8String(ent.path()), ec2.message()); - try { - std::filesystem::copy(ent.path(), target, - std::filesystem::copy_options::recursive | - std::filesystem::copy_options::skip_existing, - ec2); - if (ec2) { - LOG_WARNING(Service_FS, "Failed to copy {} -> {}: {}", - Common::FS::PathToUTF8String(ent.path()), - Common::FS::PathToUTF8String(target), ec2.message()); - } else { - LOG_INFO(Service_FS, "Copied SDMC entry to NAND: {} -> {}", - Common::FS::PathToUTF8String(ent.path()), - Common::FS::PathToUTF8String(target)); - } - } catch (const std::exception& ex) { - LOG_WARNING(Service_FS, "Exception while copying SDMC entry: {}", - ex.what()); - } - } else { - LOG_INFO(Service_FS, "Linked NAND entry to SDMC: {} -> {}", - Common::FS::PathToUTF8String(target), - Common::FS::PathToUTF8String(ent.path())); - } - } - } catch (const std::exception& ex) { - LOG_WARNING(Service_FS, "Exception while merging SDMC entries: {}", ex.what()); - } - }; - - try_merge_sdmc_entries(sdmc_nintendo_save_path); - try_merge_sdmc_entries(sdmc_nintendo_cache_path); - } catch (const std::exception& e) { - LOG_WARNING(Service_FS, "Exception while linking SDMC saves: {}", e.what()); - } + // 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 = diff --git a/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.cpp b/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.cpp index 495b131df4..8b15e21273 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.cpp @@ -14,7 +14,7 @@ namespace Service::FileSystem { ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_, std::shared_ptr 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(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()) { diff --git a/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.h b/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.h index e45ad852ba..a927c7e55c 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.h +++ b/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.h @@ -16,7 +16,7 @@ class ISaveDataInfoReader final : public ServiceFramework { public: explicit ISaveDataInfoReader(Core::System& system_, std::shared_ptr save_data_controller_, - FileSys::SaveDataSpaceId space); + FileSys::SaveDataSpaceId space, bool cache_only = false); ~ISaveDataInfoReader() override; struct SaveDataInfo { @@ -38,8 +38,9 @@ public: OutArray 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 save_data_controller; diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp index 9d59f96973..547f0f4470 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp @@ -353,10 +353,10 @@ Result FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId( Result FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage( OutInterface out_interface) { - LOG_WARNING(Service_FS, "(STUBBED) called"); + LOG_DEBUG(Service_FS, "called"); *out_interface = std::make_shared(system, save_data_controller, - FileSys::SaveDataSpaceId::Temporary); + FileSys::SaveDataSpaceId::User, true); R_SUCCEED(); } diff --git a/src/core/hle/service/hle_ipc.cpp b/src/core/hle/service/hle_ipc.cpp index e0367e774c..0b9597f3a5 100644 --- a/src/core/hle/service/hle_ipc.cpp +++ b/src/core/hle/service/hle_ipc.cpp @@ -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(); + // 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(); + } + // 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')); + } } } diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index f58b0760aa..87d5dfab52 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -923,7 +923,10 @@ std::pair BSD::SendImpl(s32 fd, u32 flags, std::span 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(result.second)); + return result; } std::pair BSD::SendToImpl(s32 fd, u32 flags, std::span message, diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 491b76d481..6ffa97e1c3 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -60,12 +60,29 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na RegisterHandlers(functions); } + +static const std::vector> 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& fqdn_out) { diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 68d73f0a59..04a42b7197 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -30,12 +30,12 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres" {5, &SFDNSRES::GetGaiStringErrorRequest, "GetGaiStringErrorRequest"}, {6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"}, {7, nullptr, "GetNameInfoRequest"}, - {8, nullptr, "RequestCancelHandleRequest"}, + {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> 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 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 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,72 @@ void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) { }); } + +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(); + const u32 flags = rp.Pop(); + const u32 interface_index = rp.Pop(); + const u64 process_id = rp.Pop(); + (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(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 +455,24 @@ void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) { rb.Push(ResultSuccess); rb.Push(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(); + + LOG_DEBUG(Service, "cancel_handle={}", input.cancel_handle); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(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(0); // bsd_errno +} } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index 282ef9071f..d853037853 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -22,7 +22,9 @@ private: void GetHostByNameRequestWithOptions(HLERequestContext& ctx); void GetAddrInfoRequest(HLERequestContext& ctx); void GetAddrInfoRequestWithOptions(HLERequestContext& ctx); + void GetNameInfoRequestWithOptions(HLERequestContext& ctx); void ResolverSetOptionRequest(HLERequestContext& ctx); + void RequestCancelHandleRequest(HLERequestContext& ctx); }; } // namespace Service::Sockets diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 5a1ae0dbc9..ca7c544ebd 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -117,6 +117,7 @@ public: RegisterHandlers(functions); shared_data->connection_count++; + LOG_CRITICAL(Service_SSL, "ISslConnection created! Total connections: {}", shared_data->connection_count); } ~ISslConnection() { @@ -277,8 +278,10 @@ private: void SetSocketDescriptor(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 in_fd = rp.Pop(); + 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(out_fd); @@ -308,7 +311,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); } diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index 02d0b8fba7..927df71680 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -115,6 +115,22 @@ inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size) } #endif + +static const std::vector> 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(); diff --git a/src/core/internal_network/emu_net_state.cpp b/src/core/internal_network/emu_net_state.cpp index d6d1a70a60..916a5c4621 100644 --- a/src/core/internal_network/emu_net_state.cpp +++ b/src/core/internal_network/emu_net_state.cpp @@ -7,7 +7,7 @@ #include "core/internal_network/network_interface.h" #ifdef _WIN32 -#define NOMINMAX + #include #include #ifdef _MSC_VER diff --git a/src/core/internal_network/legacy_online.cpp b/src/core/internal_network/legacy_online.cpp index 28bea3e676..76670e46f1 100644 --- a/src/core/internal_network/legacy_online.cpp +++ b/src/core/internal_network/legacy_online.cpp @@ -19,10 +19,25 @@ namespace Network { -LegacyOnlineService::LegacyOnlineService() = default; +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() { @@ -30,6 +45,13 @@ void LegacyOnlineService::Start() { 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); } @@ -59,14 +81,7 @@ void LegacyOnlineService::Stop() { void LegacyOnlineService::ServerLoop() { LOG_INFO(Network, "Starting Legacy Online UDP Server on port {}", PORT); -#ifdef _WIN32 - WSADATA wsa_data; - if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) { - LOG_ERROR(Network, "WSAStartup failed"); - is_running = false; - return; - } -#endif + // WSAStartup is now handled in the constructor @@ -102,10 +117,11 @@ void LegacyOnlineService::ServerLoop() { #endif if (res < 0) { - LOG_ERROR(Network, "Failed to bind to port {}", PORT); #ifdef _WIN32 + LOG_ERROR(Network, "Failed to bind to port {}: {}", PORT, WSAGetLastError()); closesocket(static_cast(socket_fd)); #else + LOG_ERROR(Network, "Failed to bind to port {}: {}", PORT, strerror(errno)); close(static_cast(socket_fd)); #endif socket_fd = ~0ULL; @@ -153,7 +169,7 @@ void LegacyOnlineService::ServerLoop() { #ifdef _WIN32 if (socket_fd != ~0ULL) closesocket(static_cast(socket_fd)); - WSACleanup(); + // WSACleanup is now handled in the destructor #else if (socket_fd != ~0ULL) close(static_cast(socket_fd)); #endif diff --git a/src/core/internal_network/legacy_online.h b/src/core/internal_network/legacy_online.h index fdeba8821a..33608bc8f8 100644 --- a/src/core/internal_network/legacy_online.h +++ b/src/core/internal_network/legacy_online.h @@ -24,6 +24,7 @@ private: 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; }; diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 35f2157761..f6aa336d4a 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -75,6 +75,8 @@ SOCKET GetInterruptSocket() { return interrupt_socket; } +} // namespace + sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr_in result; @@ -105,6 +107,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) { return addr; } +namespace { + LINGER MakeLinger(bool enable, u32 linger_value) { ASSERT(linger_value <= (std::numeric_limits::max)()); @@ -212,6 +216,8 @@ SOCKET GetInterruptSocket() { return interrupt_pipe_fd[0]; } +} // namespace + sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr_in result; @@ -234,6 +240,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) { return addr; } +namespace { + int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) { return poll(fds, static_cast(nfds), timeout); } @@ -320,6 +328,8 @@ Errno GetAndLogLastError(CallType call_type = CallType::Other) { return err; } +} // namespace + GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) { switch (gai_err) { case 0: @@ -373,6 +383,8 @@ GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) { } } +namespace { + Domain TranslateDomainFromNative(int domain) { switch (domain) { case 0: @@ -607,6 +619,8 @@ Common::Expected, GetAddrInfoError> GetAddressInfo( return ret; } + + std::pair Poll(std::vector& pollfds, s32 timeout) { const size_t num = pollfds.size(); diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index 5df28911f8..2dc612de71 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h @@ -122,8 +122,33 @@ std::optional GetHostIPv4Address(); std::string IPv4AddressToString(IPv4Address ip_addr); u32 IPv4AddressToInteger(IPv4Address ip_addr); +#ifdef _WIN32 +#include +#else +#include +#endif + // named to avoid name collision with Windows macro Common::Expected, GetAddrInfoError> GetAddressInfo( const std::string& host, const std::optional& service); +sockaddr TranslateFromSockAddrIn(SockAddrIn input); + +inline std::pair 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(&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