jdlo modifications
This commit is contained in:
@@ -17,8 +17,6 @@ add_library(core STATIC
|
|||||||
constants.h
|
constants.h
|
||||||
core.cpp
|
core.cpp
|
||||||
core.h
|
core.h
|
||||||
game_settings.cpp
|
|
||||||
game_settings.h
|
|
||||||
core_timing.cpp
|
core_timing.cpp
|
||||||
core_timing.h
|
core_timing.h
|
||||||
cpu_manager.cpp
|
cpu_manager.cpp
|
||||||
@@ -45,7 +43,11 @@ add_library(core STATIC
|
|||||||
device_memory.cpp
|
device_memory.cpp
|
||||||
device_memory.h
|
device_memory.h
|
||||||
device_memory_manager.h
|
device_memory_manager.h
|
||||||
|
device_memory.h
|
||||||
|
device_memory_manager.h
|
||||||
device_memory_manager.inc
|
device_memory_manager.inc
|
||||||
|
internal_network/legacy_online.cpp
|
||||||
|
internal_network/legacy_online.h
|
||||||
file_sys/bis_factory.cpp
|
file_sys/bis_factory.cpp
|
||||||
file_sys/bis_factory.h
|
file_sys/bis_factory.h
|
||||||
file_sys/card_image.cpp
|
file_sys/card_image.cpp
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
#include "core/hle/service/services.h"
|
#include "core/hle/service/services.h"
|
||||||
#include "core/hle/service/set/system_settings_server.h"
|
#include "core/hle/service/set/system_settings_server.h"
|
||||||
#include "core/hle/service/sm/sm.h"
|
#include "core/hle/service/sm/sm.h"
|
||||||
|
#include "core/internal_network/legacy_online.h"
|
||||||
#include "core/internal_network/network.h"
|
#include "core/internal_network/network.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
@@ -137,6 +138,13 @@ struct System::Impl {
|
|||||||
kernel.SetMulticore(is_multicore);
|
kernel.SetMulticore(is_multicore);
|
||||||
cpu_manager.SetMulticore(is_multicore);
|
cpu_manager.SetMulticore(is_multicore);
|
||||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
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) {
|
void ReinitializeIfNecessary(System& system) {
|
||||||
@@ -293,6 +301,48 @@ struct System::Impl {
|
|||||||
return SystemResultStatus::Success;
|
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,
|
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||||
const std::string& filepath,
|
const std::string& filepath,
|
||||||
Service::AM::FrontendAppletParameters& params) {
|
Service::AM::FrontendAppletParameters& params) {
|
||||||
@@ -378,8 +428,7 @@ struct System::Impl {
|
|||||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadOverrides(program_id);
|
||||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
|
||||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
Network::GameInfo game_info;
|
Network::GameInfo game_info;
|
||||||
game_info.name = name;
|
game_info.name = name;
|
||||||
@@ -428,6 +477,11 @@ struct System::Impl {
|
|||||||
stop_event = {};
|
stop_event = {};
|
||||||
Network::RestartSocketOperations();
|
Network::RestartSocketOperations();
|
||||||
|
|
||||||
|
if (legacy_online) {
|
||||||
|
legacy_online->Stop();
|
||||||
|
legacy_online.reset();
|
||||||
|
}
|
||||||
|
|
||||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
Network::GameInfo game_info{};
|
Network::GameInfo game_info{};
|
||||||
room_member->SendGameInfo(game_info);
|
room_member->SendGameInfo(game_info);
|
||||||
@@ -516,6 +570,9 @@ struct System::Impl {
|
|||||||
|
|
||||||
/// Network instance
|
/// Network instance
|
||||||
Network::NetworkInstance network_instance;
|
Network::NetworkInstance network_instance;
|
||||||
|
|
||||||
|
/// Legacy Online Service
|
||||||
|
std::unique_ptr<Network::LegacyOnlineService> legacy_online;
|
||||||
|
|
||||||
/// Debugger
|
/// Debugger
|
||||||
std::unique_ptr<Core::Debugger> debugger;
|
std::unique_ptr<Core::Debugger> debugger;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/file_sys/savedata_factory.h"
|
#include "core/file_sys/savedata_factory.h"
|
||||||
#include "core/file_sys/vfs/vfs.h"
|
#include "core/file_sys/vfs/vfs.h"
|
||||||
|
#include "core/file_sys/sdmc_factory.h"
|
||||||
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
@@ -74,20 +76,97 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
|
|||||||
|
|
||||||
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||||
|
|
||||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
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, target_program_id,
|
||||||
meta.user_id, meta.system_save_data_id);
|
meta.user_id, meta.system_save_data_id);
|
||||||
|
|
||||||
auto out = dir->GetDirectoryRelative(save_directory);
|
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 {
|
||||||
|
LOG_INFO(Service_FS, "Cache/Save path FOUND: '{}'", save_directory);
|
||||||
|
}
|
||||||
|
|
||||||
if (out == nullptr && (ShouldSaveDataBeAutomaticallyCreated(space, meta) && auto_create)) {
|
if (out == nullptr && (ShouldSaveDataBeAutomaticallyCreated(space, meta) && auto_create)) {
|
||||||
|
LOG_INFO(Service_FS, "Auto-creating save directory...");
|
||||||
return Create(space, meta);
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dir_1 != nullptr) {
|
||||||
|
LOG_INFO(Service_FS, "Returning subdirectory '1' as Save Root.");
|
||||||
|
CleanCache(dir_1);
|
||||||
|
return dir_1;
|
||||||
|
}
|
||||||
|
if (dir_0 != nullptr) {
|
||||||
|
LOG_INFO(Service_FS, "Returning subdirectory '0' as Save Root.");
|
||||||
|
CleanCache(dir_0);
|
||||||
|
return dir_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Service_FS, "No '0' or '1' subdirectories found. Returning parent folder as Save Root.");
|
||||||
|
CleanCache(out);
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
|
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) {
|
std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||||
@@ -96,12 +175,12 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
|||||||
return "/system/";
|
return "/system/";
|
||||||
case SaveDataSpaceId::User:
|
case SaveDataSpaceId::User:
|
||||||
case SaveDataSpaceId::SdUser:
|
case SaveDataSpaceId::SdUser:
|
||||||
|
case SaveDataSpaceId::Temporary: // Map into User so we can find the save/ folder
|
||||||
return "/user/";
|
return "/user/";
|
||||||
case SaveDataSpaceId::Temporary:
|
|
||||||
return "/temp/";
|
|
||||||
default:
|
default:
|
||||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
// ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||||
return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
|
LOG_WARNING(Service_FS, "Unrecognized SaveDataSpaceId: {:02X}, defaulting to /user/", static_cast<u8>(space));
|
||||||
|
return "/user/";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +216,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],
|
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||||
title_id);
|
title_id);
|
||||||
case SaveDataType::Temporary:
|
case SaveDataType::Temporary:
|
||||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
// Unified Cache/Temporary Path: Always use save/cache/{TitleID}
|
||||||
title_id);
|
// This simplifies user instructions and avoids UUID/Permission issues.
|
||||||
|
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||||
case SaveDataType::Cache:
|
case SaveDataType::Cache:
|
||||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -686,6 +686,154 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
|||||||
using EdenPath = Common::FS::EdenPath;
|
using EdenPath = Common::FS::EdenPath;
|
||||||
const auto sdmc_dir_path = Common::FS::GetEdenPath(EdenPath::SDMCDir);
|
const auto sdmc_dir_path = Common::FS::GetEdenPath(EdenPath::SDMCDir);
|
||||||
const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
|
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/...`.
|
||||||
|
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 `<eden>/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());
|
||||||
|
}
|
||||||
const auto rw_mode = FileSys::OpenMode::ReadWrite;
|
const auto rw_mode = FileSys::OpenMode::ReadWrite;
|
||||||
|
|
||||||
auto nand_directory =
|
auto nand_directory =
|
||||||
|
|||||||
164
src/core/internal_network/legacy_online.cpp
Normal file
164
src/core/internal_network/legacy_online.cpp
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
// 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() = default;
|
||||||
|
|
||||||
|
LegacyOnlineService::~LegacyOnlineService() {
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LegacyOnlineService::Start() {
|
||||||
|
if (is_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA wsa_data;
|
||||||
|
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
|
||||||
|
LOG_ERROR(Network, "WSAStartup failed");
|
||||||
|
is_running = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
LOG_ERROR(Network, "Failed to bind to port {}", PORT);
|
||||||
|
#ifdef _WIN32
|
||||||
|
closesocket(static_cast<SOCKET>(socket_fd));
|
||||||
|
#else
|
||||||
|
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();
|
||||||
|
#else
|
||||||
|
if (socket_fd != ~0ULL) close(static_cast<int>(socket_fd));
|
||||||
|
#endif
|
||||||
|
socket_fd = ~0ULL;
|
||||||
|
LOG_INFO(Network, "Legacy Online Server stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Network
|
||||||
30
src/core/internal_network/legacy_online.h
Normal file
30
src/core/internal_network/legacy_online.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// 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
|
||||||
|
static constexpr int PORT = 6000;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Network
|
||||||
@@ -107,9 +107,19 @@ void Vic::Execute() {
|
|||||||
auto output_height{config.output_surface_config.out_surface_height + 1};
|
auto output_height{config.output_surface_config.out_surface_height + 1};
|
||||||
output_surface.resize_destructive(output_width * output_height);
|
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]] {
|
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 {
|
} else {
|
||||||
for (size_t i = 0; i < config.slot_structs.size(); i++) {
|
for (size_t i = 0; i < config.slot_structs.size(); i++) {
|
||||||
auto& slot_config{config.slot_structs[i]};
|
auto& slot_config{config.slot_structs[i]};
|
||||||
@@ -122,7 +132,18 @@ void Vic::Execute() {
|
|||||||
nvdec_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
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()) {
|
if (frame.get()) {
|
||||||
switch (frame->GetPixelFormat()) {
|
switch (frame->GetPixelFormat()) {
|
||||||
case AV_PIX_FMT_YUV420P:
|
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);
|
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_width{(std::min)(frame->GetWidth(), s32(out_luma_width))};
|
||||||
const auto in_luma_height{(std::min)(frame->GetHeight(), s32(out_luma_height))};
|
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};
|
const auto out_luma_stride{out_luma_width};
|
||||||
|
|
||||||
slot_surface.resize_destructive(out_luma_width * out_luma_height);
|
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_width{(std::min)(frame->GetWidth(), s32(out_luma_width))};
|
||||||
[[maybe_unused]] const auto in_luma_height{
|
[[maybe_unused]] const auto in_luma_height{
|
||||||
|
|||||||
Reference in New Issue
Block a user