jdlo modifications
This commit is contained in:
@@ -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,11 @@ struct System::Impl {
|
||||
stop_event = {};
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (legacy_online) {
|
||||
legacy_online->Stop();
|
||||
legacy_online.reset();
|
||||
}
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
room_member->SendGameInfo(game_info);
|
||||
@@ -517,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 {
|
||||
|
||||
@@ -74,20 +76,97 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
|
||||
|
||||
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);
|
||||
|
||||
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)) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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 +175,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 +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],
|
||||
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:
|
||||
|
||||
@@ -686,6 +686,154 @@ 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/...`.
|
||||
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;
|
||||
|
||||
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};
|
||||
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{
|
||||
|
||||
Reference in New Issue
Block a user