Compare commits
11 Commits
video-core
...
jdlo-eden-
| Author | SHA1 | Date | |
|---|---|---|---|
| a71a82d3b7 | |||
| bd1d270e97 | |||
| 664ff7cc85 | |||
| b5c86787ab | |||
|
|
cfae726289 | ||
|
|
bb94cff886 | ||
|
|
370997f42e | ||
|
|
5213cc5689 | ||
|
|
bc9af86269 | ||
|
|
00ec67d65b | ||
|
|
fbd28a9d34 |
@@ -96,8 +96,8 @@
|
||||
"package": "VVL",
|
||||
"repo": "KhronosGroup/Vulkan-ValidationLayers",
|
||||
"tag": "vulkan-sdk-%VERSION%",
|
||||
"git_version": "1.4.328.1",
|
||||
"git_version": "1.4.335.0",
|
||||
"artifact": "android-binaries-%VERSION%.zip",
|
||||
"hash": "5ec895a453cb7c2f156830b9766953a0c2bd44dea99e6a3dac4160305041ccd3e87534b4ce0bd102392178d2a8eca48411856298f9395e60117cdfe89f72137e"
|
||||
"hash": "48167c4a17736301bd08f9290f41830443e1f18cce8ad867fc6f289b49e18b40e93c9850b377951af82f51b5b6d7313aa6a884fc5df79f5ce3df82696c1c1244"
|
||||
}
|
||||
}
|
||||
|
||||
4
externals/cpmfile.json
vendored
4
externals/cpmfile.json
vendored
@@ -119,10 +119,10 @@
|
||||
"package": "VulkanUtilityLibraries",
|
||||
"repo": "scripts/VulkanUtilityHeaders",
|
||||
"tag": "%VERSION%",
|
||||
"git_version": "1.4.328",
|
||||
"git_version": "1.4.335",
|
||||
"artifact": "VulkanUtilityHeaders.tar.zst",
|
||||
"git_host": "git.crueter.xyz",
|
||||
"hash": "9922217b39faf73cd4fc1510f2fdba14a49aa5c0d77f9ee24ee0512cef16b234d0cabc83c1fec861fa5df1d43e7f086ca9b6501753899119f39c5ca530cb0dae"
|
||||
"hash": "16dac0e6586702580c4279e4cd37ffe3cf909c93eb31b5069da7af36436d47b270a9cbaac953bb66c22ed12ed67ffa096688599267f307dfb62be1bc09f79833"
|
||||
},
|
||||
"spirv-tools": {
|
||||
"package": "SPIRV-Tools",
|
||||
|
||||
1
externals/ffmpeg/CMakeLists.txt
vendored
1
externals/ffmpeg/CMakeLists.txt
vendored
@@ -217,7 +217,6 @@ else()
|
||||
--disable-ffmpeg
|
||||
--disable-ffprobe
|
||||
--disable-network
|
||||
--disable-postproc
|
||||
--disable-swresample
|
||||
--enable-decoder=h264
|
||||
--enable-decoder=vp8
|
||||
|
||||
@@ -18,6 +18,7 @@ plugins {
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
||||
id("com.github.triplet.play") version "3.8.6"
|
||||
id("idea")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,6 +28,8 @@ plugins {
|
||||
*/
|
||||
val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toInt()
|
||||
|
||||
val edenDir = project(":Eden").projectDir
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
android {
|
||||
namespace = "org.yuzu.yuzu_emu"
|
||||
@@ -36,7 +39,6 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -241,11 +243,17 @@ android {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.22.1"
|
||||
path = file("../../../CMakeLists.txt")
|
||||
path = file("${edenDir}/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
// Inclusion to exclude build/ dir from non-Android
|
||||
excludeDirs.add(file("${edenDir}/build"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Delete>("ktlintReset", fun Delete.() {
|
||||
delete(File(layout.buildDirectory.toString() + File.separator + "intermediates/ktLint"))
|
||||
@@ -346,7 +354,7 @@ fun getGitVersion(): String {
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val artifactsDir = layout.projectDirectory.dir("../../../artifacts")
|
||||
val artifactsDir = layout.projectDirectory.dir("${edenDir}/artifacts")
|
||||
val outputsDir = layout.buildDirectory.dir("outputs").get()
|
||||
|
||||
android.applicationVariants.forEach { variant ->
|
||||
|
||||
@@ -14,6 +14,8 @@ android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
|
||||
# Android Gradle plugin 8.0.2
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
android.native.buildOutput=verbose
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -19,3 +22,6 @@ dependencyResolutionManagement {
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
||||
include("Eden")
|
||||
project(":Eden").projectDir = file("../..")
|
||||
|
||||
@@ -34,8 +34,8 @@ struct Member {
|
||||
struct RoomInformation {
|
||||
std::string name; ///< Name of the server
|
||||
std::string description; ///< Server description
|
||||
u32 member_slots; ///< Maximum number of members in this room
|
||||
u16 port; ///< The port of this room
|
||||
u32 member_slots{}; ///< Maximum number of members in this room
|
||||
u16 port{}; ///< The port of this room
|
||||
GameInfo preferred_game; ///< Game to advertise that you want to play
|
||||
std::string host_username; ///< Forum username of the host
|
||||
};
|
||||
@@ -46,8 +46,8 @@ struct Room {
|
||||
std::string id;
|
||||
std::string verify_uid; ///< UID used for verification
|
||||
std::string ip;
|
||||
u32 net_version;
|
||||
bool has_password;
|
||||
u32 net_version{};
|
||||
bool has_password = false;
|
||||
|
||||
std::vector<Member> members;
|
||||
};
|
||||
|
||||
@@ -17,8 +17,6 @@ add_library(core STATIC
|
||||
constants.h
|
||||
core.cpp
|
||||
core.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
cpu_manager.cpp
|
||||
@@ -45,7 +43,11 @@ add_library(core STATIC
|
||||
device_memory.cpp
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
device_memory_manager.inc
|
||||
internal_network/legacy_online.cpp
|
||||
internal_network/legacy_online.h
|
||||
file_sys/bis_factory.cpp
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "core/hle/service/services.h"
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/internal_network/legacy_online.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
@@ -137,6 +138,13 @@ struct System::Impl {
|
||||
kernel.SetMulticore(is_multicore);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
|
||||
if (!legacy_online) {
|
||||
legacy_online = std::make_unique<Network::LegacyOnlineService>();
|
||||
legacy_online->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void ReinitializeIfNecessary(System& system) {
|
||||
@@ -293,6 +301,48 @@ struct System::Impl {
|
||||
return SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
void LoadOverrides(u64 programId) const {
|
||||
std::string vendor = gpu_core->Renderer().GetDeviceVendor();
|
||||
LOG_INFO(Core, "GPU Vendor: {}", vendor);
|
||||
|
||||
// Reset all per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
|
||||
// Insert PC overrides here
|
||||
|
||||
#ifdef ANDROID
|
||||
// Example on how to set a setting based on the program ID and vendor
|
||||
if (programId == 0x010028600EBDA000 && vendor == "Mali") { // Mario 3d World
|
||||
// Settings::values.example = true;
|
||||
}
|
||||
|
||||
// Example array of program IDs
|
||||
const std::array<u64, 10> example_array = {
|
||||
//0xprogramId
|
||||
0x0004000000033400, // Game 1
|
||||
0x0004000000033500 // Game 2
|
||||
// And so on
|
||||
};
|
||||
|
||||
for (auto id : example_array) {
|
||||
if (programId == id) {
|
||||
// Settings::values.example = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Ninja Gaiden Ragebound
|
||||
constexpr u64 ngr = 0x0100781020710000ULL;
|
||||
|
||||
if (programId == ngr) {
|
||||
LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend");
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
}
|
||||
}
|
||||
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath,
|
||||
Service::AM::FrontendAppletParameters& params) {
|
||||
@@ -378,8 +428,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
LoadOverrides(program_id);
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
@@ -428,6 +477,12 @@ struct System::Impl {
|
||||
stop_event = {};
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (legacy_online) {
|
||||
// Keep legacy_online running for the emulator's lifetime
|
||||
// legacy_online->Stop();
|
||||
// legacy_online.reset();
|
||||
}
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
room_member->SendGameInfo(game_info);
|
||||
@@ -516,6 +571,9 @@ struct System::Impl {
|
||||
|
||||
/// Network instance
|
||||
Network::NetworkInstance network_instance;
|
||||
|
||||
/// Legacy Online Service
|
||||
std::unique_ptr<Network::LegacyOnlineService> legacy_online;
|
||||
|
||||
/// Debugger
|
||||
std::unique_ptr<Core::Debugger> debugger;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -55,39 +57,114 @@ std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
return dir->CreateDirectoryRelative(save_directory);
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
u64 target_program_id = meta.program_id;
|
||||
// CRITICAL FIX: If the game requests Cache/Temp with ProgramID 0 (generic),
|
||||
// we MUST redirect it to the actual running TitleID, otherwise it looks in '.../0000000000000000'.
|
||||
if ((meta.type == SaveDataType::Cache || meta.type == SaveDataType::Temporary) && target_program_id == 0) {
|
||||
target_program_id = system.GetApplicationProcessProgramID();
|
||||
LOG_INFO(Service_FS, "Redirecting generic Cache request (ID 0) to active TitleID: {:016X}", target_program_id);
|
||||
}
|
||||
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, target_program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto out = dir->GetDirectoryRelative(save_directory);
|
||||
|
||||
if (out == nullptr) {
|
||||
LOG_WARNING(Service_FS, "Cache/Save path NOT FOUND: '{}'. Auto-create={}", save_directory, auto_create);
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "Cache/Save path FOUND: '{}'", save_directory);
|
||||
}
|
||||
|
||||
if (out == nullptr && (ShouldSaveDataBeAutomaticallyCreated(space, meta) && auto_create)) {
|
||||
LOG_INFO(Service_FS, "Auto-creating save directory...");
|
||||
return Create(space, meta);
|
||||
}
|
||||
|
||||
if (out != nullptr) {
|
||||
// Some emulators (Ryujinx) or even different firmware versions may rely on the commit
|
||||
// directories /0 or /1 being present for cache or save data.
|
||||
// We prioritizing /1 as it usually implies a newer commit if both exist,
|
||||
// but /0 is what's commonly used by Ryujinx for cache.
|
||||
|
||||
// Ryujinx behavior: If 0 exists and 1 does not, copy 0 to 1.
|
||||
auto dir_0 = out->GetSubdirectory("0");
|
||||
auto dir_1 = out->GetSubdirectory("1");
|
||||
|
||||
if (dir_0) LOG_INFO(Service_FS, "Found subdirectory '0' in save path.");
|
||||
if (dir_1) LOG_INFO(Service_FS, "Found subdirectory '1' in save path.");
|
||||
|
||||
if (dir_0 != nullptr && dir_1 == nullptr) {
|
||||
// User requested removal of auto-copy 0->1 logic.
|
||||
// We simply don't create/copy '1' if it's missing. We rely on fallback to '0'.
|
||||
LOG_INFO(Service_FS, "Ryujinx structure detected: '0' exists, '1' missing. Skipping copy (User Request).");
|
||||
// VfsRawCopyD(dir_0, dir_1); // REMOVED
|
||||
}
|
||||
|
||||
// Check for 'Addressables' and 'Addressables2' and delete 'json.cache' if present.
|
||||
// This is a specific workaround for games (e.g. Just Dance) that freeze if they find old cache metadata.
|
||||
// We force them to regenerate it.
|
||||
const auto CleanCache = [](VirtualDir root) {
|
||||
if (root == nullptr) return;
|
||||
const char* subdirs[] = {"Addressables", "Addressables2"};
|
||||
for (const char* subdir_name : subdirs) {
|
||||
auto subdir = root->GetSubdirectory(subdir_name);
|
||||
if (subdir != nullptr) {
|
||||
if (subdir->DeleteFile("json.cache")) {
|
||||
LOG_INFO(Service_FS, "Deleted stale 'json.cache' in '{}'", subdir_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VirtualDir commit_root = nullptr;
|
||||
if (dir_1 != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using subdirectory '1' as Commit Root.");
|
||||
commit_root = dir_1;
|
||||
} else if (dir_0 != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using subdirectory '0' as Commit Root.");
|
||||
commit_root = dir_0;
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "No '0' or '1' subdirectories found. Using parent folder as Commit Root.");
|
||||
commit_root = out;
|
||||
}
|
||||
|
||||
CleanCache(commit_root);
|
||||
|
||||
// Implement SD_Cache.XXXX logic for Cache Storage
|
||||
if (meta.type == SaveDataType::Cache) {
|
||||
const std::string sd_cache_name = fmt::format("SD_Cache.{:04}", meta.index);
|
||||
auto sd_cache_dir = commit_root->GetSubdirectory(sd_cache_name);
|
||||
if (sd_cache_dir != nullptr) {
|
||||
LOG_INFO(Service_FS, "Found SD_Cache directory: '{}'", sd_cache_name);
|
||||
return sd_cache_dir;
|
||||
} else if (auto_create) {
|
||||
LOG_INFO(Service_FS, "Auto-creating SD_Cache directory: '{}'", sd_cache_name);
|
||||
return commit_root->CreateSubdirectory(sd_cache_name);
|
||||
} else {
|
||||
LOG_WARNING(Service_FS, "SD_Cache directory '{}' not found in commit root.", sd_cache_name);
|
||||
// Fallback? Or return nullptr?
|
||||
// If the user strictly wants SD_Cache, we should probably return nullptr if not found/created.
|
||||
// But for now, returning the commit_root might be safer for legacy compatibility IF SD_Cache isn't strictly enforced for existing saves?
|
||||
// User said "SD_Cache now represents which CacheStorage it will be". This implies correct behavior is to use SD_Cache.
|
||||
// If we return commit_root, it's CacheStorage_0 (conceptually) or just "everything".
|
||||
// Let's assume we return nullptr if not found, to trigger creation logic or error.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return commit_root;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
|
||||
return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
const auto path = GetSaveDataSpaceIdPath(space);
|
||||
// Ensure the directory exists, otherwise FindAllSaves fails.
|
||||
return GetOrCreateDirectoryRelative(dir, path);
|
||||
// return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
@@ -96,12 +173,12 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
return "/system/";
|
||||
case SaveDataSpaceId::User:
|
||||
case SaveDataSpaceId::SdUser:
|
||||
case SaveDataSpaceId::Temporary: // Map into User so we can find the save/ folder
|
||||
return "/user/";
|
||||
case SaveDataSpaceId::Temporary:
|
||||
return "/temp/";
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
|
||||
// ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
LOG_WARNING(Service_FS, "Unrecognized SaveDataSpaceId: {:02X}, defaulting to /user/", static_cast<u8>(space));
|
||||
return "/user/";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +214,9 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
case SaveDataType::Temporary:
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
// Unified Cache/Temporary Path: Always use save/cache/{TitleID}
|
||||
// This simplifies user instructions and avoids UUID/Permission issues.
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
case SaveDataType::Cache:
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
default:
|
||||
@@ -147,6 +225,40 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
}
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto created_dir = dir->CreateDirectoryRelative(save_directory);
|
||||
|
||||
// For Cache storage, enforce the new hierarchy: .../1/SD_Cache.XXXX
|
||||
if (meta.type == SaveDataType::Cache && created_dir != nullptr) {
|
||||
// Ensure commit directory '1' exists
|
||||
auto commit_dir = created_dir->GetSubdirectory("1");
|
||||
if (commit_dir == nullptr) {
|
||||
commit_dir = created_dir->CreateSubdirectory("1");
|
||||
}
|
||||
|
||||
if (commit_dir != nullptr) {
|
||||
// Create SD_Cache.XXXX
|
||||
const std::string sd_cache_name = fmt::format("SD_Cache.{:04}", meta.index);
|
||||
return commit_dir->CreateSubdirectory(sd_cache_name);
|
||||
}
|
||||
}
|
||||
|
||||
return created_dir;
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future) {
|
||||
if (future) {
|
||||
Common::UUID uuid;
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Core::Frontend {
|
||||
struct CabinetParameters {
|
||||
Service::NFP::TagInfo tag_info;
|
||||
Service::NFP::RegisterInfo register_info;
|
||||
Service::NFP::CabinetMode mode;
|
||||
Service::NFP::CabinetMode mode{};
|
||||
};
|
||||
|
||||
using CabinetCallback = std::function<void(bool, const std::string&)>;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -20,9 +23,9 @@ struct KeyboardInitializeParameters {
|
||||
std::u16string initial_text;
|
||||
char16_t left_optional_symbol_key;
|
||||
char16_t right_optional_symbol_key;
|
||||
u32 max_text_length;
|
||||
u32 min_text_length;
|
||||
s32 initial_cursor_position;
|
||||
u32 max_text_length{};
|
||||
u32 min_text_length{};
|
||||
s32 initial_cursor_position{};
|
||||
Service::AM::Frontend::SwkbdType type;
|
||||
Service::AM::Frontend::SwkbdPasswordMode password_mode;
|
||||
Service::AM::Frontend::SwkbdTextDrawType text_draw_type;
|
||||
@@ -34,12 +37,12 @@ struct KeyboardInitializeParameters {
|
||||
};
|
||||
|
||||
struct InlineAppearParameters {
|
||||
u32 max_text_length;
|
||||
u32 min_text_length;
|
||||
f32 key_top_scale_x;
|
||||
f32 key_top_scale_y;
|
||||
f32 key_top_translate_x;
|
||||
f32 key_top_translate_y;
|
||||
u32 max_text_length{};
|
||||
u32 min_text_length{};
|
||||
f32 key_top_scale_x{};
|
||||
f32 key_top_scale_y{};
|
||||
f32 key_top_translate_x{};
|
||||
f32 key_top_translate_y{};
|
||||
Service::AM::Frontend::SwkbdType type;
|
||||
Service::AM::Frontend::SwkbdKeyDisableFlags key_disable_flags;
|
||||
bool key_top_as_floating;
|
||||
@@ -50,7 +53,7 @@ struct InlineAppearParameters {
|
||||
|
||||
struct InlineTextParameters {
|
||||
std::u16string input_text;
|
||||
s32 cursor_position;
|
||||
s32 cursor_position{};
|
||||
};
|
||||
|
||||
class SoftwareKeyboardApplet : public Applet {
|
||||
|
||||
@@ -686,6 +686,16 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
||||
using EdenPath = Common::FS::EdenPath;
|
||||
const auto sdmc_dir_path = Common::FS::GetEdenPath(EdenPath::SDMCDir);
|
||||
const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
|
||||
|
||||
// If the NAND user save location doesn't exist but the SDMC contains
|
||||
// Nintendo/save (common portable save structure), create a host-side
|
||||
// symlink so the emulator will see those saves under the expected NAND path.
|
||||
// This helps users who placed saves under `<eden>/user/sdmc/Nintendo/save/...`.
|
||||
// SDMC to NAND sync logic REMOVED as per user request.
|
||||
// The emulator will no longer attempt to symlink or copy "Nintendo/save" or "SD_Cache.0000"
|
||||
// from SDMC to the NAND user save directory.
|
||||
// Users must ensure their save/cache structure is valid within the NAND directory itself
|
||||
// if that is what they intend to use, or rely on the game creating it.
|
||||
const auto rw_mode = FileSys::OpenMode::ReadWrite;
|
||||
|
||||
auto nand_directory =
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Service::FileSystem {
|
||||
|
||||
ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
|
||||
std::shared_ptr<SaveDataController> save_data_controller_,
|
||||
FileSys::SaveDataSpaceId space)
|
||||
FileSys::SaveDataSpaceId space, bool cache_only)
|
||||
: ServiceFramework{system_, "ISaveDataInfoReader"}, save_data_controller{
|
||||
save_data_controller_} {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -22,7 +22,7 @@ ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
FindAllSaves(space);
|
||||
FindAllSaves(space, cache_only);
|
||||
}
|
||||
|
||||
ISaveDataInfoReader::~ISaveDataInfoReader() = default;
|
||||
@@ -63,7 +63,7 @@ Result ISaveDataInfoReader::ReadSaveDataInfo(
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only) {
|
||||
FileSys::VirtualDir save_root{};
|
||||
const auto result = save_data_controller->OpenSaveDataSpace(&save_root, space);
|
||||
|
||||
@@ -74,8 +74,12 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
|
||||
for (const auto& type : save_root->GetSubdirectories()) {
|
||||
if (type->GetName() == "save") {
|
||||
FindNormalSaves(space, type);
|
||||
} else if (space == FileSys::SaveDataSpaceId::Temporary) {
|
||||
if (cache_only) {
|
||||
FindCacheSaves(space, type);
|
||||
} else {
|
||||
FindNormalSaves(space, type);
|
||||
}
|
||||
} else if (space == FileSys::SaveDataSpaceId::Temporary && !cache_only) {
|
||||
FindTemporaryStorageSaves(space, type);
|
||||
}
|
||||
}
|
||||
@@ -84,6 +88,11 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
for (const auto& save_id : type->GetSubdirectories()) {
|
||||
// Skip cache directory in normal scans
|
||||
if (save_id->GetName() == "cache") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& user_id : save_id->GetSubdirectories()) {
|
||||
// Skip non user id subdirectories
|
||||
if (user_id->GetName().size() != 0x20) {
|
||||
@@ -132,6 +141,96 @@ void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
|
||||
}
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindCacheSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
const auto cache_dir = type->GetSubdirectory("cache");
|
||||
if (cache_dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& title_id_dir : cache_dir->GetSubdirectories()) {
|
||||
const auto title_id = stoull_be(title_id_dir->GetName());
|
||||
// Simple validation: TitleID should be non-zero
|
||||
if (title_id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine commit root (priority to "1", then "0", then self)
|
||||
auto commit_root = title_id_dir->GetSubdirectory("1");
|
||||
if (commit_root == nullptr) {
|
||||
commit_root = title_id_dir->GetSubdirectory("0");
|
||||
}
|
||||
// If neither exists, we might fall back to title_id_dir itself if users put SD_Cache directly there,
|
||||
// but based on SaveDataFactory we expect it inside 0 or 1.
|
||||
if (commit_root == nullptr) {
|
||||
// Check if SD_Cache exists directly in title_dir (legacy/fallback)
|
||||
bool has_sd_cache_root = false;
|
||||
for (const auto& sub : title_id_dir->GetSubdirectories()) {
|
||||
if (sub->GetName().find("SD_Cache.") == 0) {
|
||||
has_sd_cache_root = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_sd_cache_root) {
|
||||
commit_root = title_id_dir;
|
||||
} else {
|
||||
continue; // No valid storage found
|
||||
}
|
||||
}
|
||||
|
||||
bool found_any = false;
|
||||
for (const auto& sd_cache_dir : commit_root->GetSubdirectories()) {
|
||||
const std::string& name = sd_cache_dir->GetName();
|
||||
if (name.find("SD_Cache.") == 0) {
|
||||
// Parse index from "SD_Cache.XXXX"
|
||||
u64 index = 0;
|
||||
try {
|
||||
if (name.size() > 9) {
|
||||
index = std::stoull(name.substr(9));
|
||||
}
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
|
||||
info.emplace_back(SaveDataInfo{
|
||||
0,
|
||||
space,
|
||||
FileSys::SaveDataType::Cache,
|
||||
{}, // padding 0x6
|
||||
{}, // user_id (empty array match)
|
||||
0, // save_id
|
||||
title_id,
|
||||
sd_cache_dir->GetSize(),
|
||||
static_cast<u16>(index), // Correct index with cast
|
||||
FileSys::SaveDataRank::Primary,
|
||||
{}, // padding 0x25
|
||||
});
|
||||
found_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for legacy "flat" cache if no SD_Cache folders found?
|
||||
// If the user specific structure IS enforced, maybe we don't fallback.
|
||||
// But if they have existing cache without the folder, it is effectively index 0.
|
||||
if (!found_any) {
|
||||
// Treat the entire commit_root as index 0 (Legacy behavior)
|
||||
info.emplace_back(SaveDataInfo{
|
||||
0,
|
||||
space,
|
||||
FileSys::SaveDataType::Cache,
|
||||
{}, // padding 0x6
|
||||
{}, // user_id (empty array match)
|
||||
0, // save_id
|
||||
title_id,
|
||||
commit_root->GetSize(),
|
||||
0, // index 0
|
||||
FileSys::SaveDataRank::Primary,
|
||||
{}, // padding 0x25
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
for (const auto& user_id : type->GetSubdirectories()) {
|
||||
|
||||
@@ -16,7 +16,7 @@ class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
|
||||
public:
|
||||
explicit ISaveDataInfoReader(Core::System& system_,
|
||||
std::shared_ptr<SaveDataController> save_data_controller_,
|
||||
FileSys::SaveDataSpaceId space);
|
||||
FileSys::SaveDataSpaceId space, bool cache_only = false);
|
||||
~ISaveDataInfoReader() override;
|
||||
|
||||
struct SaveDataInfo {
|
||||
@@ -38,8 +38,9 @@ public:
|
||||
OutArray<SaveDataInfo, BufferAttr_HipcMapAlias> out_entries);
|
||||
|
||||
private:
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space);
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only);
|
||||
void FindNormalSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
void FindCacheSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
void FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
|
||||
std::shared_ptr<SaveDataController> save_data_controller;
|
||||
|
||||
@@ -353,10 +353,10 @@ Result FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(
|
||||
|
||||
Result FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(
|
||||
OutInterface<ISaveDataInfoReader> out_interface) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
*out_interface = std::make_shared<ISaveDataInfoReader>(system, save_data_controller,
|
||||
FileSys::SaveDataSpaceId::Temporary);
|
||||
FileSys::SaveDataSpaceId::User, true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -42,8 +42,16 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co
|
||||
const auto& message_header = context.GetDomainMessageHeader();
|
||||
const auto object_id = message_header.object_id;
|
||||
|
||||
// Some games send magic numbers in the object_id field, which indicates
|
||||
// this is not actually a proper domain request
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
if (object_id == sfci_magic || object_id == sfco_magic) {
|
||||
// This is not a domain request, treat it as a regular session request
|
||||
return session_handler != nullptr;
|
||||
}
|
||||
|
||||
if (object_id > DomainHandlerCount()) {
|
||||
LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
|
||||
return false;
|
||||
}
|
||||
return !DomainHandler(object_id - 1).expired();
|
||||
@@ -91,7 +99,18 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const auto& domain_message_header = context.GetDomainMessageHeader();
|
||||
const u32 object_id{domain_message_header.object_id};
|
||||
const u32 object_id = domain_message_header.object_id;
|
||||
|
||||
// Some games send magic numbers in the object_id field
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
if (object_id == sfci_magic || object_id == sfco_magic) {
|
||||
// This is not a domain request, handle as regular session request
|
||||
LOG_DEBUG(IPC, "Detected magic number 0x{:08X} in object_id, treating as regular session request. Command={}",
|
||||
object_id, context.GetCommand());
|
||||
return session_handler->HandleSyncRequest(*server_session, context);
|
||||
}
|
||||
|
||||
switch (domain_message_header.command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > this->DomainHandlerCount()) {
|
||||
@@ -200,7 +219,17 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
// If this is an incoming message, only CommandType "Request" has a domain header
|
||||
// All outgoing domain messages have the domain header, if only incoming has it
|
||||
if (incoming || domain_message_header) {
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
// Check if the next value is actually a magic number (SFCI/SFCO)
|
||||
// Some games send these in the object_id field, indicating this is not a domain request
|
||||
const u32 possible_object_id = src_cmdbuf[rp.GetCurrentOffset() + 1]; // object_id is second field
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
|
||||
if (possible_object_id != sfci_magic && possible_object_id != sfco_magic) {
|
||||
// This is a proper domain request
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
}
|
||||
// If it's a magic number, skip reading the domain header entirely
|
||||
} else {
|
||||
if (GetManager()->IsDomain()) {
|
||||
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
|
||||
@@ -220,9 +249,15 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
}
|
||||
|
||||
if (incoming) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
|
||||
// Only check magic if we have a valid data payload header
|
||||
// When domain header is skipped (SFCI in object_id), the structure is different
|
||||
if (domain_message_header) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
|
||||
}
|
||||
} else {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
|
||||
if (domain_message_header) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -79,7 +82,7 @@ using DeviceHandle = u64;
|
||||
|
||||
// This is nn::nfc::TagInfo
|
||||
struct TagInfo {
|
||||
UniqueSerialNumber uuid;
|
||||
UniqueSerialNumber uuid{};
|
||||
u8 uuid_length;
|
||||
INSERT_PADDING_BYTES(0x15);
|
||||
NfcProtocol protocol;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -315,7 +318,7 @@ static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
|
||||
// This is nn::nfp::RegisterInfo
|
||||
struct RegisterInfo {
|
||||
Service::Mii::CharInfo mii_char_info;
|
||||
WriteDate creation_date;
|
||||
WriteDate creation_date{};
|
||||
AmiiboName amiibo_name;
|
||||
u8 font_region;
|
||||
INSERT_PADDING_BYTES(0x7A);
|
||||
|
||||
@@ -164,7 +164,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
|
||||
// Rebuild shared fonts from data ncas or synthesize
|
||||
|
||||
impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(SHARED_FONT_MEM_SIZE);
|
||||
for (auto font : SHARED_FONTS) {
|
||||
for (auto& font : SHARED_FONTS) {
|
||||
FileSys::VirtualFile romfs;
|
||||
const auto nca =
|
||||
nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
|
||||
@@ -261,7 +261,7 @@ Result IPlatformServiceManager::GetSharedFontInOrderOfPriority(
|
||||
out_font_sizes.size(), impl->shared_font_regions.size()});
|
||||
|
||||
for (size_t i = 0; i < max_size; i++) {
|
||||
auto region = impl->GetSharedFontRegion(i);
|
||||
auto& region = impl->GetSharedFontRegion(i);
|
||||
|
||||
out_font_codes[i] = static_cast<u32>(i);
|
||||
out_font_offsets[i] = region.offset;
|
||||
|
||||
@@ -923,7 +923,10 @@ std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> messa
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
return Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
LOG_CRITICAL(Service, "SendImpl called: fd={}, size={} bytes, flags={}", fd, message.size(), flags);
|
||||
auto result = Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
LOG_CRITICAL(Service, "SendImpl result: sent={} bytes, errno={}", result.first, static_cast<int>(result.second));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, std::span<const u8> message,
|
||||
|
||||
@@ -60,12 +60,29 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service, "Redirecting NSD host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static std::string ResolveImpl(const std::string& fqdn_in) {
|
||||
// The real implementation makes various substitutions.
|
||||
// For now we just return the string as-is, which is good enough when not
|
||||
// connecting to real Nintendo servers.
|
||||
LOG_WARNING(Service, "(STUBBED) called, fqdn_in={}", fqdn_in);
|
||||
return fqdn_in;
|
||||
|
||||
return GetRedirectedHost(fqdn_in);
|
||||
}
|
||||
|
||||
static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) {
|
||||
|
||||
@@ -29,13 +29,13 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
|
||||
{4, nullptr, "GetHostStringErrorRequest"},
|
||||
{5, &SFDNSRES::GetGaiStringErrorRequest, "GetGaiStringErrorRequest"},
|
||||
{6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"},
|
||||
{7, nullptr, "GetNameInfoRequest"},
|
||||
{8, nullptr, "RequestCancelHandleRequest"},
|
||||
{7, &SFDNSRES::GetNameInfoRequest, "GetNameInfoRequest"},
|
||||
{8, &SFDNSRES::RequestCancelHandleRequest, "RequestCancelHandleRequest"},
|
||||
{9, nullptr, "CancelRequest"},
|
||||
{10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"},
|
||||
{11, nullptr, "GetHostByAddrRequestWithOptions"},
|
||||
{12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"},
|
||||
{13, nullptr, "GetNameInfoRequestWithOptions"},
|
||||
{13, &SFDNSRES::GetNameInfoRequestWithOptions, "GetNameInfoRequestWithOptions"},
|
||||
{14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"},
|
||||
{15, nullptr, "ResolverGetOptionRequest"},
|
||||
};
|
||||
@@ -66,6 +66,21 @@ static bool IsBlockedHost(const std::string& host) {
|
||||
[&host](const std::string& domain) { return host.find(domain) != std::string::npos; });
|
||||
}
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service, "Redirecting host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) {
|
||||
// These combinations have been verified on console (but are not
|
||||
// exhaustive).
|
||||
@@ -163,7 +178,8 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
|
||||
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
std::string host = Common::StringFromBuffer(host_buffer);
|
||||
host = GetRedirectedHost(host);
|
||||
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
|
||||
|
||||
// Prevent resolution of Nintendo servers
|
||||
@@ -281,7 +297,8 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
|
||||
// before looking up.
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
std::string host = Common::StringFromBuffer(host_buffer);
|
||||
host = GetRedirectedHost(host);
|
||||
|
||||
// Prevent resolution of Nintendo servers
|
||||
if (IsBlockedHost(host)) {
|
||||
@@ -364,6 +381,120 @@ void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void SFDNSRES::GetNameInfoRequest(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto addr_in = rp.PopRaw<SockAddrIn>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
const u32 cancel_handle = rp.Pop<u32>();
|
||||
const u64 process_id = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service, "called. flags={}, cancel_handle={}, process_id={}", flags, cancel_handle,
|
||||
process_id);
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
const auto res = Network::GetNameInfo(Translate(addr_in));
|
||||
if (res.second != 0) {
|
||||
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
|
||||
const auto service_error = Translate(network_error);
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = 0,
|
||||
.gai_error = service_error,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& host = res.first;
|
||||
const u32 data_size = static_cast<u32>(host.size() + 1);
|
||||
|
||||
ctx.WriteBuffer(host.data(), data_size, 0);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = GetAddrInfoError::SUCCESS,
|
||||
.netdb_error = NetDbError::Success,
|
||||
.bsd_errno = Errno::SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::GetNameInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
struct InputParameters {
|
||||
u32 flags;
|
||||
u32 interface_index;
|
||||
u64 process_id;
|
||||
u32 padding; // 0x14 + 4 = 0x18? No. 0x14 aligned to 8 bytes?
|
||||
// Wait, sizeof(InputParameters) == 0x14.
|
||||
};
|
||||
// Derived from partial snippets:
|
||||
// u32 flags, u32 interface_index, u64 process_id.
|
||||
// 4 + 4 + 8 = 16 bytes (0x10).
|
||||
// The previous prompt had static_assert size 0x14.
|
||||
// Maybe a u32 padding?
|
||||
|
||||
// Let's rely on standard layout.
|
||||
// I will use manual popping for safety if struct definition is unknown.
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto addr_in = rp.PopRaw<SockAddrIn>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
const u32 interface_index = rp.Pop<u32>();
|
||||
const u64 process_id = rp.Pop<u64>();
|
||||
(void)flags;
|
||||
(void)interface_index;
|
||||
(void)process_id;
|
||||
// If there was padding, it might be implicitly popped or ignored.
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
const auto res = Network::GetNameInfo(Translate(addr_in));
|
||||
if (res.second != 0) {
|
||||
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
|
||||
const auto service_error = Translate(network_error);
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = 0,
|
||||
.gai_error = service_error,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& host = res.first;
|
||||
const u32 data_size = static_cast<u32>(host.size() + 1);
|
||||
|
||||
ctx.WriteBuffer(host.data(), data_size, 0);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = GetAddrInfoError::SUCCESS,
|
||||
.netdb_error = NetDbError::Success,
|
||||
.bsd_errno = Errno::SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
@@ -372,4 +503,24 @@ void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<s32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void SFDNSRES::RequestCancelHandleRequest(HLERequestContext& ctx) {
|
||||
// This is just a stub for now.
|
||||
// In a real implementation this would likely cancel a pending request represented by the handle.
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
struct InputParameters {
|
||||
u32 cancel_handle;
|
||||
};
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto input = rp.PopRaw<InputParameters>();
|
||||
|
||||
LOG_DEBUG(Service, "cancel_handle={}", input.cancel_handle);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(0); // cancel_handle (response seems to echo it or return a new one? usually just an error code or unrelated)
|
||||
// Actually based on typical patterns, it probably returns an error code (bsd_errno).
|
||||
rb.Push<s32>(0); // bsd_errno
|
||||
}
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -22,7 +22,10 @@ private:
|
||||
void GetHostByNameRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequest(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetNameInfoRequest(HLERequestContext& ctx);
|
||||
void GetNameInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void ResolverSetOptionRequest(HLERequestContext& ctx);
|
||||
void RequestCancelHandleRequest(HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -16,10 +16,12 @@ enum class Errno : u32 {
|
||||
SUCCESS = 0,
|
||||
BADF = 9,
|
||||
AGAIN = 11,
|
||||
ACCES = 13,
|
||||
INVAL = 22,
|
||||
MFILE = 24,
|
||||
PIPE = 32,
|
||||
MSGSIZE = 90,
|
||||
ADDRINUSE = 98,
|
||||
CONNABORTED = 103,
|
||||
CONNRESET = 104,
|
||||
NOTCONN = 107,
|
||||
|
||||
@@ -37,6 +37,10 @@ Errno Translate(Network::Errno value) {
|
||||
return Errno::CONNRESET;
|
||||
case Network::Errno::INPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case Network::Errno::ACCES:
|
||||
return Errno::ACCES;
|
||||
case Network::Errno::ADDRINUSE:
|
||||
return Errno::ADDRINUSE;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
|
||||
return Errno::SUCCESS;
|
||||
@@ -261,7 +265,9 @@ PollEvents Translate(Network::PollEvents flags) {
|
||||
Network::SockAddrIn Translate(SockAddrIn value) {
|
||||
// Note: 6 is incorrect, but can be passed by homebrew (because libnx sets
|
||||
// sin_len to 6 when deserializing getaddrinfo results).
|
||||
ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6);
|
||||
if (value.len != 0 && value.len != sizeof(value) && value.len != 6) {
|
||||
LOG_WARNING(Service, "Unexpected SockAddrIn length={}", value.len);
|
||||
}
|
||||
|
||||
return {
|
||||
.family = Translate(static_cast<Domain>(value.family)),
|
||||
|
||||
@@ -117,15 +117,20 @@ public:
|
||||
RegisterHandlers(functions);
|
||||
|
||||
shared_data->connection_count++;
|
||||
LOG_CRITICAL(Service_SSL, "ISslConnection created! Total connections: {}", shared_data->connection_count);
|
||||
}
|
||||
|
||||
~ISslConnection() {
|
||||
shared_data->connection_count--;
|
||||
if (fd_to_close.has_value()) {
|
||||
const s32 fd = *fd_to_close;
|
||||
if (!do_not_close_socket) {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"do_not_close_socket was changed after setting socket; is this right?");
|
||||
if (do_not_close_socket) {
|
||||
// If we aren't supposed to close the socket, but we have an fd_to_close,
|
||||
// that means the configuration changed after we took ownership.
|
||||
// This is weird but we should probably honor the flag.
|
||||
// However, the original valid logic seemed to imply we duped the socket
|
||||
// and should close our dup... but let's stick to what the flag says.
|
||||
LOG_INFO(Service_SSL, "do_not_close_socket is true, skipping close of fd {}", fd);
|
||||
} else {
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
|
||||
if (bsd) {
|
||||
@@ -269,16 +274,17 @@ private:
|
||||
}
|
||||
|
||||
Result PendingImpl(s32* out_pending) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
||||
*out_pending = 0;
|
||||
return ResultSuccess;
|
||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||
return backend->Pending(out_pending);
|
||||
}
|
||||
|
||||
void SetSocketDescriptor(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 in_fd = rp.Pop<s32>();
|
||||
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor called with fd={}", in_fd);
|
||||
s32 out_fd{-1};
|
||||
const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
|
||||
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor result: res={}, out_fd={}", res.raw, out_fd);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res);
|
||||
rb.Push<s32>(out_fd);
|
||||
@@ -308,7 +314,9 @@ private:
|
||||
}
|
||||
|
||||
void DoHandshake(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_SSL, "DoHandshake called, socket={}", socket != nullptr);
|
||||
const Result res = DoHandshakeImpl();
|
||||
LOG_INFO(Service_SSL, "DoHandshake result: {}, did_handshake={}", res.raw, did_handshake);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
||||
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
||||
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
|
||||
virtual Result Pending(s32* out_pending) = 0;
|
||||
};
|
||||
|
||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
|
||||
|
||||
@@ -115,6 +115,22 @@ inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service_SSL, "Redirecting SSL host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
|
||||
@@ -157,7 +173,8 @@ public:
|
||||
socket = std::move(socket_in);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname) override {
|
||||
Result SetHostName(const std::string& hostname_in) override {
|
||||
const std::string hostname = GetRedirectedHost(hostname_in);
|
||||
if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification
|
||||
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
||||
return CheckOpenSSLErrors();
|
||||
@@ -247,6 +264,28 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
if (!ssl) {
|
||||
return ResultInternalError;
|
||||
}
|
||||
int pending = SSL_pending(ssl);
|
||||
if (pending > 0) {
|
||||
*out_pending = pending;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
} else {
|
||||
*out_pending = 0;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
~SSLConnectionBackendOpenSSL() {
|
||||
// this is null-tolerant:
|
||||
SSL_free(ssl);
|
||||
|
||||
@@ -489,6 +489,27 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
*out_pending = static_cast<s32>(cleartext_read_buf.size());
|
||||
if (*out_pending > 0) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (!ciphertext_read_buf.empty()) {
|
||||
*out_pending = 1;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
~SSLConnectionBackendSchannel() {
|
||||
if (handshake_state != HandshakeState::Initial) {
|
||||
DeleteSecurityContext(&ctxt);
|
||||
|
||||
@@ -149,6 +149,26 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
size_t bufferSize = 0;
|
||||
OSStatus status = SSLGetBufferedReadSize(context, &bufferSize);
|
||||
if (status == 0 && bufferSize > 0) {
|
||||
*out_pending = static_cast<s32>(bufferSize);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
} else {
|
||||
*out_pending = 0;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
|
||||
return ReadOrWriteCallback(connection, data, dataLength, true);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "core/internal_network/network_interface.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
|
||||
#include <windows.h>
|
||||
#include <wlanapi.h>
|
||||
#ifdef _MSC_VER
|
||||
|
||||
180
src/core/internal_network/legacy_online.cpp
Normal file
180
src/core/internal_network/legacy_online.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/internal_network/legacy_online.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
LegacyOnlineService::LegacyOnlineService() {
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa_data;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
|
||||
LOG_ERROR(Network, "WSAStartup failed with error: {}", WSAGetLastError());
|
||||
winsock_initialized = false;
|
||||
} else {
|
||||
winsock_initialized = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
LegacyOnlineService::~LegacyOnlineService() {
|
||||
Stop();
|
||||
#ifdef _WIN32
|
||||
if (winsock_initialized) {
|
||||
WSACleanup();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LegacyOnlineService::Start() {
|
||||
if (is_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!winsock_initialized) {
|
||||
LOG_ERROR(Network, "Cannot start Legacy Online Service: Winsock not initialized");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
is_running = true;
|
||||
worker_thread = std::thread(&LegacyOnlineService::ServerLoop, this);
|
||||
}
|
||||
|
||||
void LegacyOnlineService::Stop() {
|
||||
if (!is_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_running = false;
|
||||
|
||||
// Close socket to wake up the thread if it's blocked on recvfrom
|
||||
if (socket_fd != ~0ULL) {
|
||||
#ifdef _WIN32
|
||||
closesocket(static_cast<SOCKET>(socket_fd));
|
||||
#else
|
||||
close(static_cast<int>(socket_fd));
|
||||
#endif
|
||||
socket_fd = ~0ULL;
|
||||
}
|
||||
|
||||
if (worker_thread.joinable()) {
|
||||
worker_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void LegacyOnlineService::ServerLoop() {
|
||||
LOG_INFO(Network, "Starting Legacy Online UDP Server on port {}", PORT);
|
||||
|
||||
// WSAStartup is now handled in the constructor
|
||||
|
||||
|
||||
|
||||
auto s = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
#ifdef _WIN32
|
||||
if (s == INVALID_SOCKET) {
|
||||
#else
|
||||
if (s == -1) {
|
||||
#endif
|
||||
LOG_ERROR(Network, "Failed to create socket");
|
||||
is_running = false;
|
||||
return;
|
||||
}
|
||||
socket_fd = static_cast<uintptr_t>(s);
|
||||
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(static_cast<SOCKET>(socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(static_cast<int>(socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
sockaddr_in server_addr{};
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(PORT);
|
||||
|
||||
int res = -1;
|
||||
#ifdef _WIN32
|
||||
res = bind(static_cast<SOCKET>(socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#else
|
||||
res = bind(static_cast<int>(socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Network, "Failed to bind to port {}: {}", PORT, WSAGetLastError());
|
||||
closesocket(static_cast<SOCKET>(socket_fd));
|
||||
#else
|
||||
LOG_ERROR(Network, "Failed to bind to port {}: {}", PORT, strerror(errno));
|
||||
close(static_cast<int>(socket_fd));
|
||||
#endif
|
||||
socket_fd = ~0ULL;
|
||||
is_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Network, "Legacy Online Server waiting for messages...");
|
||||
|
||||
// Set a timeout for recvfrom so check is_running periodically if not closed via socket
|
||||
// Alternatively, closing the socket (as done in Stop) will cause recvfrom to return error
|
||||
|
||||
char buffer[2048];
|
||||
while (is_running) {
|
||||
sockaddr_in client_addr{};
|
||||
#ifdef _WIN32
|
||||
int client_len = sizeof(client_addr);
|
||||
#else
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
#endif
|
||||
|
||||
int len = -1;
|
||||
#ifdef _WIN32
|
||||
len = recvfrom(static_cast<SOCKET>(socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
|
||||
#else
|
||||
len = recvfrom(static_cast<int>(socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
|
||||
#endif
|
||||
|
||||
if (!is_running) break;
|
||||
|
||||
if (len > 0) {
|
||||
// Send ACK
|
||||
const char* ack_msg = "ACK";
|
||||
#ifdef _WIN32
|
||||
sendto(static_cast<SOCKET>(socket_fd), ack_msg, static_cast<int>(strlen(ack_msg)), 0, (sockaddr*)&client_addr, client_len);
|
||||
#else
|
||||
sendto(static_cast<int>(socket_fd), ack_msg, strlen(ack_msg), 0, (sockaddr*)&client_addr, client_len);
|
||||
#endif
|
||||
} else {
|
||||
// Error or closed
|
||||
// If we closed the socket in Stop(), this will likely trigger.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(socket_fd));
|
||||
// WSACleanup is now handled in the destructor
|
||||
#else
|
||||
if (socket_fd != ~0ULL) close(static_cast<int>(socket_fd));
|
||||
#endif
|
||||
socket_fd = ~0ULL;
|
||||
LOG_INFO(Network, "Legacy Online Server stopped");
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
31
src/core/internal_network/legacy_online.h
Normal file
31
src/core/internal_network/legacy_online.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace Network {
|
||||
|
||||
class LegacyOnlineService {
|
||||
public:
|
||||
LegacyOnlineService();
|
||||
~LegacyOnlineService();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void ServerLoop();
|
||||
|
||||
std::atomic_bool is_running{false};
|
||||
std::thread worker_thread;
|
||||
uintptr_t socket_fd{~0ULL}; // ~0ULL is approx -1 equivalent for unsigned
|
||||
bool winsock_initialized{false};
|
||||
static constexpr int PORT = 6000;
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
@@ -75,6 +75,8 @@ SOCKET GetInterruptSocket() {
|
||||
return interrupt_socket;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -83,6 +85,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
#endif
|
||||
|
||||
switch (static_cast<Domain>(input.family)) {
|
||||
case Domain::Unspecified:
|
||||
case Domain::INET:
|
||||
result.sin_family = AF_INET;
|
||||
break;
|
||||
@@ -105,6 +108,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
LINGER MakeLinger(bool enable, u32 linger_value) {
|
||||
ASSERT(linger_value <= (std::numeric_limits<u_short>::max)());
|
||||
|
||||
@@ -124,6 +129,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
case 0:
|
||||
return Errno::SUCCESS;
|
||||
case WSAEBADF:
|
||||
case WSAENOTSOCK:
|
||||
return Errno::BADF;
|
||||
case WSAEINVAL:
|
||||
return Errno::INVAL;
|
||||
@@ -157,6 +163,10 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
return Errno::TIMEDOUT;
|
||||
case WSAEINPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case WSAEACCES:
|
||||
return Errno::ACCES;
|
||||
case WSAEADDRINUSE:
|
||||
return Errno::ADDRINUSE;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
||||
return Errno::OTHER;
|
||||
@@ -212,6 +222,8 @@ SOCKET GetInterruptSocket() {
|
||||
return interrupt_pipe_fd[0];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -234,6 +246,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
|
||||
return poll(fds, static_cast<nfds_t>(nfds), timeout);
|
||||
}
|
||||
@@ -320,6 +334,8 @@ Errno GetAndLogLastError(CallType call_type = CallType::Other) {
|
||||
return err;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
switch (gai_err) {
|
||||
case 0:
|
||||
@@ -373,6 +389,8 @@ GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Domain TranslateDomainFromNative(int domain) {
|
||||
switch (domain) {
|
||||
case 0:
|
||||
@@ -607,6 +625,8 @@ Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
|
||||
const size_t num = pollfds.size();
|
||||
|
||||
@@ -871,8 +891,13 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
|
||||
}
|
||||
|
||||
Errno Socket::Close() {
|
||||
if (fd == INVALID_SOCKET) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
[[maybe_unused]] const int result = closesocket(fd);
|
||||
ASSERT(result == 0);
|
||||
if (result != 0) {
|
||||
GetAndLogLastError();
|
||||
}
|
||||
fd = INVALID_SOCKET;
|
||||
|
||||
return Errno::SUCCESS;
|
||||
|
||||
@@ -48,6 +48,8 @@ enum class Errno {
|
||||
TIMEDOUT,
|
||||
MSGSIZE,
|
||||
INPROGRESS,
|
||||
ACCES,
|
||||
ADDRINUSE,
|
||||
OTHER,
|
||||
};
|
||||
|
||||
@@ -122,8 +124,33 @@ std::optional<IPv4Address> GetHostIPv4Address();
|
||||
std::string IPv4AddressToString(IPv4Address ip_addr);
|
||||
u32 IPv4AddressToInteger(IPv4Address ip_addr);
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
// named to avoid name collision with Windows macro
|
||||
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
const std::string& host, const std::optional<std::string>& service);
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input);
|
||||
|
||||
inline std::pair<std::string, int> GetNameInfo(const SockAddrIn& addr) {
|
||||
sockaddr sa = TranslateFromSockAddrIn(addr);
|
||||
sockaddr_in addr_in{};
|
||||
std::memcpy(&addr_in, &sa, sizeof(sockaddr_in));
|
||||
|
||||
char host[1025]; // NI_MAXHOST
|
||||
int err = getnameinfo(reinterpret_cast<const sockaddr*>(&addr_in), sizeof(addr_in), host, sizeof(host), nullptr, 0, 0);
|
||||
|
||||
if (err != 0) {
|
||||
return {std::string{}, err};
|
||||
}
|
||||
|
||||
return {std::string(host), 0};
|
||||
}
|
||||
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int err);
|
||||
|
||||
} // namespace Network
|
||||
|
||||
@@ -134,4 +134,4 @@ target_include_directories(dynarmic_tests PRIVATE . ../src)
|
||||
target_compile_options(dynarmic_tests PRIVATE ${DYNARMIC_CXX_FLAGS})
|
||||
target_compile_definitions(dynarmic_tests PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
|
||||
|
||||
add_test(dynarmic_tests dynarmic_tests --durations yes)
|
||||
add_test(NAME dynarmic_tests COMMAND dynarmic_tests --durations yes)
|
||||
|
||||
@@ -48,7 +48,7 @@ private:
|
||||
void Save();
|
||||
|
||||
PlayTimeDatabase database;
|
||||
u64 running_program_id;
|
||||
u64 running_program_id{};
|
||||
std::jthread play_time_thread;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -53,7 +56,7 @@ struct ChatEntry {
|
||||
|
||||
/// Represents a system status message.
|
||||
struct StatusMessageEntry {
|
||||
StatusMessageTypes type; ///< Type of the message
|
||||
StatusMessageTypes type{}; ///< Type of the message
|
||||
/// Subject of the message. i.e. the user who is joining/leaving/being banned, etc.
|
||||
std::string nickname;
|
||||
std::string username;
|
||||
|
||||
@@ -446,10 +446,12 @@ void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst) {
|
||||
|
||||
void EmitSR_WScaleFactorXY(EmitContext& ctx, IR::Inst& inst) {
|
||||
LOG_WARNING(Shader, "(STUBBED) called");
|
||||
ctx.AddU32("{}=0x3c003c00u;", inst);
|
||||
}
|
||||
|
||||
void EmitSR_WScaleFactorZ(EmitContext& ctx, IR::Inst& inst) {
|
||||
LOG_WARNING(Shader, "(STUBBED) called");
|
||||
ctx.AddU32("{}=0x3f800000u;", inst);
|
||||
}
|
||||
|
||||
void EmitYDirection(EmitContext& ctx, IR::Inst& inst) {
|
||||
|
||||
@@ -58,6 +58,9 @@ std::string FormatFloat(std::string_view value, IR::Type type) {
|
||||
if (value == "nan") {
|
||||
return "utof(0x7fc00000)";
|
||||
}
|
||||
if (value == "-nan") {
|
||||
return "utof(0xffc00000)";
|
||||
}
|
||||
if (value == "inf") {
|
||||
return "utof(0x7f800000)";
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
|
||||
@@ -16,8 +15,6 @@
|
||||
#include "video_core/guest_memory.h"
|
||||
#include "video_core/host1x/gpu_device_memory_manager.h"
|
||||
#include "video_core/texture_cache/util.h"
|
||||
#include "video_core/polygon_mode_utils.h"
|
||||
#include "video_core/renderer_vulkan/line_loop_utils.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
@@ -356,37 +353,14 @@ void BufferCache<P>::UpdateComputeBuffers() {
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
if (is_indexed) {
|
||||
BindHostIndexBuffer();
|
||||
} else {
|
||||
if constexpr (!P::IS_OPENGL) {
|
||||
const auto polygon_mode = VideoCore::EffectivePolygonMode(maxwell3d->regs);
|
||||
if (draw_state.topology == Maxwell::PrimitiveTopology::Polygon &&
|
||||
polygon_mode == Maxwell::PolygonMode::Line && draw_state.vertex_buffer.count > 1) {
|
||||
const u32 vertex_count = draw_state.vertex_buffer.count;
|
||||
const u32 generated_count = vertex_count + 1;
|
||||
const bool use_u16 = vertex_count <= 0x10000;
|
||||
const u32 element_size = use_u16 ? sizeof(u16) : sizeof(u32);
|
||||
auto staging = runtime.UploadStagingBuffer(
|
||||
static_cast<size_t>(generated_count) * element_size);
|
||||
std::span<u8> dst_span{staging.mapped_span.data(),
|
||||
generated_count * static_cast<size_t>(element_size)};
|
||||
Vulkan::LineLoop::GenerateSequentialWithClosureRaw(dst_span, element_size);
|
||||
const auto synthetic_format = use_u16 ? Maxwell::IndexFormat::UnsignedShort
|
||||
: Maxwell::IndexFormat::UnsignedInt;
|
||||
runtime.BindIndexBuffer(draw_state.topology, synthetic_format,
|
||||
draw_state.vertex_buffer.first, generated_count,
|
||||
staging.buffer, static_cast<u32>(staging.offset),
|
||||
generated_count * element_size);
|
||||
}
|
||||
}
|
||||
if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
|
||||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
|
||||
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
|
||||
draw_state.vertex_buffer.count);
|
||||
}
|
||||
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
|
||||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
|
||||
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
|
||||
draw_state.vertex_buffer.count);
|
||||
}
|
||||
}
|
||||
BindHostVertexBuffers();
|
||||
@@ -715,44 +689,6 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
||||
const u32 offset = buffer.Offset(channel_state->index_buffer.device_addr);
|
||||
const u32 size = channel_state->index_buffer.size;
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
if constexpr (!P::IS_OPENGL) {
|
||||
const auto polygon_mode = VideoCore::EffectivePolygonMode(maxwell3d->regs);
|
||||
const bool polygon_line =
|
||||
draw_state.topology == Maxwell::PrimitiveTopology::Polygon &&
|
||||
polygon_mode == Maxwell::PolygonMode::Line;
|
||||
if (polygon_line && draw_state.index_buffer.count > 1) {
|
||||
const u32 element_size = draw_state.index_buffer.FormatSizeInBytes();
|
||||
const size_t src_bytes = static_cast<size_t>(draw_state.index_buffer.count) * element_size;
|
||||
const size_t total_bytes = src_bytes + element_size;
|
||||
auto staging = runtime.UploadStagingBuffer(total_bytes);
|
||||
std::span<u8> dst_span{staging.mapped_span.data(), total_bytes};
|
||||
std::span<const u8> src_span;
|
||||
if (!draw_state.inline_index_draw_indexes.empty()) {
|
||||
const u8* const src =
|
||||
draw_state.inline_index_draw_indexes.data() +
|
||||
static_cast<size_t>(draw_state.index_buffer.first) * element_size;
|
||||
src_span = {src, src_bytes};
|
||||
} else if (const u8* const cpu_base =
|
||||
device_memory.GetPointer<u8>(channel_state->index_buffer.device_addr)) {
|
||||
const u8* const src = cpu_base +
|
||||
static_cast<size_t>(draw_state.index_buffer.first) * element_size;
|
||||
src_span = {src, src_bytes};
|
||||
} else {
|
||||
const DAddr src_addr =
|
||||
channel_state->index_buffer.device_addr +
|
||||
static_cast<DAddr>(draw_state.index_buffer.first) * element_size;
|
||||
device_memory.ReadBlockUnsafe(src_addr, dst_span.data(), src_bytes);
|
||||
src_span = {dst_span.data(), src_bytes};
|
||||
}
|
||||
Vulkan::LineLoop::CopyWithClosureRaw(dst_span, src_span, element_size);
|
||||
buffer.MarkUsage(offset, size);
|
||||
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
|
||||
draw_state.index_buffer.first, draw_state.index_buffer.count + 1,
|
||||
staging.buffer, static_cast<u32>(staging.offset),
|
||||
static_cast<u32>(total_bytes));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
|
||||
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
|
||||
auto upload_staging = runtime.UploadStagingBuffer(size);
|
||||
@@ -1769,21 +1705,26 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
|
||||
return NULL_BINDING;
|
||||
}
|
||||
|
||||
// xbzk: New size logic. Fixes MCI.
|
||||
// If ever the * comment below prove wrong, the 'if' block may be removed.
|
||||
const auto size = [&]() {
|
||||
const bool is_nvn_cbuf = cbuf_index == 0;
|
||||
// The NVN driver buffer (index 0) is known to pack the SSBO address followed by its size.
|
||||
if (is_nvn_cbuf) {
|
||||
const u32 ssbo_size = gpu_memory->Read<u32>(ssbo_addr + 8);
|
||||
if (ssbo_size != 0) {
|
||||
return ssbo_size;
|
||||
// * The NVN driver buffer (index 0) is known to pack the SSBO address followed by its size.
|
||||
const u64 next_qword = gpu_memory->Read<u64>(ssbo_addr + 8);
|
||||
const u32 upper_32 = static_cast<u32>(next_qword >> 32);
|
||||
// Hardware-based detection: GPU addresses have non-zero upper bits
|
||||
if (upper_32 == 0) {
|
||||
// This is a size field, not a GPU address
|
||||
return static_cast<u32>(next_qword); // Return lower_32
|
||||
}
|
||||
}
|
||||
// Other titles (notably Doom Eternal) may use STG/LDG on buffer addresses in custom defined
|
||||
// cbufs, which do not store the sizes adjacent to the addresses, so use the fully
|
||||
// mapped buffer size for now.
|
||||
// Fall through: either not NVN cbuf (Doom Eternal & +), or NVN but ssbo_addr+8 is a GPU address (MCI)
|
||||
const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr));
|
||||
// Cap at 8MB to prevent allocator overflow from misinterpreted addresses
|
||||
return (std::min)(memory_layout_size, static_cast<u32>(8_MiB));
|
||||
}();
|
||||
|
||||
// Alignment only applies to the offset of the buffer
|
||||
const u32 alignment = runtime.GetStorageBufferAlignment();
|
||||
const GPUVAddr aligned_gpu_addr = Common::AlignDown(gpu_addr, alignment);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -17,7 +20,7 @@ void Scheduler::Push(s32 channel, CommandList&& entries) {
|
||||
std::unique_lock lk(scheduling_guard);
|
||||
auto it = channels.find(channel);
|
||||
ASSERT(it != channels.end());
|
||||
auto channel_state = it->second;
|
||||
auto& channel_state = it->second;
|
||||
gpu.BindChannel(channel_state->bind_id);
|
||||
channel_state->dma_pusher->Push(std::move(entries));
|
||||
channel_state->dma_pusher->DispatchCalls();
|
||||
|
||||
@@ -81,7 +81,8 @@ public:
|
||||
if constexpr (can_async_check) {
|
||||
guard.lock();
|
||||
}
|
||||
if (Settings::IsGPULevelLow() || (Settings::IsGPULevelMedium() && !should_flush)) {
|
||||
// if ((Settings::IsGPULevelLow() || Settings::IsGPULevelMedium()) && !should_flush) {
|
||||
if (false) {
|
||||
func();
|
||||
} else {
|
||||
uncommitted_operations.emplace_back(std::move(func));
|
||||
|
||||
@@ -107,9 +107,19 @@ void Vic::Execute() {
|
||||
auto output_height{config.output_surface_config.out_surface_height + 1};
|
||||
output_surface.resize_destructive(output_width * output_height);
|
||||
|
||||
// Initialize the surface with the appropriate black pixel
|
||||
Pixel black_pixel{};
|
||||
if (config.output_surface_config.out_pixel_format == VideoPixelFormat::Y8__V8U8_N420) {
|
||||
// Y=0, U=512, V=512 (10-bit), A=0
|
||||
black_pixel = {0, 512, 512, 0};
|
||||
} else {
|
||||
// R=0, G=0, B=0, A=0
|
||||
black_pixel = {0, 0, 0, 0};
|
||||
}
|
||||
std::fill(output_surface.begin(), output_surface.end(), black_pixel);
|
||||
|
||||
if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Off) [[unlikely]] {
|
||||
// Fill the frame with black, as otherwise they can have random data and be very glitchy.
|
||||
std::fill(output_surface.begin(), output_surface.end(), Pixel{});
|
||||
|
||||
} else {
|
||||
for (size_t i = 0; i < config.slot_structs.size(); i++) {
|
||||
auto& slot_config{config.slot_structs[i]};
|
||||
@@ -122,7 +132,18 @@ void Vic::Execute() {
|
||||
nvdec_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
||||
}
|
||||
|
||||
if (auto frame = frame_queue.GetFrame(nvdec_id, luma_offset); frame) {
|
||||
auto frame = frame_queue.GetFrame(nvdec_id, luma_offset);
|
||||
if (!frame) {
|
||||
// We might've failed to find the frame, or the nvdec id is stale/wrong.
|
||||
// Try to find the nvdec id again.
|
||||
const s32 new_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
||||
if (new_id != -1) {
|
||||
nvdec_id = new_id;
|
||||
frame = frame_queue.GetFrame(nvdec_id, luma_offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
if (frame.get()) {
|
||||
switch (frame->GetPixelFormat()) {
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
@@ -170,6 +191,7 @@ void Vic::ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const P
|
||||
}
|
||||
|
||||
slot_surface.resize_destructive(out_luma_width * out_luma_height);
|
||||
std::fill(slot_surface.begin(), slot_surface.end(), Pixel{0, 512, 512, 0});
|
||||
|
||||
const auto in_luma_width{(std::min)(frame->GetWidth(), s32(out_luma_width))};
|
||||
const auto in_luma_height{(std::min)(frame->GetHeight(), s32(out_luma_height))};
|
||||
@@ -219,6 +241,7 @@ void Vic::ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span<const Pl
|
||||
const auto out_luma_stride{out_luma_width};
|
||||
|
||||
slot_surface.resize_destructive(out_luma_width * out_luma_height);
|
||||
std::fill(slot_surface.begin(), slot_surface.end(), Pixel{0, 512, 512, 0});
|
||||
|
||||
const auto in_luma_width{(std::min)(frame->GetWidth(), s32(out_luma_width))};
|
||||
[[maybe_unused]] const auto in_luma_height{
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
inline Tegra::Engines::Maxwell3D::Regs::PolygonMode EffectivePolygonMode(
|
||||
const Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
const bool cull_enabled = regs.gl_cull_test_enabled != 0;
|
||||
const auto cull_face = regs.gl_cull_face;
|
||||
const bool cull_front = cull_enabled && (cull_face == Maxwell::CullFace::Front ||
|
||||
cull_face == Maxwell::CullFace::FrontAndBack);
|
||||
const bool cull_back = cull_enabled && (cull_face == Maxwell::CullFace::Back ||
|
||||
cull_face == Maxwell::CullFace::FrontAndBack);
|
||||
|
||||
const bool render_front = !cull_front;
|
||||
const bool render_back = !cull_back;
|
||||
|
||||
const auto front_mode = regs.polygon_mode_front;
|
||||
const auto back_mode = regs.polygon_mode_back;
|
||||
|
||||
if (render_front && render_back && front_mode != back_mode) {
|
||||
if (front_mode == Maxwell::PolygonMode::Line || back_mode == Maxwell::PolygonMode::Line) {
|
||||
return Maxwell::PolygonMode::Line;
|
||||
}
|
||||
if (front_mode == Maxwell::PolygonMode::Point || back_mode == Maxwell::PolygonMode::Point) {
|
||||
return Maxwell::PolygonMode::Point;
|
||||
}
|
||||
}
|
||||
|
||||
if (render_front) {
|
||||
return front_mode;
|
||||
}
|
||||
if (render_back) {
|
||||
return back_mode;
|
||||
}
|
||||
return front_mode;
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
||||
@@ -33,6 +33,8 @@ static OGLProgram LinkSeparableProgram(GLuint shader) {
|
||||
glGetProgramInfoLog(program.handle, log_length, nullptr, log.data());
|
||||
if (link_status == GL_FALSE) {
|
||||
LOG_ERROR(Render_OpenGL, "{}", log);
|
||||
glDeleteProgram(program.handle);
|
||||
program.handle = 0;
|
||||
} else {
|
||||
LOG_WARNING(Render_OpenGL, "{}", log);
|
||||
}
|
||||
|
||||
@@ -116,9 +116,14 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
|
||||
glBindTextureUnit(0, textures[i]);
|
||||
glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
|
||||
matrices[i].data());
|
||||
glProgramUniform2ui(frag.handle, ScreenSizeLocation,
|
||||
static_cast<GLuint>(layout.screen.GetWidth()),
|
||||
static_cast<GLuint>(layout.screen.GetHeight()));
|
||||
if (frag.handle != 0) {
|
||||
const GLint screen_size_loc = glGetUniformLocation(frag.handle, "screen_size");
|
||||
if (screen_size_loc != -1) {
|
||||
glProgramUniform2ui(frag.handle, screen_size_loc,
|
||||
static_cast<GLuint>(layout.screen.GetWidth()),
|
||||
static_cast<GLuint>(layout.screen.GetHeight()));
|
||||
}
|
||||
}
|
||||
glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i]));
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
|
||||
#include "video_core/renderer_vulkan/vk_state_tracker.h"
|
||||
#include "video_core/polygon_mode_utils.h"
|
||||
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
@@ -66,7 +65,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0);
|
||||
xfb_enabled.Assign(regs.transform_feedback_enabled != 0);
|
||||
ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
|
||||
polygon_mode.Assign(PackPolygonMode(VideoCore::EffectivePolygonMode(regs)));
|
||||
polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front));
|
||||
tessellation_primitive.Assign(static_cast<u32>(regs.tessellation.params.domain_type.Value()));
|
||||
tessellation_spacing.Assign(static_cast<u32>(regs.tessellation.params.spacing.Value()));
|
||||
tessellation_clockwise.Assign(regs.tessellation.params.output_primitives.Value() ==
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Vulkan::LineLoop {
|
||||
|
||||
inline void CopyWithClosureRaw(std::span<u8> dst, std::span<const u8> src, size_t element_size) {
|
||||
ASSERT_MSG(dst.size() == src.size() + element_size, "Invalid line loop copy sizes");
|
||||
if (src.empty()) {
|
||||
if (!dst.empty()) {
|
||||
std::fill(dst.begin(), dst.end(), u8{0});
|
||||
}
|
||||
return;
|
||||
}
|
||||
std::memcpy(dst.data(), src.data(), src.size());
|
||||
std::memcpy(dst.data() + src.size(), src.data(), element_size);
|
||||
}
|
||||
|
||||
inline void GenerateSequentialWithClosureRaw(std::span<u8> dst, size_t element_size,
|
||||
u64 start_value = 0) {
|
||||
if (dst.empty()) {
|
||||
return;
|
||||
}
|
||||
const size_t last = dst.size() - element_size;
|
||||
size_t offset = 0;
|
||||
u64 value = start_value;
|
||||
while (offset < last) {
|
||||
std::memcpy(dst.data() + offset, &value, element_size);
|
||||
offset += element_size;
|
||||
++value;
|
||||
}
|
||||
std::memcpy(dst.data() + offset, &start_value, element_size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void CopyWithClosure(std::span<T> dst, std::span<const T> src) {
|
||||
ASSERT_MSG(dst.size() == src.size() + 1, "Invalid destination size for line loop copy");
|
||||
if (src.empty()) {
|
||||
if (!dst.empty()) {
|
||||
dst.front() = {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
std::copy(src.begin(), src.end(), dst.begin());
|
||||
dst.back() = src.front();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void GenerateSequentialWithClosure(std::span<T> dst, T start_value = {}) {
|
||||
if (dst.empty()) {
|
||||
return;
|
||||
}
|
||||
const size_t last = dst.size() - 1;
|
||||
for (size_t i = 0; i < last; ++i) {
|
||||
dst[i] = static_cast<T>(start_value + static_cast<T>(i));
|
||||
}
|
||||
dst.back() = start_value;
|
||||
}
|
||||
|
||||
} // namespace Vulkan::LineLoop
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -326,9 +323,44 @@ VkShaderStageFlagBits ShaderStage(Shader::Stage stage) {
|
||||
}
|
||||
|
||||
VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
|
||||
Maxwell::PrimitiveTopology topology,
|
||||
Maxwell::PolygonMode polygon_mode) {
|
||||
return detail::PrimitiveTopologyNoDevice(topology, polygon_mode);
|
||||
Maxwell::PrimitiveTopology topology) {
|
||||
switch (topology) {
|
||||
case Maxwell::PrimitiveTopology::Points:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
case Maxwell::PrimitiveTopology::Lines:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
case Maxwell::PrimitiveTopology::LineLoop:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case Maxwell::PrimitiveTopology::LineStrip:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
case Maxwell::PrimitiveTopology::Triangles:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case Maxwell::PrimitiveTopology::TriangleStrip:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
case Maxwell::PrimitiveTopology::TriangleFan:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
|
||||
case Maxwell::PrimitiveTopology::LinesAdjacency:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
|
||||
case Maxwell::PrimitiveTopology::LineStripAdjacency:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY;
|
||||
case Maxwell::PrimitiveTopology::TrianglesAdjacency:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY;
|
||||
case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY;
|
||||
case Maxwell::PrimitiveTopology::Quads:
|
||||
case Maxwell::PrimitiveTopology::QuadStrip:
|
||||
// TODO: Use VK_PRIMITIVE_TOPOLOGY_QUAD_LIST_EXT/VK_PRIMITIVE_TOPOLOGY_QUAD_STRIP_EXT
|
||||
// whenever it releases
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case Maxwell::PrimitiveTopology::Patches:
|
||||
return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
case Maxwell::PrimitiveTopology::Polygon:
|
||||
LOG_WARNING(Render_Vulkan, "Draw mode is Polygon with a polygon mode of lines should be a "
|
||||
"single body and not a bunch of triangles.");
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented topology={}", topology);
|
||||
return {};
|
||||
}
|
||||
|
||||
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -18,52 +15,6 @@ namespace Vulkan::MaxwellToVK {
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using PixelFormat = VideoCore::Surface::PixelFormat;
|
||||
|
||||
namespace detail {
|
||||
constexpr VkPrimitiveTopology PrimitiveTopologyNoDevice(Maxwell::PrimitiveTopology topology,
|
||||
Maxwell::PolygonMode polygon_mode) {
|
||||
switch (topology) {
|
||||
case Maxwell::PrimitiveTopology::Points:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
case Maxwell::PrimitiveTopology::Lines:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
case Maxwell::PrimitiveTopology::LineLoop:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
case Maxwell::PrimitiveTopology::LineStrip:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
case Maxwell::PrimitiveTopology::Triangles:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case Maxwell::PrimitiveTopology::TriangleStrip:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
case Maxwell::PrimitiveTopology::TriangleFan:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
|
||||
case Maxwell::PrimitiveTopology::LinesAdjacency:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
|
||||
case Maxwell::PrimitiveTopology::LineStripAdjacency:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY;
|
||||
case Maxwell::PrimitiveTopology::TrianglesAdjacency:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY;
|
||||
case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY;
|
||||
case Maxwell::PrimitiveTopology::Quads:
|
||||
case Maxwell::PrimitiveTopology::QuadStrip:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case Maxwell::PrimitiveTopology::Patches:
|
||||
return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
case Maxwell::PrimitiveTopology::Polygon:
|
||||
switch (polygon_mode) {
|
||||
case Maxwell::PolygonMode::Fill:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
|
||||
case Maxwell::PolygonMode::Line:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
case Maxwell::PolygonMode::Point:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
namespace Sampler {
|
||||
|
||||
VkFilter Filter(Tegra::Texture::TextureFilter filter);
|
||||
@@ -95,8 +46,7 @@ struct FormatInfo {
|
||||
|
||||
VkShaderStageFlagBits ShaderStage(Shader::Stage stage);
|
||||
|
||||
VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology,
|
||||
Maxwell::PolygonMode polygon_mode);
|
||||
VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology);
|
||||
|
||||
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
||||
Maxwell::VertexAttribute::Size size);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
@@ -23,7 +22,6 @@
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/polygon_mode_utils.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
@@ -616,10 +614,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
vertex_input_ci.pNext = &input_divisor_ci;
|
||||
}
|
||||
const bool has_tess_stages = spv_modules[1] || spv_modules[2];
|
||||
const auto polygon_mode =
|
||||
FixedPipelineState::UnpackPolygonMode(key.state.polygon_mode.Value());
|
||||
auto input_assembly_topology =
|
||||
MaxwellToVK::PrimitiveTopology(device, key.state.topology, polygon_mode);
|
||||
auto input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, key.state.topology);
|
||||
if (input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) {
|
||||
if (!has_tess_stages) {
|
||||
LOG_WARNING(Render_Vulkan, "Patch topology used without tessellation, using points");
|
||||
@@ -634,33 +629,6 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
}
|
||||
}
|
||||
if (key.state.topology == Maxwell::PrimitiveTopology::Polygon) {
|
||||
const auto polygon_mode_name = [polygon_mode]() -> std::string_view {
|
||||
switch (polygon_mode) {
|
||||
case Maxwell::PolygonMode::Fill:
|
||||
return "Fill";
|
||||
case Maxwell::PolygonMode::Line:
|
||||
return "Line";
|
||||
case Maxwell::PolygonMode::Point:
|
||||
return "Point";
|
||||
}
|
||||
return "Unknown";
|
||||
}();
|
||||
const auto vk_topology_name = [input_assembly_topology]() -> std::string_view {
|
||||
switch (input_assembly_topology) {
|
||||
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
|
||||
return "TriangleFan";
|
||||
case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
|
||||
return "LineStrip";
|
||||
case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
|
||||
return "PointList";
|
||||
default:
|
||||
return "Unexpected";
|
||||
}
|
||||
}();
|
||||
LOG_DEBUG(Render_Vulkan, "Polygon primitive in {} mode mapped to {}", polygon_mode_name,
|
||||
vk_topology_name);
|
||||
}
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/shader_cache.h"
|
||||
#include "video_core/texture_cache/texture_cache_base.h"
|
||||
#include "video_core/polygon_mode_utils.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
@@ -109,7 +108,7 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in
|
||||
|
||||
VkRect2D GetScissorState(const Maxwell& regs, size_t index, u32 up_scale = 1, u32 down_shift = 0) {
|
||||
const auto& src = regs.scissor_test[index];
|
||||
VkRect2D scissor;
|
||||
VkRect2D scissor{};
|
||||
const auto scale_up = [&](s32 value) -> s32 {
|
||||
if (value == 0) {
|
||||
return 0U;
|
||||
@@ -149,8 +148,7 @@ VkRect2D GetScissorState(const Maxwell& regs, size_t index, u32 up_scale = 1, u3
|
||||
return scissor;
|
||||
}
|
||||
|
||||
DrawParams MakeDrawParams(const MaxwellDrawState& draw_state, u32 num_instances, bool is_indexed,
|
||||
Maxwell::PolygonMode polygon_mode) {
|
||||
DrawParams MakeDrawParams(const MaxwellDrawState& draw_state, u32 num_instances, bool is_indexed) {
|
||||
DrawParams params{
|
||||
.base_instance = draw_state.base_instance,
|
||||
.num_instances = num_instances,
|
||||
@@ -170,21 +168,6 @@ DrawParams MakeDrawParams(const MaxwellDrawState& draw_state, u32 num_instances,
|
||||
params.base_vertex = 0;
|
||||
params.is_indexed = true;
|
||||
}
|
||||
const bool polygon_line =
|
||||
draw_state.topology == Maxwell::PrimitiveTopology::Polygon &&
|
||||
polygon_mode == Maxwell::PolygonMode::Line;
|
||||
if (polygon_line) {
|
||||
if (params.is_indexed) {
|
||||
if (draw_state.index_buffer.count > 1) {
|
||||
params.num_vertices = draw_state.index_buffer.count + 1;
|
||||
}
|
||||
} else if (draw_state.vertex_buffer.count > 1) {
|
||||
params.num_vertices = draw_state.vertex_buffer.count + 1;
|
||||
params.is_indexed = true;
|
||||
params.first_index = 0;
|
||||
params.base_vertex = draw_state.vertex_buffer.first;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
@@ -250,8 +233,7 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
||||
PrepareDraw(is_indexed, [this, is_indexed, instance_count] {
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
const u32 num_instances{instance_count};
|
||||
const auto polygon_mode = VideoCore::EffectivePolygonMode(maxwell3d->regs);
|
||||
const DrawParams draw_params{MakeDrawParams(draw_state, num_instances, is_indexed, polygon_mode)};
|
||||
const DrawParams draw_params{MakeDrawParams(draw_state, num_instances, is_indexed)};
|
||||
scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) {
|
||||
if (draw_params.is_indexed) {
|
||||
cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances,
|
||||
@@ -392,7 +374,7 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
||||
}
|
||||
UpdateViewportsState(regs);
|
||||
|
||||
VkRect2D default_scissor;
|
||||
VkRect2D default_scissor{};
|
||||
default_scissor.offset.x = 0;
|
||||
default_scissor.offset.y = 0;
|
||||
default_scissor.extent.width = (std::numeric_limits<s32>::max)();
|
||||
|
||||
@@ -277,7 +277,10 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
|
||||
Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
|
||||
bool via_header_index, u32 raw) {
|
||||
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
|
||||
ASSERT(handle.first <= tic_limit);
|
||||
if (handle.first > tic_limit) {
|
||||
LOG_WARNING(Shader, "Texture ID {} is out of bounds (limit {})", handle.first, tic_limit);
|
||||
return {};
|
||||
}
|
||||
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
|
||||
Tegra::Texture::TICEntry entry;
|
||||
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -71,11 +74,11 @@ private:
|
||||
|
||||
std::unique_ptr<InputProfiles> profiles;
|
||||
|
||||
std::array<ConfigureInputPlayer*, 8> player_controllers;
|
||||
std::array<QWidget*, 8> player_tabs;
|
||||
std::array<ConfigureInputPlayer*, 8> player_controllers{};
|
||||
std::array<QWidget*, 8> player_tabs{};
|
||||
// Checkboxes representing the "Connected Controllers".
|
||||
std::array<QCheckBox*, 8> connected_controller_checkboxes;
|
||||
ConfigureInputAdvanced* advanced;
|
||||
std::array<QCheckBox*, 8> connected_controller_checkboxes{};
|
||||
ConfigureInputAdvanced* advanced = nullptr;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -689,10 +692,10 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen
|
||||
{
|
||||
// Draw joysticks
|
||||
using namespace Settings::NativeAnalog;
|
||||
const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value);
|
||||
const auto l_button = button_values[Settings::NativeButton::LStick];
|
||||
const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value);
|
||||
const auto r_button = button_values[Settings::NativeButton::RStick];
|
||||
const auto& l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value);
|
||||
const auto& l_button = button_values[Settings::NativeButton::LStick];
|
||||
const auto& r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value);
|
||||
const auto& r_button = button_values[Settings::NativeButton::RStick];
|
||||
|
||||
DrawJoystick(p, center + QPointF(-171, -41) + (l_stick * 4), 1.0f, l_button);
|
||||
DrawJoystick(p, center + QPointF(171, 8) + (r_stick * 4), 1.0f, r_button);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -214,7 +217,7 @@ private:
|
||||
|
||||
bool mapping_active{};
|
||||
int blink_counter{};
|
||||
int callback_key;
|
||||
int callback_key{};
|
||||
QColor button_color{};
|
||||
ColorMapping colors{};
|
||||
Core::HID::LedPattern led_pattern{0, 0, 0, 0};
|
||||
|
||||
@@ -1495,7 +1495,7 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
|
||||
(state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) {
|
||||
auto_paused = true;
|
||||
OnPauseGame();
|
||||
} else if (!emu_thread->IsRunning() && auto_paused && state == Qt::ApplicationActive) {
|
||||
} else if (!emu_thread->IsRunning() && auto_paused && (state & Qt::ApplicationActive)) {
|
||||
auto_paused = false;
|
||||
OnStartGame();
|
||||
}
|
||||
@@ -1505,7 +1505,7 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
|
||||
(state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) {
|
||||
Settings::values.audio_muted = true;
|
||||
auto_muted = true;
|
||||
} else if (auto_muted && state == Qt::ApplicationActive) {
|
||||
} else if (auto_muted && (state & Qt::ApplicationActive)) {
|
||||
Settings::values.audio_muted = false;
|
||||
auto_muted = false;
|
||||
}
|
||||
|
||||
@@ -482,13 +482,13 @@ private:
|
||||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
|
||||
GRenderWindow* render_window;
|
||||
GameList* game_list;
|
||||
LoadingScreen* loading_screen;
|
||||
GRenderWindow* render_window = nullptr;
|
||||
GameList* game_list = nullptr;
|
||||
LoadingScreen* loading_screen = nullptr;
|
||||
QTimer shutdown_timer;
|
||||
OverlayDialog* shutdown_dialog{};
|
||||
|
||||
GameListPlaceholder* game_list_placeholder;
|
||||
GameListPlaceholder* game_list_placeholder = nullptr;
|
||||
|
||||
std::vector<VkDeviceInfo::Record> vk_device_records;
|
||||
|
||||
@@ -531,7 +531,7 @@ private:
|
||||
QString startup_icon_theme;
|
||||
|
||||
// Debugger panes
|
||||
ControllerDialog* controller_dialog;
|
||||
ControllerDialog* controller_dialog = nullptr;
|
||||
|
||||
QAction* actions_recent_files[max_recent_files_item];
|
||||
|
||||
@@ -543,7 +543,7 @@ private:
|
||||
QTranslator translator;
|
||||
|
||||
// Install progress dialog
|
||||
QProgressDialog* install_progress;
|
||||
QProgressDialog* install_progress = nullptr;
|
||||
|
||||
// Last game booted, used for multi-process apps
|
||||
QString last_filename_booted;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -175,7 +178,7 @@ public:
|
||||
private:
|
||||
QString username;
|
||||
QString nickname;
|
||||
u64 title_id;
|
||||
u64 title_id{};
|
||||
QString game_name;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ try {
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$VulkanSDKVer = "1.4.328.1"
|
||||
$VulkanSDKVer = "1.4.335.0"
|
||||
$VULKAN_SDK = "C:/VulkanSDK/$VulkanSDKVer"
|
||||
$ExeFile = "vulkansdk-windows-X64-$VulkanSDKVer.exe"
|
||||
$Uri = "https://sdk.lunarg.com/sdk/download/$VulkanSDKVer/windows/$ExeFile"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
: "${VULKAN_SDK_VER:=1.4.328.1}"
|
||||
: "${VULKAN_SDK_VER:=1.4.335.0}"
|
||||
: "${VULKAN_ROOT:=C:/VulkanSDK/$VULKAN_SDK_VER}"
|
||||
EXE_FILE="vulkansdk-windows-X64-$VULKAN_SDK_VER.exe"
|
||||
URI="https://sdk.lunarg.com/sdk/download/$VULKAN_SDK_VER/windows/$EXE_FILE"
|
||||
|
||||
Reference in New Issue
Block a user