diff options
Diffstat (limited to '')
98 files changed, 2962 insertions, 848 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc177fa52..54de1dc94 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -162,6 +162,7 @@ add_subdirectory(video_core) add_subdirectory(network) add_subdirectory(input_common) add_subdirectory(shader_recompiler) +add_subdirectory(dedicated_room) if (YUZU_TESTS) add_subdirectory(tests) diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index a4e28de6d..90d049e8e 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -185,6 +185,9 @@ public: constexpr s32 max{std::numeric_limits<s16>::max()}; auto yuzu_volume{Settings::Volume()}; + if (yuzu_volume > 1.0f) { + yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume); + } auto volume{system_volume * device_volume * yuzu_volume}; if (system_channels == 6 && device_channels == 2) { diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a6dc31b53..635fb85c8 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -124,6 +124,7 @@ add_library(common STATIC settings.h settings_input.cpp settings_input.h + socket_types.h spin_lock.cpp spin_lock.h stream.cpp diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h index 0ad9da2be..cb004e0eb 100644 --- a/src/common/announce_multiplayer_room.h +++ b/src/common/announce_multiplayer_room.h @@ -8,12 +8,11 @@ #include <string> #include <vector> #include "common/common_types.h" +#include "common/socket_types.h" #include "web_service/web_result.h" namespace AnnounceMultiplayerRoom { -using MacAddress = std::array<u8, 6>; - struct GameInfo { std::string name{""}; u64 id{0}; @@ -24,7 +23,7 @@ struct Member { std::string nickname; std::string display_name; std::string avatar_url; - MacAddress mac_address; + Network::IPv4Address fake_ip; GameInfo game; }; @@ -75,10 +74,7 @@ public: const bool has_password, const GameInfo& preferred_game) = 0; /** * Adds a player information to the data that gets announced - * @param nickname The nickname of the player - * @param mac_address The MAC Address of the player - * @param game_id The title id of the game the player plays - * @param game_name The name of the game the player plays + * @param member The player to add */ virtual void AddPlayer(const Member& member) = 0; diff --git a/src/common/microprofile.h b/src/common/microprofile.h index 91d14d5e1..56ef0a2dc 100644 --- a/src/common/microprofile.h +++ b/src/common/microprofile.h @@ -22,12 +22,3 @@ typedef void* HANDLE; #include <microprofile.h> #define MP_RGB(r, g, b) ((r) << 16 | (g) << 8 | (b) << 0) - -// On OS X, some Mach header included by MicroProfile defines these as macros, conflicting with -// identifiers we use. -#ifdef PAGE_SIZE -#undef PAGE_SIZE -#endif -#ifdef PAGE_MASK -#undef PAGE_MASK -#endif diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 1c7b6dfae..7282a45d3 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -105,7 +105,7 @@ float Volume() { if (values.audio_muted) { return 0.0f; } - return values.volume.GetValue() / 100.0f; + return values.volume.GetValue() / static_cast<f32>(values.volume.GetDefault()); } void UpdateRescalingInfo() { diff --git a/src/common/settings.h b/src/common/settings.h index 1079cf8cb..14ed9b237 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -374,7 +374,7 @@ struct Values { Setting<std::string> audio_output_device_id{"auto", "output_device"}; Setting<std::string> audio_input_device_id{"auto", "input_device"}; Setting<bool> audio_muted{false, "audio_muted"}; - SwitchableSetting<u8, true> volume{100, 0, 100, "volume"}; + SwitchableSetting<u8, true> volume{100, 0, 200, "volume"}; Setting<bool> dump_audio_commands{false, "dump_audio_commands"}; // Core diff --git a/src/common/socket_types.h b/src/common/socket_types.h new file mode 100644 index 000000000..0a801a443 --- /dev/null +++ b/src/common/socket_types.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace Network { + +/// Address families +enum class Domain : u8 { + INET, ///< Address family for IPv4 +}; + +/// Socket types +enum class Type { + STREAM, + DGRAM, + RAW, + SEQPACKET, +}; + +/// Protocol values for sockets +enum class Protocol : u8 { + ICMP, + TCP, + UDP, +}; + +/// Shutdown mode +enum class ShutdownHow { + RD, + WR, + RDWR, +}; + +/// Array of IPv4 address +using IPv4Address = std::array<u8, 4>; + +/// Cross-platform sockaddr structure +struct SockAddrIn { + Domain family; + IPv4Address ip; + u16 portno; +}; + +constexpr u32 FLAG_MSG_PEEK = 0x2; +constexpr u32 FLAG_MSG_DONTWAIT = 0x80; +constexpr u32 FLAG_O_NONBLOCK = 0x800; + +} // namespace Network diff --git a/src/common/uint128.h b/src/common/uint128.h index f890ffec2..f450a6db9 100644 --- a/src/common/uint128.h +++ b/src/common/uint128.h @@ -12,7 +12,6 @@ #pragma intrinsic(_udiv128) #else #include <cstring> -#include <x86intrin.h> #endif #include "common/common_types.h" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 052357be4..8db9a3c65 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -502,9 +502,10 @@ add_library(core STATIC hle/service/jit/jit.h hle/service/lbl/lbl.cpp hle/service/lbl/lbl.h - hle/service/ldn/errors.h + hle/service/ldn/ldn_results.h hle/service/ldn/ldn.cpp hle/service/ldn/ldn.h + hle/service/ldn/ldn_types.h hle/service/ldr/ldr.cpp hle/service/ldr/ldr.h hle/service/lm/lm.cpp @@ -723,6 +724,8 @@ add_library(core STATIC internal_network/network_interface.cpp internal_network/network_interface.h internal_network/sockets.h + internal_network/socket_proxy.cpp + internal_network/socket_proxy.h loader/deconstructed_rom_directory.cpp loader/deconstructed_rom_directory.h loader/kip.cpp @@ -783,7 +786,7 @@ endif() create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus) +target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) if (MINGW) target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) endif() diff --git a/src/core/announce_multiplayer_session.cpp b/src/core/announce_multiplayer_session.cpp index d73a488cf..6737ce85a 100644 --- a/src/core/announce_multiplayer_session.cpp +++ b/src/core/announce_multiplayer_session.cpp @@ -31,7 +31,7 @@ AnnounceMultiplayerSession::AnnounceMultiplayerSession(Network::RoomNetwork& roo } WebService::WebResult AnnounceMultiplayerSession::Register() { - std::shared_ptr<Network::Room> room = room_network.GetRoom().lock(); + auto room = room_network.GetRoom().lock(); if (!room) { return WebService::WebResult{WebService::WebResult::Code::LibError, "Network is not initialized", ""}; @@ -102,7 +102,7 @@ void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { // Invokes all current bound error callbacks. const auto ErrorCallback = [this](WebService::WebResult result) { - std::lock_guard<std::mutex> lock(callback_mutex); + std::lock_guard lock(callback_mutex); for (auto callback : error_callbacks) { (*callback)(result); } @@ -120,7 +120,7 @@ void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { std::future<WebService::WebResult> future; while (!shutdown_event.WaitUntil(update_time)) { update_time += announce_time_interval; - std::shared_ptr<Network::Room> room = room_network.GetRoom().lock(); + auto room = room_network.GetRoom().lock(); if (!room) { break; } diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 1638bc41d..d1e70f19d 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -190,19 +190,21 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* config.callbacks = cb.get(); config.coprocessors[15] = cp15; config.define_unpredictable_behaviour = true; - static constexpr std::size_t PAGE_BITS = 12; - static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - PAGE_BITS); + static constexpr std::size_t YUZU_PAGEBITS = 12; + static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - YUZU_PAGEBITS); if (page_table) { config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>( page_table->pointers.data()); + config.absolute_offset_page_table = true; + config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; + config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; + config.only_detect_misalignment_via_page_table_on_page_boundary = true; + config.fastmem_pointer = page_table->fastmem_arena; + + config.fastmem_exclusive_access = config.fastmem_pointer != nullptr; + config.recompile_on_exclusive_fastmem_failure = true; } - config.absolute_offset_page_table = true; - config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; - config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; - config.only_detect_misalignment_via_page_table_on_page_boundary = true; - config.fastmem_exclusive_access = true; - config.recompile_on_exclusive_fastmem_failure = true; // Multi-process state config.processor_id = core_index; @@ -254,6 +256,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* } if (!Settings::values.cpuopt_fastmem) { config.fastmem_pointer = nullptr; + config.fastmem_exclusive_access = false; } if (!Settings::values.cpuopt_fastmem_exclusives) { config.fastmem_exclusive_access = false; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 921a5a734..1d46f6d40 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -250,7 +250,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* config.fastmem_address_space_bits = address_space_bits; config.silently_mirror_fastmem = false; - config.fastmem_exclusive_access = true; + config.fastmem_exclusive_access = config.fastmem_pointer != nullptr; config.recompile_on_exclusive_fastmem_failure = true; } @@ -314,6 +314,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* } if (!Settings::values.cpuopt_fastmem) { config.fastmem_pointer = nullptr; + config.fastmem_exclusive_access = false; } if (!Settings::values.cpuopt_fastmem_exclusives) { config.fastmem_exclusive_access = false; diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp index e9123c13d..200efe4db 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp @@ -8,6 +8,10 @@ #include "core/core.h" #include "core/core_timing.h" +#ifdef _MSC_VER +#include <intrin.h> +#endif + using Callback = Dynarmic::A32::Coprocessor::Callback; using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord; using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords; @@ -47,12 +51,31 @@ CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1 switch (opc2) { case 4: // CP15_DATA_SYNC_BARRIER - // This is a dummy write, we ignore the value written here. - return &dummy_value; + return Callback{ + [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t { +#ifdef _MSC_VER + _mm_mfence(); + _mm_lfence(); +#else + asm volatile("mfence\n\tlfence\n\t" : : : "memory"); +#endif + return 0; + }, + std::nullopt, + }; case 5: // CP15_DATA_MEMORY_BARRIER - // This is a dummy write, we ignore the value written here. - return &dummy_value; + return Callback{ + [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t { +#ifdef _MSC_VER + _mm_mfence(); +#else + asm volatile("mfence\n\t" : : : "memory"); +#endif + return 0; + }, + std::nullopt, + }; } } diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h index 5b2a51636..d90b3e568 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h +++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h @@ -35,6 +35,8 @@ public: ARM_Dynarmic_32& parent; u32 uprw = 0; u32 uro = 0; + + friend class ARM_Dynarmic_32; }; } // namespace Core diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index 4b35ca82f..5aab428bb 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -217,9 +217,7 @@ void IPSwitchCompiler::Parse() { break; } else if (StartsWith(line, "@nsobid-")) { // NSO Build ID Specifier - auto raw_build_id = line.substr(8); - if (raw_build_id.size() != 0x40) - raw_build_id.resize(0x40, '0'); + const auto raw_build_id = fmt::format("{:0<64}", line.substr(8)); nso_build_id = Common::HexStringToArray<0x20>(raw_build_id); } else if (StartsWith(line, "#")) { // Mandatory Comment @@ -287,7 +285,8 @@ void IPSwitchCompiler::Parse() { std::copy(value.begin(), value.end(), std::back_inserter(replace)); } else { // hex replacement - const auto value = patch_line.substr(9); + const auto value = + patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9); replace = Common::HexStringToVector(value, is_little_endian); } diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index bd525b26c..4c80e13a9 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -191,6 +191,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, const std::string& build_id) const { const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto nso_build_id = fmt::format("{:0<64}", build_id); std::vector<VirtualFile> out; out.reserve(patch_dirs.size()); @@ -203,21 +204,18 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD for (const auto& file : exefs_dir->GetFiles()) { if (file->GetExtension() == "ips") { auto name = file->GetName(); - const auto p1 = name.substr(0, name.find('.')); - const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1); - if (build_id == this_build_id) + const auto this_build_id = + fmt::format("{:0<64}", name.substr(0, name.find('.'))); + if (nso_build_id == this_build_id) out.push_back(file); } else if (file->GetExtension() == "pchtxt") { IPSwitchCompiler compiler{file}; if (!compiler.IsValid()) continue; - auto this_build_id = Common::HexToString(compiler.GetBuildID()); - this_build_id = - this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1); - - if (build_id == this_build_id) + const auto this_build_id = Common::HexToString(compiler.GetBuildID()); + if (nso_build_id == this_build_id) out.push_back(file); } } diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 8c3895937..f9f902c2d 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/thread.h" #include "core/hid/emulated_controller.h" #include "core/hid/input_converter.h" @@ -84,23 +85,26 @@ void EmulatedController::ReloadFromSettings() { motion_params[index] = Common::ParamPackage(player.motions[index]); } + controller.colors_state.fullkey = { + .body = GetNpadColor(player.body_color_left), + .button = GetNpadColor(player.button_color_left), + }; controller.colors_state.left = { - .body = player.body_color_left, - .button = player.button_color_left, + .body = GetNpadColor(player.body_color_left), + .button = GetNpadColor(player.button_color_left), }; - - controller.colors_state.right = { - .body = player.body_color_right, - .button = player.button_color_right, + controller.colors_state.left = { + .body = GetNpadColor(player.body_color_right), + .button = GetNpadColor(player.button_color_right), }; - controller.colors_state.fullkey = controller.colors_state.left; - // Other or debug controller should always be a pro controller if (npad_id_type != NpadIdType::Other) { SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); + original_npad_type = npad_type; } else { SetNpadStyleIndex(NpadStyleIndex::ProController); + original_npad_type = npad_type; } if (player.connected) { @@ -352,6 +356,7 @@ void EmulatedController::DisableConfiguration() { Disconnect(); } SetNpadStyleIndex(tmp_npad_type); + original_npad_type = tmp_npad_type; } // Apply temporary connected status to the real controller @@ -949,6 +954,9 @@ bool EmulatedController::TestVibration(std::size_t device_index) { // Send a slight vibration to test for rumble support output_devices[device_index]->SetVibration(test_vibration); + // Wait for about 15ms to ensure the controller is ready for the stop command + std::this_thread::sleep_for(std::chrono::milliseconds(15)); + // Stop any vibration and return the result return output_devices[device_index]->SetVibration(zero_vibration) == Common::Input::VibrationError::None; @@ -999,13 +1007,27 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) if (!is_connected) { return; } + + // Attempt to reconnect with the original type + if (npad_type != original_npad_type) { + Disconnect(); + const auto current_npad_type = npad_type; + SetNpadStyleIndex(original_npad_type); + if (IsControllerSupported()) { + Connect(); + return; + } + SetNpadStyleIndex(current_npad_type); + Connect(); + } + if (IsControllerSupported()) { return; } Disconnect(); - // Fallback fullkey controllers to Pro controllers + // Fallback Fullkey controllers to Pro controllers if (IsControllerFullkey() && supported_style_tag.fullkey) { LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); SetNpadStyleIndex(NpadStyleIndex::ProController); @@ -1013,6 +1035,22 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) return; } + // Fallback Dual joycon controllers to Pro controllers + if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) { + LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); + SetNpadStyleIndex(NpadStyleIndex::ProController); + Connect(); + return; + } + + // Fallback Pro controllers to Dual joycon + if (npad_type == NpadStyleIndex::ProController && supported_style_tag.joycon_dual) { + LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type); + SetNpadStyleIndex(NpadStyleIndex::JoyconDual); + Connect(); + return; + } + LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", npad_type); } @@ -1310,6 +1348,15 @@ const CameraState& EmulatedController::GetCamera() const { return controller.camera_state; } +NpadColor EmulatedController::GetNpadColor(u32 color) { + return { + .r = static_cast<u8>((color >> 16) & 0xFF), + .g = static_cast<u8>((color >> 8) & 0xFF), + .b = static_cast<u8>(color & 0xFF), + .a = 0xff, + }; +} + void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) { std::scoped_lock lock{callback_mutex}; for (const auto& poller_pair : callback_list) { diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index 823c1700c..c3aa8f9d3 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -425,6 +425,13 @@ private: void SetCamera(const Common::Input::CallbackStatus& callback); /** + * Converts a color format from bgra to rgba + * @param color in bgra format + * @return NpadColor in rgba format + */ + NpadColor GetNpadColor(u32 color); + + /** * Triggers a callback that something has changed on the controller status * @param type Input type of the event to trigger * @param is_service_update indicates if this event should only be sent to HID services @@ -433,6 +440,7 @@ private: const NpadIdType npad_id_type; NpadStyleIndex npad_type{NpadStyleIndex::None}; + NpadStyleIndex original_npad_type{NpadStyleIndex::None}; NpadStyleTag supported_style_tag{NpadStyleSet::All}; bool is_connected{false}; bool is_configuring{false}; diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h index e49223016..e3b1cfbc6 100644 --- a/src/core/hid/hid_types.h +++ b/src/core/hid/hid_types.h @@ -327,10 +327,18 @@ struct TouchState { }; static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); +struct NpadColor { + u8 r{}; + u8 g{}; + u8 b{}; + u8 a{}; +}; +static_assert(sizeof(NpadColor) == 4, "NpadColor is an invalid size"); + // This is nn::hid::NpadControllerColor struct NpadControllerColor { - u32 body{}; - u32 button{}; + NpadColor body{}; + NpadColor button{}; }; static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size"); diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 381a66ba5..bc69117c6 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -50,7 +50,7 @@ public: {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"}, {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, - {10, nullptr, "RequestUpdateAuto"}, + {10, &IAudioRenderer::RequestUpdate, "RequestUpdateAuto"}, {11, nullptr, "ExecuteAudioRendererRendering"}, }; // clang-format on @@ -113,15 +113,30 @@ private: // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for // checking size 0. Performance size is 0 for most games. - const auto buffers{ctx.BufferDescriptorB()}; - std::vector<u8> output(buffers[0].Size(), 0); - std::vector<u8> performance(buffers[1].Size(), 0); + + std::vector<u8> output{}; + std::vector<u8> performance{}; + auto is_buffer_b{ctx.BufferDescriptorB()[0].Size() != 0}; + if (is_buffer_b) { + const auto buffersB{ctx.BufferDescriptorB()}; + output.resize(buffersB[0].Size(), 0); + performance.resize(buffersB[1].Size(), 0); + } else { + const auto buffersC{ctx.BufferDescriptorC()}; + output.resize(buffersC[0].Size(), 0); + performance.resize(buffersC[1].Size(), 0); + } auto result = impl->RequestUpdate(input, performance, output); if (result.IsSuccess()) { - ctx.WriteBufferB(output.data(), output.size(), 0); - ctx.WriteBufferB(performance.data(), performance.size(), 1); + if (is_buffer_b) { + ctx.WriteBufferB(output.data(), output.size(), 0); + ctx.WriteBufferB(performance.data(), performance.size(), 1); + } else { + ctx.WriteBufferC(output.data(), output.size(), 0); + ctx.WriteBufferC(performance.data(), performance.size(), 1); + } } else { LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description); } diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index fae6e5aff..e23eae36a 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -246,7 +246,8 @@ static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vec entries.reserve(entries.size() + new_data.size()); for (const auto& new_entry : new_data) { - entries.emplace_back(new_entry->GetName(), type, new_entry->GetSize()); + entries.emplace_back(new_entry->GetName(), type, + type == FileSys::EntryType::Directory ? 0 : new_entry->GetSize()); } } diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 3c28dee76..cb29004e8 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -163,28 +163,51 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { } LOG_DEBUG(Service_HID, "Npad connected {}", npad_id); const auto controller_type = controller.device->GetNpadStyleIndex(); + const auto& body_colors = controller.device->GetColors(); + const auto& battery_level = controller.device->GetBattery(); auto* shared_memory = controller.shared_memory; if (controller_type == Core::HID::NpadStyleIndex::None) { controller.styleset_changed_event->GetWritableEvent().Signal(); return; } + + // Reset memory values shared_memory->style_tag.raw = Core::HID::NpadStyleSet::None; shared_memory->device_type.raw = 0; shared_memory->system_properties.raw = 0; + shared_memory->joycon_color.attribute = ColorAttribute::NoController; + shared_memory->joycon_color.attribute = ColorAttribute::NoController; + shared_memory->fullkey_color = {}; + shared_memory->joycon_color.left = {}; + shared_memory->joycon_color.right = {}; + shared_memory->battery_level_dual = {}; + shared_memory->battery_level_left = {}; + shared_memory->battery_level_right = {}; + switch (controller_type) { case Core::HID::NpadStyleIndex::None: ASSERT(false); break; case Core::HID::NpadStyleIndex::ProController: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->fullkey_color.fullkey = body_colors.fullkey; + shared_memory->battery_level_dual = battery_level.dual.battery_level; shared_memory->style_tag.fullkey.Assign(1); shared_memory->device_type.fullkey.Assign(1); shared_memory->system_properties.is_vertical.Assign(1); shared_memory->system_properties.use_plus.Assign(1); shared_memory->system_properties.use_minus.Assign(1); + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.dual.is_charging); shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::Handheld: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->joycon_color.attribute = ColorAttribute::Ok; + shared_memory->fullkey_color.fullkey = body_colors.fullkey; + shared_memory->joycon_color.left = body_colors.left; + shared_memory->joycon_color.right = body_colors.right; shared_memory->style_tag.handheld.Assign(1); shared_memory->device_type.handheld_left.Assign(1); shared_memory->device_type.handheld_right.Assign(1); @@ -192,47 +215,86 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.use_plus.Assign(1); shared_memory->system_properties.use_minus.Assign(1); shared_memory->system_properties.use_directional_buttons.Assign(1); + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.left.is_charging); + shared_memory->system_properties.is_charging_joy_left.Assign( + battery_level.left.is_charging); + shared_memory->system_properties.is_charging_joy_right.Assign( + battery_level.right.is_charging); shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconDual: + shared_memory->fullkey_color.attribute = ColorAttribute::Ok; + shared_memory->joycon_color.attribute = ColorAttribute::Ok; shared_memory->style_tag.joycon_dual.Assign(1); if (controller.is_dual_left_connected) { + shared_memory->joycon_color.left = body_colors.left; + shared_memory->battery_level_left = battery_level.left.battery_level; shared_memory->device_type.joycon_left.Assign(1); shared_memory->system_properties.use_minus.Assign(1); + shared_memory->system_properties.is_charging_joy_left.Assign( + battery_level.left.is_charging); shared_memory->sixaxis_dual_left_properties.is_newly_assigned.Assign(1); } if (controller.is_dual_right_connected) { + shared_memory->joycon_color.right = body_colors.right; + shared_memory->battery_level_right = battery_level.right.battery_level; shared_memory->device_type.joycon_right.Assign(1); shared_memory->system_properties.use_plus.Assign(1); + shared_memory->system_properties.is_charging_joy_right.Assign( + battery_level.right.is_charging); shared_memory->sixaxis_dual_right_properties.is_newly_assigned.Assign(1); } shared_memory->system_properties.use_directional_buttons.Assign(1); shared_memory->system_properties.is_vertical.Assign(1); shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; + if (controller.is_dual_left_connected && controller.is_dual_right_connected) { shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; + shared_memory->fullkey_color.fullkey = body_colors.left; + shared_memory->battery_level_dual = battery_level.left.battery_level; + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.left.is_charging); } else if (controller.is_dual_left_connected) { shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; + shared_memory->fullkey_color.fullkey = body_colors.left; + shared_memory->battery_level_dual = battery_level.left.battery_level; + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.left.is_charging); } else { shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; + shared_memory->fullkey_color.fullkey = body_colors.right; + shared_memory->battery_level_dual = battery_level.right.battery_level; + shared_memory->system_properties.is_charging_joy_dual.Assign( + battery_level.right.is_charging); } break; case Core::HID::NpadStyleIndex::JoyconLeft: + shared_memory->joycon_color.attribute = ColorAttribute::Ok; + shared_memory->joycon_color.left = body_colors.left; + shared_memory->battery_level_dual = battery_level.left.battery_level; shared_memory->style_tag.joycon_left.Assign(1); shared_memory->device_type.joycon_left.Assign(1); shared_memory->system_properties.is_horizontal.Assign(1); shared_memory->system_properties.use_minus.Assign(1); + shared_memory->system_properties.is_charging_joy_left.Assign( + battery_level.left.is_charging); shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconRight: + shared_memory->joycon_color.attribute = ColorAttribute::Ok; + shared_memory->joycon_color.right = body_colors.right; + shared_memory->battery_level_right = battery_level.right.battery_level; shared_memory->style_tag.joycon_right.Assign(1); shared_memory->device_type.joycon_right.Assign(1); shared_memory->system_properties.is_horizontal.Assign(1); shared_memory->system_properties.use_plus.Assign(1); + shared_memory->system_properties.is_charging_joy_right.Assign( + battery_level.right.is_charging); shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); break; @@ -269,21 +331,6 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { break; } - const auto& body_colors = controller.device->GetColors(); - - shared_memory->fullkey_color.attribute = ColorAttribute::Ok; - shared_memory->fullkey_color.fullkey = body_colors.fullkey; - - shared_memory->joycon_color.attribute = ColorAttribute::Ok; - shared_memory->joycon_color.left = body_colors.left; - shared_memory->joycon_color.right = body_colors.right; - - // TODO: Investigate when we should report all batery types - const auto& battery_level = controller.device->GetBattery(); - shared_memory->battery_level_dual = battery_level.dual.battery_level; - shared_memory->battery_level_left = battery_level.left.battery_level; - shared_memory->battery_level_right = battery_level.right.battery_level; - controller.is_connected = true; controller.device->Connect(); SignalStyleSetChangedEvent(npad_id); diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 5ecbddf94..7909141c0 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2146,12 +2146,18 @@ public: {324, nullptr, "GetUniquePadButtonSet"}, {325, nullptr, "GetUniquePadColor"}, {326, nullptr, "GetUniquePadAppletDetailedUiType"}, + {327, nullptr, "GetAbstractedPadIdDataFromNpad"}, + {328, nullptr, "AttachAbstractedPadToNpad"}, + {329, nullptr, "DetachAbstractedPadAll"}, + {330, nullptr, "CheckAbstractedPadConnection"}, {500, nullptr, "SetAppletResourceUserId"}, {501, nullptr, "RegisterAppletResourceUserId"}, {502, nullptr, "UnregisterAppletResourceUserId"}, {503, nullptr, "EnableAppletToGetInput"}, {504, nullptr, "SetAruidValidForVibration"}, {505, nullptr, "EnableAppletToGetSixAxisSensor"}, + {506, nullptr, "EnableAppletToGetPadInput"}, + {507, nullptr, "EnableAppletToGetTouchScreen"}, {510, nullptr, "SetVibrationMasterVolume"}, {511, nullptr, "GetVibrationMasterVolume"}, {512, nullptr, "BeginPermitVibrationSession"}, diff --git a/src/core/hle/service/ldn/errors.h b/src/core/hle/service/ldn/errors.h deleted file mode 100644 index 972a74806..000000000 --- a/src/core/hle/service/ldn/errors.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/hle/result.h" - -namespace Service::LDN { - -constexpr Result ERROR_DISABLED{ErrorModule::LDN, 22}; - -} // namespace Service::LDN diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index 125d4dc4c..c11daff54 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -3,11 +3,15 @@ #include <memory> -#include "core/hle/ipc_helpers.h" -#include "core/hle/result.h" -#include "core/hle/service/ldn/errors.h" +#include "core/core.h" #include "core/hle/service/ldn/ldn.h" -#include "core/hle/service/sm/sm.h" +#include "core/hle/service/ldn/ldn_results.h" +#include "core/hle/service/ldn/ldn_types.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" + +// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent +#undef CreateEvent namespace Service::LDN { @@ -100,74 +104,418 @@ class IUserLocalCommunicationService final : public ServiceFramework<IUserLocalCommunicationService> { public: explicit IUserLocalCommunicationService(Core::System& system_) - : ServiceFramework{system_, "IUserLocalCommunicationService"} { + : ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew}, + service_context{system, "IUserLocalCommunicationService"}, room_network{ + system_.GetRoomNetwork()} { // clang-format off static const FunctionInfo functions[] = { {0, &IUserLocalCommunicationService::GetState, "GetState"}, - {1, nullptr, "GetNetworkInfo"}, + {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"}, {2, nullptr, "GetIpv4Address"}, - {3, nullptr, "GetDisconnectReason"}, - {4, nullptr, "GetSecurityParameter"}, - {5, nullptr, "GetNetworkConfig"}, - {100, nullptr, "AttachStateChangeEvent"}, - {101, nullptr, "GetNetworkInfoLatestUpdate"}, - {102, nullptr, "Scan"}, - {103, nullptr, "ScanPrivate"}, + {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"}, + {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"}, + {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"}, + {100, &IUserLocalCommunicationService::AttachStateChangeEvent, "AttachStateChangeEvent"}, + {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"}, + {102, &IUserLocalCommunicationService::Scan, "Scan"}, + {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"}, {104, nullptr, "SetWirelessControllerRestriction"}, - {200, nullptr, "OpenAccessPoint"}, - {201, nullptr, "CloseAccessPoint"}, - {202, nullptr, "CreateNetwork"}, - {203, nullptr, "CreateNetworkPrivate"}, - {204, nullptr, "DestroyNetwork"}, + {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"}, + {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"}, + {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"}, + {203, &IUserLocalCommunicationService::CreateNetworkPrivate, "CreateNetworkPrivate"}, + {204, &IUserLocalCommunicationService::DestroyNetwork, "DestroyNetwork"}, {205, nullptr, "Reject"}, - {206, nullptr, "SetAdvertiseData"}, - {207, nullptr, "SetStationAcceptPolicy"}, - {208, nullptr, "AddAcceptFilterEntry"}, + {206, &IUserLocalCommunicationService::SetAdvertiseData, "SetAdvertiseData"}, + {207, &IUserLocalCommunicationService::SetStationAcceptPolicy, "SetStationAcceptPolicy"}, + {208, &IUserLocalCommunicationService::AddAcceptFilterEntry, "AddAcceptFilterEntry"}, {209, nullptr, "ClearAcceptFilter"}, - {300, nullptr, "OpenStation"}, - {301, nullptr, "CloseStation"}, - {302, nullptr, "Connect"}, + {300, &IUserLocalCommunicationService::OpenStation, "OpenStation"}, + {301, &IUserLocalCommunicationService::CloseStation, "CloseStation"}, + {302, &IUserLocalCommunicationService::Connect, "Connect"}, {303, nullptr, "ConnectPrivate"}, - {304, nullptr, "Disconnect"}, - {400, nullptr, "Initialize"}, - {401, nullptr, "Finalize"}, - {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"}, // 7.0.0+ + {304, &IUserLocalCommunicationService::Disconnect, "Disconnect"}, + {400, &IUserLocalCommunicationService::Initialize, "Initialize"}, + {401, &IUserLocalCommunicationService::Finalize, "Finalize"}, + {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"}, }; // clang-format on RegisterHandlers(functions); + + state_change_event = + service_context.CreateEvent("IUserLocalCommunicationService:StateChangeEvent"); + } + + ~IUserLocalCommunicationService() { + service_context.CloseEvent(state_change_event); + } + + void OnEventFired() { + state_change_event->GetWritableEvent().Signal(); } void GetState(Kernel::HLERequestContext& ctx) { + State state = State::Error; + LOG_WARNING(Service_LDN, "(STUBBED) called, state = {}", state); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(state); + } + + void GetNetworkInfo(Kernel::HLERequestContext& ctx) { + const auto write_buffer_size = ctx.GetWriteBufferSize(); + + if (write_buffer_size != sizeof(NetworkInfo)) { + LOG_ERROR(Service_LDN, "Invalid buffer size {}", write_buffer_size); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultBadInput); + return; + } + + NetworkInfo network_info{}; + const auto rc = ResultSuccess; + if (rc.IsError()) { + LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(rc); + return; + } + + LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}", + network_info.common.ssid.GetStringValue(), network_info.ldn.node_count); + + ctx.WriteBuffer<NetworkInfo>(network_info); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(rc); + } + + void GetDisconnectReason(Kernel::HLERequestContext& ctx) { + const auto disconnect_reason = DisconnectReason::None; + + LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(disconnect_reason); + } + + void GetSecurityParameter(Kernel::HLERequestContext& ctx) { + SecurityParameter security_parameter{}; + NetworkInfo info{}; + const Result rc = ResultSuccess; + + if (rc.IsError()) { + LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(rc); + return; + } + + security_parameter.session_id = info.network_id.session_id; + std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(), + sizeof(SecurityParameter::data)); + LOG_WARNING(Service_LDN, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(rc); + rb.PushRaw<SecurityParameter>(security_parameter); + } + + void GetNetworkConfig(Kernel::HLERequestContext& ctx) { + NetworkConfig config{}; + NetworkInfo info{}; + const Result rc = ResultSuccess; + + if (rc.IsError()) { + LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(rc); + return; + } + + config.intent_id = info.network_id.intent_id; + config.channel = info.common.channel; + config.node_count_max = info.ldn.node_count_max; + config.local_communication_version = info.ldn.nodes[0].local_communication_version; + + LOG_WARNING(Service_LDN, + "(STUBBED) called, intent_id={}/{}, channel={}, node_count_max={}, " + "local_communication_version={}", + config.intent_id.local_communication_id, config.intent_id.scene_id, + config.channel, config.node_count_max, config.local_communication_version); + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(rc); + rb.PushRaw<NetworkConfig>(config); + } + + void AttachStateChangeEvent(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_LDN, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(state_change_event->GetReadableEvent()); + } + + void GetNetworkInfoLatestUpdate(Kernel::HLERequestContext& ctx) { + const std::size_t network_buffer_size = ctx.GetWriteBufferSize(0); + const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate); + + if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) { + LOG_ERROR(Service_LDN, "Invalid buffer size {}, {}", network_buffer_size, + node_buffer_count); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultBadInput); + return; + } + + NetworkInfo info; + std::vector<NodeLatestUpdate> latest_update(node_buffer_count); + + const auto rc = ResultSuccess; + if (rc.IsError()) { + LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(rc); + return; + } + + LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}", + info.common.ssid.GetStringValue(), info.ldn.node_count); + + ctx.WriteBuffer(info, 0); + ctx.WriteBuffer(latest_update, 1); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Scan(Kernel::HLERequestContext& ctx) { + ScanImpl(ctx); + } + + void ScanPrivate(Kernel::HLERequestContext& ctx) { + ScanImpl(ctx, true); + } + + void ScanImpl(Kernel::HLERequestContext& ctx, bool is_private = false) { + IPC::RequestParser rp{ctx}; + const auto channel{rp.PopEnum<WifiChannel>()}; + const auto scan_filter{rp.PopRaw<ScanFilter>()}; + + const std::size_t network_info_size = ctx.GetWriteBufferSize() / sizeof(NetworkInfo); + + if (network_info_size == 0) { + LOG_ERROR(Service_LDN, "Invalid buffer size {}", network_info_size); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultBadInput); + return; + } + + u16 count = 0; + std::vector<NetworkInfo> network_infos(network_info_size); + + LOG_WARNING(Service_LDN, + "(STUBBED) called, channel={}, filter_scan_flag={}, filter_network_type={}", + channel, scan_filter.flag, scan_filter.network_type); + + ctx.WriteBuffer(network_infos); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u32>(count); + } + + void OpenAccessPoint(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void CloseAccessPoint(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void CreateNetwork(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + SecurityConfig security_config; + UserConfig user_config; + INSERT_PADDING_WORDS_NOINIT(1); + NetworkConfig network_config; + }; + static_assert(sizeof(Parameters) == 0x98, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_LDN, + "(STUBBED) called, passphrase_size={}, security_mode={}, " + "local_communication_version={}", + parameters.security_config.passphrase_size, + parameters.security_config.security_mode, + parameters.network_config.local_communication_version); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + SecurityConfig security_config; + SecurityParameter security_parameter; + UserConfig user_config; + NetworkConfig network_config; + }; + static_assert(sizeof(Parameters) == 0xB8, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_LDN, + "(STUBBED) called, passphrase_size={}, security_mode={}, " + "local_communication_version={}", + parameters.security_config.passphrase_size, + parameters.security_config.security_mode, + parameters.network_config.local_communication_version); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void DestroyNetwork(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void SetAdvertiseData(Kernel::HLERequestContext& ctx) { + std::vector<u8> read_buffer = ctx.ReadBuffer(); + + LOG_WARNING(Service_LDN, "(STUBBED) called, size {}", read_buffer.size()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } - // Indicate a network error, as we do not actually emulate LDN - rb.Push(static_cast<u32>(State::Error)); + void AddAcceptFilterEntry(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void OpenStation(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void CloseStation(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Connect(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + SecurityConfig security_config; + UserConfig user_config; + u32 local_communication_version; + u32 option; + }; + static_assert(sizeof(Parameters) == 0x7C, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_LDN, + "(STUBBED) called, passphrase_size={}, security_mode={}, " + "local_communication_version={}", + parameters.security_config.passphrase_size, + parameters.security_config.security_mode, + parameters.local_communication_version); + + const std::vector<u8> read_buffer = ctx.ReadBuffer(); + NetworkInfo network_info{}; + + if (read_buffer.size() != sizeof(NetworkInfo)) { + LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultBadInput); + return; + } + + std::memcpy(&network_info, read_buffer.data(), read_buffer.size()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Disconnect(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + void Initialize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + const auto rc = InitializeImpl(ctx); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(rc); + } + + void Finalize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + + is_initialized = false; + + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void Initialize2(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_LDN, "called"); + LOG_WARNING(Service_LDN, "(STUBBED) called"); - is_initialized = true; + const auto rc = InitializeImpl(ctx); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_DISABLED); + rb.Push(rc); + } + + Result InitializeImpl(Kernel::HLERequestContext& ctx) { + const auto network_interface = Network::GetSelectedNetworkInterface(); + if (!network_interface) { + LOG_ERROR(Service_LDN, "No network interface is set"); + return ResultAirplaneModeEnabled; + } + + is_initialized = true; + // TODO (flTobi): Change this to ResultSuccess when LDN is fully implemented + return ResultAirplaneModeEnabled; } -private: - enum class State { - None, - Initialized, - AccessPointOpened, - AccessPointCreated, - StationOpened, - StationConnected, - Error, - }; + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* state_change_event; + Network::RoomNetwork& room_network; bool is_initialized{}; }; @@ -273,7 +621,7 @@ public: LOG_WARNING(Service_LDN, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_DISABLED); + rb.Push(ResultDisabled); } }; diff --git a/src/core/hle/service/ldn/ldn.h b/src/core/hle/service/ldn/ldn.h index a0031ac71..6afe2ea6f 100644 --- a/src/core/hle/service/ldn/ldn.h +++ b/src/core/hle/service/ldn/ldn.h @@ -3,6 +3,12 @@ #pragma once +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/result.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/sm/sm.h" + namespace Core { class System; } diff --git a/src/core/hle/service/ldn/ldn_results.h b/src/core/hle/service/ldn/ldn_results.h new file mode 100644 index 000000000..f340bda42 --- /dev/null +++ b/src/core/hle/service/ldn/ldn_results.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/result.h" + +namespace Service::LDN { + +constexpr Result ResultAdvertiseDataTooLarge{ErrorModule::LDN, 10}; +constexpr Result ResultAuthenticationFailed{ErrorModule::LDN, 20}; +constexpr Result ResultDisabled{ErrorModule::LDN, 22}; +constexpr Result ResultAirplaneModeEnabled{ErrorModule::LDN, 23}; +constexpr Result ResultInvalidNodeCount{ErrorModule::LDN, 30}; +constexpr Result ResultConnectionFailed{ErrorModule::LDN, 31}; +constexpr Result ResultBadState{ErrorModule::LDN, 32}; +constexpr Result ResultNoIpAddress{ErrorModule::LDN, 33}; +constexpr Result ResultInvalidBufferCount{ErrorModule::LDN, 50}; +constexpr Result ResultAccessPointConnectionFailed{ErrorModule::LDN, 65}; +constexpr Result ResultAuthenticationTimeout{ErrorModule::LDN, 66}; +constexpr Result ResultMaximumNodeCount{ErrorModule::LDN, 67}; +constexpr Result ResultBadInput{ErrorModule::LDN, 96}; +constexpr Result ResultLocalCommunicationIdNotFound{ErrorModule::LDN, 97}; +constexpr Result ResultLocalCommunicationVersionTooLow{ErrorModule::LDN, 113}; +constexpr Result ResultLocalCommunicationVersionTooHigh{ErrorModule::LDN, 114}; + +} // namespace Service::LDN diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h new file mode 100644 index 000000000..0c07a7397 --- /dev/null +++ b/src/core/hle/service/ldn/ldn_types.h @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <fmt/format.h> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "network/network.h" + +namespace Service::LDN { + +constexpr size_t SsidLengthMax = 32; +constexpr size_t AdvertiseDataSizeMax = 384; +constexpr size_t UserNameBytesMax = 32; +constexpr int NodeCountMax = 8; +constexpr int StationCountMax = NodeCountMax - 1; +constexpr size_t PassphraseLengthMax = 64; + +enum class SecurityMode : u16 { + All, + Retail, + Debug, +}; + +enum class NodeStateChange : u8 { + None, + Connect, + Disconnect, + DisconnectAndConnect, +}; + +enum class ScanFilterFlag : u32 { + None = 0, + LocalCommunicationId = 1 << 0, + SessionId = 1 << 1, + NetworkType = 1 << 2, + Ssid = 1 << 4, + SceneId = 1 << 5, + IntentId = LocalCommunicationId | SceneId, + NetworkId = IntentId | SessionId, +}; + +enum class NetworkType : u32 { + None, + General, + Ldn, + All, +}; + +enum class PackedNetworkType : u8 { + None, + General, + Ldn, + All, +}; + +enum class State : u32 { + None, + Initialized, + AccessPointOpened, + AccessPointCreated, + StationOpened, + StationConnected, + Error, +}; + +enum class DisconnectReason : s16 { + Unknown = -1, + None, + DisconnectedByUser, + DisconnectedBySystem, + DestroyedByUser, + DestroyedBySystem, + Rejected, + SignalLost, +}; + +enum class NetworkError { + Unknown = -1, + None = 0, + PortUnreachable, + TooManyPlayers, + VersionTooLow, + VersionTooHigh, + ConnectFailure, + ConnectNotFound, + ConnectTimeout, + ConnectRejected, + RejectFailed, +}; + +enum class AcceptPolicy : u8 { + AcceptAll, + RejectAll, + BlackList, + WhiteList, +}; + +enum class WifiChannel : s16 { + Default = 0, + wifi24_1 = 1, + wifi24_6 = 6, + wifi24_11 = 11, + wifi50_36 = 36, + wifi50_40 = 40, + wifi50_44 = 44, + wifi50_48 = 48, +}; + +enum class LinkLevel : s8 { + Bad, + Low, + Good, + Excelent, +}; + +struct NodeLatestUpdate { + NodeStateChange state_change; + INSERT_PADDING_BYTES(0x7); // Unknown +}; +static_assert(sizeof(NodeLatestUpdate) == 0x8, "NodeLatestUpdate is an invalid size"); + +struct SessionId { + u64 high; + u64 low; + + bool operator==(const SessionId&) const = default; +}; +static_assert(sizeof(SessionId) == 0x10, "SessionId is an invalid size"); + +struct IntentId { + u64 local_communication_id; + INSERT_PADDING_BYTES(0x2); // Reserved + u16 scene_id; + INSERT_PADDING_BYTES(0x4); // Reserved +}; +static_assert(sizeof(IntentId) == 0x10, "IntentId is an invalid size"); + +struct NetworkId { + IntentId intent_id; + SessionId session_id; +}; +static_assert(sizeof(NetworkId) == 0x20, "NetworkId is an invalid size"); + +struct Ssid { + u8 length; + std::array<char, SsidLengthMax + 1> raw; + + std::string GetStringValue() const { + return std::string(raw.data(), length); + } +}; +static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size"); + +struct Ipv4Address { + union { + u32 raw{}; + std::array<u8, 4> bytes; + }; + + std::string GetStringValue() const { + return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); + } +}; +static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size"); + +struct MacAddress { + std::array<u8, 6> raw{}; + + friend bool operator==(const MacAddress& lhs, const MacAddress& rhs) = default; +}; +static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size"); + +struct ScanFilter { + NetworkId network_id; + NetworkType network_type; + MacAddress mac_address; + Ssid ssid; + INSERT_PADDING_BYTES(0x10); + ScanFilterFlag flag; +}; +static_assert(sizeof(ScanFilter) == 0x60, "ScanFilter is an invalid size"); + +struct CommonNetworkInfo { + MacAddress bssid; + Ssid ssid; + WifiChannel channel; + LinkLevel link_level; + PackedNetworkType network_type; + INSERT_PADDING_BYTES(0x4); +}; +static_assert(sizeof(CommonNetworkInfo) == 0x30, "CommonNetworkInfo is an invalid size"); + +struct NodeInfo { + Ipv4Address ipv4_address; + MacAddress mac_address; + s8 node_id; + u8 is_connected; + std::array<u8, UserNameBytesMax + 1> user_name; + INSERT_PADDING_BYTES(0x1); // Reserved + s16 local_communication_version; + INSERT_PADDING_BYTES(0x10); // Reserved +}; +static_assert(sizeof(NodeInfo) == 0x40, "NodeInfo is an invalid size"); + +struct LdnNetworkInfo { + std::array<u8, 0x10> security_parameter; + SecurityMode security_mode; + AcceptPolicy station_accept_policy; + u8 has_action_frame; + INSERT_PADDING_BYTES(0x2); // Padding + u8 node_count_max; + u8 node_count; + std::array<NodeInfo, NodeCountMax> nodes; + INSERT_PADDING_BYTES(0x2); // Reserved + u16 advertise_data_size; + std::array<u8, AdvertiseDataSizeMax> advertise_data; + INSERT_PADDING_BYTES(0x8C); // Reserved + u64 random_authentication_id; +}; +static_assert(sizeof(LdnNetworkInfo) == 0x430, "LdnNetworkInfo is an invalid size"); + +struct NetworkInfo { + NetworkId network_id; + CommonNetworkInfo common; + LdnNetworkInfo ldn; +}; +static_assert(sizeof(NetworkInfo) == 0x480, "NetworkInfo is an invalid size"); + +struct SecurityConfig { + SecurityMode security_mode; + u16 passphrase_size; + std::array<u8, PassphraseLengthMax> passphrase; +}; +static_assert(sizeof(SecurityConfig) == 0x44, "SecurityConfig is an invalid size"); + +struct UserConfig { + std::array<u8, UserNameBytesMax + 1> user_name; + INSERT_PADDING_BYTES(0xF); // Reserved +}; +static_assert(sizeof(UserConfig) == 0x30, "UserConfig is an invalid size"); + +#pragma pack(push, 4) +struct ConnectRequest { + SecurityConfig security_config; + UserConfig user_config; + u32 local_communication_version; + u32 option_unknown; + NetworkInfo network_info; +}; +static_assert(sizeof(ConnectRequest) == 0x4FC, "ConnectRequest is an invalid size"); +#pragma pack(pop) + +struct SecurityParameter { + std::array<u8, 0x10> data; // Data, used with the same key derivation as SecurityConfig + SessionId session_id; +}; +static_assert(sizeof(SecurityParameter) == 0x20, "SecurityParameter is an invalid size"); + +struct NetworkConfig { + IntentId intent_id; + WifiChannel channel; + u8 node_count_max; + INSERT_PADDING_BYTES(0x1); // Reserved + u16 local_communication_version; + INSERT_PADDING_BYTES(0xA); // Reserved +}; +static_assert(sizeof(NetworkConfig) == 0x20, "NetworkConfig is an invalid size"); + +struct AddressEntry { + Ipv4Address ipv4_address; + MacAddress mac_address; + INSERT_PADDING_BYTES(0x2); // Reserved +}; +static_assert(sizeof(AddressEntry) == 0xC, "AddressEntry is an invalid size"); + +struct AddressList { + std::array<AddressEntry, 0x8> addresses; +}; +static_assert(sizeof(AddressList) == 0x60, "AddressList is an invalid size"); + +} // namespace Service::LDN diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 2889973e4..e3ef06481 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -6,7 +6,6 @@ #include "core/hle/kernel/k_event.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/nifm/nifm.h" -#include "core/hle/service/service.h" namespace { @@ -271,213 +270,228 @@ public: } }; -class IGeneralService final : public ServiceFramework<IGeneralService> { -public: - explicit IGeneralService(Core::System& system_); +void IGeneralService::GetClientId(Kernel::HLERequestContext& ctx) { + static constexpr u32 client_id = 1; + LOG_WARNING(Service_NIFM, "(STUBBED) called"); -private: - void GetClientId(Kernel::HLERequestContext& ctx) { - static constexpr u32 client_id = 1; - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid +} - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); - rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid - } +void IGeneralService::CreateScanRequest(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "called"); - void CreateScanRequest(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NIFM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IScanRequest>(system); +} - rb.Push(ResultSuccess); - rb.PushIpcInterface<IScanRequest>(system); - } +void IGeneralService::CreateRequest(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "called"); - void CreateRequest(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NIFM, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IRequest>(system); +} - rb.Push(ResultSuccess); - rb.PushIpcInterface<IRequest>(system); - } +void IGeneralService::GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); - void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + const auto net_iface = Network::GetSelectedNetworkInterface(); - const auto net_iface = Network::GetSelectedNetworkInterface(); - - const SfNetworkProfileData network_profile_data = [&net_iface] { - if (!net_iface) { - return SfNetworkProfileData{}; - } - - return SfNetworkProfileData{ - .ip_setting_data{ - .ip_address_setting{ - .is_automatic{true}, - .current_address{Network::TranslateIPv4(net_iface->ip_address)}, - .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)}, - .gateway{Network::TranslateIPv4(net_iface->gateway)}, - }, - .dns_setting{ - .is_automatic{true}, - .primary_dns{1, 1, 1, 1}, - .secondary_dns{1, 0, 0, 1}, - }, - .proxy_setting{ - .enabled{false}, - .port{}, - .proxy_server{}, - .automatic_auth_enabled{}, - .user{}, - .password{}, - }, - .mtu{1500}, + SfNetworkProfileData network_profile_data = [&net_iface] { + if (!net_iface) { + return SfNetworkProfileData{}; + } + + return SfNetworkProfileData{ + .ip_setting_data{ + .ip_address_setting{ + .is_automatic{true}, + .current_address{Network::TranslateIPv4(net_iface->ip_address)}, + .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)}, + .gateway{Network::TranslateIPv4(net_iface->gateway)}, }, - .uuid{0xdeadbeef, 0xdeadbeef}, - .network_name{"yuzu Network"}, - .wireless_setting_data{ - .ssid_length{12}, - .ssid{"yuzu Network"}, - .passphrase{"yuzupassword"}, + .dns_setting{ + .is_automatic{true}, + .primary_dns{1, 1, 1, 1}, + .secondary_dns{1, 0, 0, 1}, }, - }; - }(); - - ctx.WriteBuffer(network_profile_data); + .proxy_setting{ + .enabled{false}, + .port{}, + .proxy_server{}, + .automatic_auth_enabled{}, + .user{}, + .password{}, + }, + .mtu{1500}, + }, + .uuid{0xdeadbeef, 0xdeadbeef}, + .network_name{"yuzu Network"}, + .wireless_setting_data{ + .ssid_length{12}, + .ssid{"yuzu Network"}, + .passphrase{"yuzupassword"}, + }, + }; + }(); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + // When we're connected to a room, spoof the hosts IP address + if (auto room_member = network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { + network_profile_data.ip_setting_data.ip_address_setting.current_address = + room_member->GetFakeIpAddress(); + } } - void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + ctx.WriteBuffer(network_profile_data); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} - void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); +void IGeneralService::RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); - auto ipv4 = Network::GetHostIPv4Address(); - if (!ipv4) { - LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0"); - ipv4.emplace(Network::IPv4Address{0, 0, 0, 0}); - } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushRaw(*ipv4); +void IGeneralService::GetCurrentIpAddress(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + auto ipv4 = Network::GetHostIPv4Address(); + if (!ipv4) { + LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0"); + ipv4.emplace(Network::IPv4Address{0, 0, 0, 0}); } - void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NIFM, "called"); + // When we're connected to a room, spoof the hosts IP address + if (auto room_member = network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { + ipv4 = room_member->GetFakeIpAddress(); + } + } - ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, - "SfNetworkProfileData is not the correct size"); - u128 uuid{}; - auto buffer = ctx.ReadBuffer(); - std::memcpy(&uuid, buffer.data() + 8, sizeof(u128)); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(*ipv4); +} - IPC::ResponseBuilder rb{ctx, 6, 0, 1}; +void IGeneralService::CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NIFM, "called"); - rb.Push(ResultSuccess); - rb.PushIpcInterface<INetworkProfile>(system); - rb.PushRaw<u128>(uuid); - } + ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, "SfNetworkProfileData is not the correct size"); + u128 uuid{}; + auto buffer = ctx.ReadBuffer(); + std::memcpy(&uuid, buffer.data() + 8, sizeof(u128)); - void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 6, 0, 1}; - struct IpConfigInfo { - IpAddressSetting ip_address_setting{}; - DnsSetting dns_setting{}; - }; - static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting), - "IpConfigInfo has incorrect size."); + rb.Push(ResultSuccess); + rb.PushIpcInterface<INetworkProfile>(system); + rb.PushRaw<u128>(uuid); +} - const auto net_iface = Network::GetSelectedNetworkInterface(); +void IGeneralService::GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); - const IpConfigInfo ip_config_info = [&net_iface] { - if (!net_iface) { - return IpConfigInfo{}; - } + struct IpConfigInfo { + IpAddressSetting ip_address_setting{}; + DnsSetting dns_setting{}; + }; + static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting), + "IpConfigInfo has incorrect size."); - return IpConfigInfo{ - .ip_address_setting{ - .is_automatic{true}, - .current_address{Network::TranslateIPv4(net_iface->ip_address)}, - .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)}, - .gateway{Network::TranslateIPv4(net_iface->gateway)}, - }, - .dns_setting{ - .is_automatic{true}, - .primary_dns{1, 1, 1, 1}, - .secondary_dns{1, 0, 0, 1}, - }, - }; - }(); + const auto net_iface = Network::GetSelectedNetworkInterface(); - IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)}; - rb.Push(ResultSuccess); - rb.PushRaw<IpConfigInfo>(ip_config_info); - } + IpConfigInfo ip_config_info = [&net_iface] { + if (!net_iface) { + return IpConfigInfo{}; + } - void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + return IpConfigInfo{ + .ip_address_setting{ + .is_automatic{true}, + .current_address{Network::TranslateIPv4(net_iface->ip_address)}, + .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)}, + .gateway{Network::TranslateIPv4(net_iface->gateway)}, + }, + .dns_setting{ + .is_automatic{true}, + .primary_dns{1, 1, 1, 1}, + .secondary_dns{1, 0, 0, 1}, + }, + }; + }(); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u8>(0); + // When we're connected to a room, spoof the hosts IP address + if (auto room_member = network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { + ip_config_info.ip_address_setting.current_address = room_member->GetFakeIpAddress(); + } } - void GetInternetConnectionStatus(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)}; + rb.Push(ResultSuccess); + rb.PushRaw<IpConfigInfo>(ip_config_info); +} - struct Output { - InternetConnectionType type{InternetConnectionType::WiFi}; - u8 wifi_strength{3}; - InternetConnectionStatus state{InternetConnectionStatus::Connected}; - }; - static_assert(sizeof(Output) == 0x3, "Output has incorrect size."); +void IGeneralService::IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); - constexpr Output out{}; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(1); +} - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushRaw(out); - } +void IGeneralService::GetInternetConnectionStatus(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); - void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + struct Output { + InternetConnectionType type{InternetConnectionType::WiFi}; + u8 wifi_strength{3}; + InternetConnectionStatus state{InternetConnectionStatus::Connected}; + }; + static_assert(sizeof(Output) == 0x3, "Output has incorrect size."); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - if (Network::GetHostIPv4Address().has_value()) { - rb.Push<u8>(1); - } else { - rb.Push<u8>(0); - } + constexpr Output out{}; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(out); +} + +void IGeneralService::IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + if (Network::GetHostIPv4Address().has_value()) { + rb.Push<u8>(1); + } else { + rb.Push<u8>(0); } +} - void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); +void IGeneralService::IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { + LOG_ERROR(Service_NIFM, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - if (Network::GetHostIPv4Address().has_value()) { - rb.Push<u8>(1); - } else { - rb.Push<u8>(0); - } + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + if (Network::GetHostIPv4Address().has_value()) { + rb.Push<u8>(1); + } else { + rb.Push<u8>(0); } -}; +} IGeneralService::IGeneralService(Core::System& system_) - : ServiceFramework{system_, "IGeneralService"} { + : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} { // clang-format off static const FunctionInfo functions[] = { {1, &IGeneralService::GetClientId, "GetClientId"}, @@ -528,6 +542,8 @@ IGeneralService::IGeneralService(Core::System& system_) RegisterHandlers(functions); } +IGeneralService::~IGeneralService() = default; + class NetworkInterface final : public ServiceFramework<NetworkInterface> { public: explicit NetworkInterface(const char* name, Core::System& system_) diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index 5f62d0014..48161be28 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -3,6 +3,11 @@ #pragma once +#include "core/hle/service/service.h" +#include "network/network.h" +#include "network/room.h" +#include "network/room_member.h" + namespace Core { class System; } @@ -16,4 +21,26 @@ namespace Service::NIFM { /// Registers all NIFM services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); +class IGeneralService final : public ServiceFramework<IGeneralService> { +public: + explicit IGeneralService(Core::System& system_); + ~IGeneralService() override; + +private: + void GetClientId(Kernel::HLERequestContext& ctx); + void CreateScanRequest(Kernel::HLERequestContext& ctx); + void CreateRequest(Kernel::HLERequestContext& ctx); + void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx); + void RemoveNetworkProfile(Kernel::HLERequestContext& ctx); + void GetCurrentIpAddress(Kernel::HLERequestContext& ctx); + void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx); + void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx); + void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx); + void GetInternetConnectionStatus(Kernel::HLERequestContext& ctx); + void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx); + void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx); + + Network::RoomNetwork& network; +}; + } // namespace Service::NIFM diff --git a/src/core/hle/service/pcv/pcv.cpp b/src/core/hle/service/pcv/pcv.cpp index 0989474be..f7a497a14 100644 --- a/src/core/hle/service/pcv/pcv.cpp +++ b/src/core/hle/service/pcv/pcv.cpp @@ -3,6 +3,7 @@ #include <memory> +#include "core/hle/ipc_helpers.h" #include "core/hle/service/pcv/pcv.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" @@ -77,10 +78,102 @@ public: } }; +class IClkrstSession final : public ServiceFramework<IClkrstSession> { +public: + explicit IClkrstSession(Core::System& system_, DeviceCode deivce_code_) + : ServiceFramework{system_, "IClkrstSession"}, deivce_code(deivce_code_) { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "SetClockEnabled"}, + {1, nullptr, "SetClockDisabled"}, + {2, nullptr, "SetResetAsserted"}, + {3, nullptr, "SetResetDeasserted"}, + {4, nullptr, "SetPowerEnabled"}, + {5, nullptr, "SetPowerDisabled"}, + {6, nullptr, "GetState"}, + {7, &IClkrstSession::SetClockRate, "SetClockRate"}, + {8, &IClkrstSession::GetClockRate, "GetClockRate"}, + {9, nullptr, "SetMinVClockRate"}, + {10, nullptr, "GetPossibleClockRates"}, + {11, nullptr, "GetDvfsTable"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + void SetClockRate(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + clock_rate = rp.Pop<u32>(); + LOG_DEBUG(Service_PCV, "(STUBBED) called, clock_rate={}", clock_rate); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetClockRate(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PCV, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u32>(clock_rate); + } + + DeviceCode deivce_code; + u32 clock_rate{}; +}; + +class CLKRST final : public ServiceFramework<CLKRST> { +public: + explicit CLKRST(Core::System& system_, const char* name) : ServiceFramework{system_, name} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &CLKRST::OpenSession, "OpenSession"}, + {1, nullptr, "GetTemperatureThresholds"}, + {2, nullptr, "SetTemperature"}, + {3, nullptr, "GetModuleStateTable"}, + {4, nullptr, "GetModuleStateTableEvent"}, + {5, nullptr, "GetModuleStateTableMaxCount"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void OpenSession(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_code = static_cast<DeviceCode>(rp.Pop<u32>()); + const auto unkonwn_input = rp.Pop<u32>(); + + LOG_DEBUG(Service_PCV, "called, device_code={}, input={}", device_code, unkonwn_input); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IClkrstSession>(system, device_code); + } +}; + +class CLKRST_A final : public ServiceFramework<CLKRST_A> { +public: + explicit CLKRST_A(Core::System& system_) : ServiceFramework{system_, "clkrst:a"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "ReleaseControl"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { std::make_shared<PCV>(system)->InstallAsService(sm); std::make_shared<PCV_ARB>(system)->InstallAsService(sm); std::make_shared<PCV_IMM>(system)->InstallAsService(sm); + std::make_shared<CLKRST>(system, "clkrst")->InstallAsService(sm); + std::make_shared<CLKRST>(system, "clkrst:i")->InstallAsService(sm); + std::make_shared<CLKRST_A>(system)->InstallAsService(sm); } } // namespace Service::PCV diff --git a/src/core/hle/service/pcv/pcv.h b/src/core/hle/service/pcv/pcv.h index a42e6f8f6..6b26b6fa7 100644 --- a/src/core/hle/service/pcv/pcv.h +++ b/src/core/hle/service/pcv/pcv.h @@ -13,6 +13,97 @@ class ServiceManager; namespace Service::PCV { +enum class DeviceCode : u32 { + Cpu = 0x40000001, + Gpu = 0x40000002, + I2s1 = 0x40000003, + I2s2 = 0x40000004, + I2s3 = 0x40000005, + Pwm = 0x40000006, + I2c1 = 0x02000001, + I2c2 = 0x02000002, + I2c3 = 0x02000003, + I2c4 = 0x02000004, + I2c5 = 0x02000005, + I2c6 = 0x02000006, + Spi1 = 0x07000000, + Spi2 = 0x07000001, + Spi3 = 0x07000002, + Spi4 = 0x07000003, + Disp1 = 0x40000011, + Disp2 = 0x40000012, + Isp = 0x40000013, + Vi = 0x40000014, + Sdmmc1 = 0x40000015, + Sdmmc2 = 0x40000016, + Sdmmc3 = 0x40000017, + Sdmmc4 = 0x40000018, + Owr = 0x40000019, + Csite = 0x4000001A, + Tsec = 0x4000001B, + Mselect = 0x4000001C, + Hda2codec2x = 0x4000001D, + Actmon = 0x4000001E, + I2cSlow = 0x4000001F, + Sor1 = 0x40000020, + Sata = 0x40000021, + Hda = 0x40000022, + XusbCoreHostSrc = 0x40000023, + XusbFalconSrc = 0x40000024, + XusbFsSrc = 0x40000025, + XusbCoreDevSrc = 0x40000026, + XusbSsSrc = 0x40000027, + UartA = 0x03000001, + UartB = 0x35000405, + UartC = 0x3500040F, + UartD = 0x37000001, + Host1x = 0x4000002C, + Entropy = 0x4000002D, + SocTherm = 0x4000002E, + Vic = 0x4000002F, + Nvenc = 0x40000030, + Nvjpg = 0x40000031, + Nvdec = 0x40000032, + Qspi = 0x40000033, + ViI2c = 0x40000034, + Tsecb = 0x40000035, + Ape = 0x40000036, + AudioDsp = 0x40000037, + AudioUart = 0x40000038, + Emc = 0x40000039, + Plle = 0x4000003A, + PlleHwSeq = 0x4000003B, + Dsi = 0x4000003C, + Maud = 0x4000003D, + Dpaux1 = 0x4000003E, + MipiCal = 0x4000003F, + UartFstMipiCal = 0x40000040, + Osc = 0x40000041, + SysBus = 0x40000042, + SorSafe = 0x40000043, + XusbSs = 0x40000044, + XusbHost = 0x40000045, + XusbDevice = 0x40000046, + Extperiph1 = 0x40000047, + Ahub = 0x40000048, + Hda2hdmicodec = 0x40000049, + Gpuaux = 0x4000004A, + UsbD = 0x4000004B, + Usb2 = 0x4000004C, + Pcie = 0x4000004D, + Afi = 0x4000004E, + PciExClk = 0x4000004F, + PExUsbPhy = 0x40000050, + XUsbPadCtl = 0x40000051, + Apbdma = 0x40000052, + Usb2TrkClk = 0x40000053, + XUsbIoPll = 0x40000054, + XUsbIoPllHwSeq = 0x40000055, + Cec = 0x40000056, + Extperiph2 = 0x40000057, + OscClk = 0x40000080 +}; + void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PCV diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index c7194731e..e08c3cb67 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -9,12 +9,16 @@ #include <fmt/format.h> #include "common/microprofile.h" +#include "common/socket_types.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/service/sockets/bsd.h" #include "core/hle/service/sockets/sockets_translate.h" #include "core/internal_network/network.h" +#include "core/internal_network/socket_proxy.h" #include "core/internal_network/sockets.h" +#include "network/network.h" namespace Service::Sockets { @@ -472,7 +476,13 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco LOG_INFO(Service, "New socket fd={}", fd); - descriptor.socket = std::make_unique<Network::Socket>(); + auto room_member = room_network.GetRoomMember().lock(); + if (room_member && room_member->IsConnected()) { + descriptor.socket = std::make_unique<Network::ProxySocket>(room_network); + } else { + descriptor.socket = std::make_unique<Network::Socket>(); + } + descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol)); descriptor.is_connection_based = IsConnectionBased(type); @@ -648,7 +658,7 @@ std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { ASSERT(arg == 0); return {descriptor.flags, Errno::SUCCESS}; case FcntlCmd::SETFL: { - const bool enable = (arg & FLAG_O_NONBLOCK) != 0; + const bool enable = (arg & Network::FLAG_O_NONBLOCK) != 0; const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable)); if (bsd_errno != Errno::SUCCESS) { return {-1, bsd_errno}; @@ -669,7 +679,7 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con return Errno::BADF; } - Network::Socket* const socket = file_descriptors[fd]->socket.get(); + Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); if (optname == OptName::LINGER) { ASSERT(optlen == sizeof(Linger)); @@ -724,6 +734,8 @@ std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) FileDescriptor& descriptor = *file_descriptors[fd]; // Apply flags + using Network::FLAG_MSG_DONTWAIT; + using Network::FLAG_O_NONBLOCK; if ((flags & FLAG_MSG_DONTWAIT) != 0) { flags &= ~FLAG_MSG_DONTWAIT; if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { @@ -759,6 +771,8 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess } // Apply flags + using Network::FLAG_MSG_DONTWAIT; + using Network::FLAG_O_NONBLOCK; if ((flags & FLAG_MSG_DONTWAIT) != 0) { flags &= ~FLAG_MSG_DONTWAIT; if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { @@ -857,8 +871,19 @@ void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) co rb.PushEnum(bsd_errno); } +void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) { + for (auto& optional_descriptor : file_descriptors) { + if (!optional_descriptor.has_value()) { + continue; + } + FileDescriptor& descriptor = *optional_descriptor; + descriptor.socket.get()->HandleProxyPacket(packet); + } +} + BSD::BSD(Core::System& system_, const char* name) - : ServiceFramework{system_, name, ServiceThreadType::CreateNew} { + : ServiceFramework{system_, name, ServiceThreadType::CreateNew}, room_network{ + system_.GetRoomNetwork()} { // clang-format off static const FunctionInfo functions[] = { {0, &BSD::RegisterClient, "RegisterClient"}, @@ -899,6 +924,13 @@ BSD::BSD(Core::System& system_, const char* name) // clang-format on RegisterHandlers(functions); + + if (auto room_member = room_network.GetRoomMember().lock()) { + proxy_packet_received = room_member->BindOnProxyPacketReceived( + [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); }); + } else { + LOG_ERROR(Service, "Network isn't initalized"); + } } BSD::~BSD() = default; diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 9ea36428d..81e855e0f 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -7,14 +7,17 @@ #include <vector> #include "common/common_types.h" +#include "common/socket_types.h" #include "core/hle/service/service.h" #include "core/hle/service/sockets/sockets.h" +#include "network/network.h" namespace Core { class System; } namespace Network { +class SocketBase; class Socket; } // namespace Network @@ -30,7 +33,7 @@ private: static constexpr size_t MAX_FD = 128; struct FileDescriptor { - std::unique_ptr<Network::Socket> socket; + std::unique_ptr<Network::SocketBase> socket; s32 flags = 0; bool is_connection_based = false; }; @@ -165,6 +168,14 @@ private: void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept; std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors; + + Network::RoomNetwork& room_network; + + /// Callback to parse and handle a received wifi packet. + void OnProxyPacketReceived(const Network::ProxyPacket& packet); + + // Callback identifier for the OnProxyPacketReceived event. + Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received; }; class BSDCFG final : public ServiceFramework<BSDCFG> { diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index b735b00fc..31b7dad33 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -22,7 +22,9 @@ enum class Errno : u32 { AGAIN = 11, INVAL = 22, MFILE = 24, + MSGSIZE = 90, NOTCONN = 107, + TIMEDOUT = 110, }; enum class Domain : u32 { @@ -96,10 +98,6 @@ struct Linger { u32 linger; }; -constexpr u32 FLAG_MSG_DONTWAIT = 0x80; - -constexpr u32 FLAG_O_NONBLOCK = 0x800; - /// Registers all Sockets services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index 2db10ec81..023aa0486 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -25,6 +25,8 @@ Errno Translate(Network::Errno value) { return Errno::MFILE; case Network::Errno::NOTCONN: return Errno::NOTCONN; + case Network::Errno::TIMEDOUT: + return Errno::TIMEDOUT; default: UNIMPLEMENTED_MSG("Unimplemented errno={}", value); return Errno::SUCCESS; diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 36c43cc8f..cdf38a2a4 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -32,6 +32,7 @@ #include "core/internal_network/network.h" #include "core/internal_network/network_interface.h" #include "core/internal_network/sockets.h" +#include "network/network.h" namespace Network { @@ -114,7 +115,10 @@ Errno TranslateNativeError(int e) { return Errno::NETDOWN; case WSAENETUNREACH: return Errno::NETUNREACH; + case WSAEMSGSIZE: + return Errno::MSGSIZE; default: + UNIMPLEMENTED_MSG("Unimplemented errno={}", e); return Errno::OTHER; } } @@ -125,7 +129,6 @@ using SOCKET = int; using WSAPOLLFD = pollfd; using ULONG = u64; -constexpr SOCKET INVALID_SOCKET = -1; constexpr SOCKET SOCKET_ERROR = -1; constexpr int SD_RECEIVE = SHUT_RD; @@ -206,7 +209,10 @@ Errno TranslateNativeError(int e) { return Errno::NETDOWN; case ENETUNREACH: return Errno::NETUNREACH; + case EMSGSIZE: + return Errno::MSGSIZE; default: + UNIMPLEMENTED_MSG("Unimplemented errno={}", e); return Errno::OTHER; } } @@ -329,16 +335,6 @@ PollEvents TranslatePollRevents(short revents) { return result; } -template <typename T> -Errno SetSockOpt(SOCKET fd, int option, T value) { - const int result = - setsockopt(fd, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value)); - if (result != SOCKET_ERROR) { - return Errno::SUCCESS; - } - return GetAndLogLastError(); -} - } // Anonymous namespace NetworkInstance::NetworkInstance() { @@ -350,26 +346,16 @@ NetworkInstance::~NetworkInstance() { } std::optional<IPv4Address> GetHostIPv4Address() { - const std::string& selected_network_interface = Settings::values.network_interface.GetValue(); - const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); - if (network_interfaces.size() == 0) { - LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); + const auto network_interface = Network::GetSelectedNetworkInterface(); + if (!network_interface.has_value()) { + LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); return {}; } - const auto res = - std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) { - return iface.name == selected_network_interface; - }); - - if (res != network_interfaces.end()) { - char ip_addr[16] = {}; - ASSERT(inet_ntop(AF_INET, &res->ip_address, ip_addr, sizeof(ip_addr)) != nullptr); - return TranslateIPv4(res->ip_address); - } else { - LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); - return {}; - } + std::array<char, 16> ip_addr = {}; + ASSERT(inet_ntop(AF_INET, &network_interface->ip_address, ip_addr.data(), sizeof(ip_addr)) != + nullptr); + return TranslateIPv4(network_interface->ip_address); } std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { @@ -412,7 +398,19 @@ Socket::~Socket() { fd = INVALID_SOCKET; } -Socket::Socket(Socket&& rhs) noexcept : fd{std::exchange(rhs.fd, INVALID_SOCKET)} {} +Socket::Socket(Socket&& rhs) noexcept { + fd = std::exchange(rhs.fd, INVALID_SOCKET); +} + +template <typename T> +Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) { + const int result = + setsockopt(fd_, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value)); + if (result != SOCKET_ERROR) { + return Errno::SUCCESS; + } + return GetAndLogLastError(); +} Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol)); @@ -423,7 +421,7 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { return GetAndLogLastError(); } -std::pair<Socket::AcceptResult, Errno> Socket::Accept() { +std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { sockaddr addr; socklen_t addrlen = sizeof(addr); const SOCKET new_socket = accept(fd, &addr, &addrlen); @@ -634,4 +632,8 @@ bool Socket::IsOpened() const { return fd != INVALID_SOCKET; } +void Socket::HandleProxyPacket(const ProxyPacket& packet) { + LOG_WARNING(Network, "ProxyPacket received, but not in Proxy mode!"); +} + } // namespace Network diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index 10e5ef10d..36994c22e 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h @@ -8,6 +8,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/socket_types.h" #ifdef _WIN32 #include <winsock2.h> @@ -17,6 +18,7 @@ namespace Network { +class SocketBase; class Socket; /// Error code for network functions @@ -31,46 +33,11 @@ enum class Errno { HOSTUNREACH, NETDOWN, NETUNREACH, + TIMEDOUT, + MSGSIZE, OTHER, }; -/// Address families -enum class Domain { - INET, ///< Address family for IPv4 -}; - -/// Socket types -enum class Type { - STREAM, - DGRAM, - RAW, - SEQPACKET, -}; - -/// Protocol values for sockets -enum class Protocol { - ICMP, - TCP, - UDP, -}; - -/// Shutdown mode -enum class ShutdownHow { - RD, - WR, - RDWR, -}; - -/// Array of IPv4 address -using IPv4Address = std::array<u8, 4>; - -/// Cross-platform sockaddr structure -struct SockAddrIn { - Domain family; - IPv4Address ip; - u16 portno; -}; - /// Cross-platform poll fd structure enum class PollEvents : u16 { @@ -86,7 +53,7 @@ enum class PollEvents : u16 { DECLARE_ENUM_FLAG_OPERATORS(PollEvents); struct PollFD { - Socket* socket; + SocketBase* socket; PollEvents events; PollEvents revents; }; diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp new file mode 100644 index 000000000..49d067f4c --- /dev/null +++ b/src/core/internal_network/socket_proxy.cpp @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <chrono> +#include <thread> + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/internal_network/network.h" +#include "core/internal_network/network_interface.h" +#include "core/internal_network/socket_proxy.h" + +namespace Network { + +ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {} + +ProxySocket::~ProxySocket() { + if (fd == INVALID_SOCKET) { + return; + } + fd = INVALID_SOCKET; +} + +void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) { + if (protocol != packet.protocol || local_endpoint.portno != packet.remote_endpoint.portno || + closed) { + return; + } + std::lock_guard guard(packets_mutex); + received_packets.push(packet); +} + +template <typename T> +Errno ProxySocket::SetSockOpt(SOCKET fd_, int option, T value) { + LOG_DEBUG(Network, "(STUBBED) called"); + return Errno::SUCCESS; +} + +Errno ProxySocket::Initialize(Domain domain, Type type, Protocol socket_protocol) { + protocol = socket_protocol; + SetSockOpt(fd, SO_TYPE, type); + + return Errno::SUCCESS; +} + +std::pair<ProxySocket::AcceptResult, Errno> ProxySocket::Accept() { + LOG_WARNING(Network, "(STUBBED) called"); + return {AcceptResult{}, Errno::SUCCESS}; +} + +Errno ProxySocket::Connect(SockAddrIn addr_in) { + LOG_WARNING(Network, "(STUBBED) called"); + return Errno::SUCCESS; +} + +std::pair<SockAddrIn, Errno> ProxySocket::GetPeerName() { + LOG_WARNING(Network, "(STUBBED) called"); + return {SockAddrIn{}, Errno::SUCCESS}; +} + +std::pair<SockAddrIn, Errno> ProxySocket::GetSockName() { + LOG_WARNING(Network, "(STUBBED) called"); + return {SockAddrIn{}, Errno::SUCCESS}; +} + +Errno ProxySocket::Bind(SockAddrIn addr) { + if (is_bound) { + LOG_WARNING(Network, "Rebinding Socket is unimplemented!"); + return Errno::SUCCESS; + } + local_endpoint = addr; + is_bound = true; + + return Errno::SUCCESS; +} + +Errno ProxySocket::Listen(s32 backlog) { + LOG_WARNING(Network, "(STUBBED) called"); + return Errno::SUCCESS; +} + +Errno ProxySocket::Shutdown(ShutdownHow how) { + LOG_WARNING(Network, "(STUBBED) called"); + return Errno::SUCCESS; +} + +std::pair<s32, Errno> ProxySocket::Recv(int flags, std::vector<u8>& message) { + LOG_WARNING(Network, "(STUBBED) called"); + ASSERT(flags == 0); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + + return {static_cast<s32>(0), Errno::SUCCESS}; +} + +std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) { + ASSERT(flags == 0); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + + // TODO (flTobi): Verify the timeout behavior and break when connection is lost + const auto timestamp = std::chrono::steady_clock::now(); + // When receive_timeout is set to zero, the socket is supposed to wait indefinitely until a + // packet arrives. In order to prevent lost packets from hanging the emulation thread, we set + // the timeout to 5s instead + const auto timeout = receive_timeout == 0 ? 5000 : receive_timeout; + while (true) { + { + std::lock_guard guard(packets_mutex); + if (received_packets.size() > 0) { + return ReceivePacket(flags, message, addr, message.size()); + } + } + + if (!blocking) { + return {-1, Errno::AGAIN}; + } + + std::this_thread::yield(); + + const auto time_diff = std::chrono::steady_clock::now() - timestamp; + const auto time_diff_ms = + std::chrono::duration_cast<std::chrono::milliseconds>(time_diff).count(); + + if (time_diff_ms > timeout) { + return {-1, Errno::TIMEDOUT}; + } + } +} + +std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& message, + SockAddrIn* addr, std::size_t max_length) { + ProxyPacket& packet = received_packets.front(); + if (addr) { + addr->family = Domain::INET; + addr->ip = packet.local_endpoint.ip; // The senders ip address + addr->portno = packet.local_endpoint.portno; // The senders port number + } + + bool peek = (flags & FLAG_MSG_PEEK) != 0; + std::size_t read_bytes; + if (packet.data.size() > max_length) { + read_bytes = max_length; + message.clear(); + std::copy(packet.data.begin(), packet.data.begin() + read_bytes, + std::back_inserter(message)); + message.resize(max_length); + + if (protocol == Protocol::UDP) { + if (!peek) { + received_packets.pop(); + } + return {-1, Errno::MSGSIZE}; + } else if (protocol == Protocol::TCP) { + std::vector<u8> numArray(packet.data.size() - max_length); + std::copy(packet.data.begin() + max_length, packet.data.end(), + std::back_inserter(numArray)); + packet.data = numArray; + } + } else { + read_bytes = packet.data.size(); + message.clear(); + std::copy(packet.data.begin(), packet.data.end(), std::back_inserter(message)); + message.resize(max_length); + if (!peek) { + received_packets.pop(); + } + } + + return {static_cast<u32>(read_bytes), Errno::SUCCESS}; +} + +std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flags) { + LOG_WARNING(Network, "(STUBBED) called"); + ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); + ASSERT(flags == 0); + + return {static_cast<s32>(0), Errno::SUCCESS}; +} + +void ProxySocket::SendPacket(ProxyPacket& packet) { + if (auto room_member = room_network.GetRoomMember().lock()) { + if (room_member->IsConnected()) { + room_member->SendProxyPacket(packet); + } + } +} + +std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) { + ASSERT(flags == 0); + + if (!is_bound) { + LOG_ERROR(Network, "ProxySocket is not bound!"); + return {static_cast<s32>(message.size()), Errno::SUCCESS}; + } + + if (auto room_member = room_network.GetRoomMember().lock()) { + if (!room_member->IsConnected()) { + return {static_cast<s32>(message.size()), Errno::SUCCESS}; + } + } + + ProxyPacket packet; + packet.local_endpoint = local_endpoint; + packet.remote_endpoint = *addr; + packet.protocol = protocol; + packet.broadcast = broadcast; + + auto& ip = local_endpoint.ip; + auto ipv4 = Network::GetHostIPv4Address(); + // If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address, + // replace it with a "fake" routing address + if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) { + if (auto room_member = room_network.GetRoomMember().lock()) { + packet.local_endpoint.ip = room_member->GetFakeIpAddress(); + } + } + + packet.data.clear(); + std::copy(message.begin(), message.end(), std::back_inserter(packet.data)); + + SendPacket(packet); + + return {static_cast<s32>(message.size()), Errno::SUCCESS}; +} + +Errno ProxySocket::Close() { + fd = INVALID_SOCKET; + closed = true; + + return Errno::SUCCESS; +} + +Errno ProxySocket::SetLinger(bool enable, u32 linger) { + struct Linger { + u16 linger_enable; + u16 linger_time; + } values; + values.linger_enable = enable ? 1 : 0; + values.linger_time = static_cast<u16>(linger); + + return SetSockOpt(fd, SO_LINGER, values); +} + +Errno ProxySocket::SetReuseAddr(bool enable) { + return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0); +} + +Errno ProxySocket::SetBroadcast(bool enable) { + broadcast = enable; + return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0); +} + +Errno ProxySocket::SetSndBuf(u32 value) { + return SetSockOpt(fd, SO_SNDBUF, value); +} + +Errno ProxySocket::SetKeepAlive(bool enable) { + return Errno::SUCCESS; +} + +Errno ProxySocket::SetRcvBuf(u32 value) { + return SetSockOpt(fd, SO_RCVBUF, value); +} + +Errno ProxySocket::SetSndTimeo(u32 value) { + send_timeout = value; + return SetSockOpt(fd, SO_SNDTIMEO, static_cast<int>(value)); +} + +Errno ProxySocket::SetRcvTimeo(u32 value) { + receive_timeout = value; + return SetSockOpt(fd, SO_RCVTIMEO, static_cast<int>(value)); +} + +Errno ProxySocket::SetNonBlock(bool enable) { + blocking = !enable; + return Errno::SUCCESS; +} + +bool ProxySocket::IsOpened() const { + return fd != INVALID_SOCKET; +} + +} // namespace Network diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h new file mode 100644 index 000000000..f12b5f567 --- /dev/null +++ b/src/core/internal_network/socket_proxy.h @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <mutex> +#include <vector> +#include <queue> + +#include "common/common_funcs.h" +#include "core/internal_network/sockets.h" +#include "network/network.h" + +namespace Network { + +class ProxySocket : public SocketBase { +public: + YUZU_NON_COPYABLE(ProxySocket); + YUZU_NON_MOVEABLE(ProxySocket); + + explicit ProxySocket(RoomNetwork& room_network_) noexcept; + ~ProxySocket() override; + + void HandleProxyPacket(const ProxyPacket& packet) override; + + Errno Initialize(Domain domain, Type type, Protocol socket_protocol) override; + + Errno Close() override; + + std::pair<AcceptResult, Errno> Accept() override; + + Errno Connect(SockAddrIn addr_in) override; + + std::pair<SockAddrIn, Errno> GetPeerName() override; + + std::pair<SockAddrIn, Errno> GetSockName() override; + + Errno Bind(SockAddrIn addr) override; + + Errno Listen(s32 backlog) override; + + Errno Shutdown(ShutdownHow how) override; + + std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override; + + std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override; + + std::pair<s32, Errno> ReceivePacket(int flags, std::vector<u8>& message, SockAddrIn* addr, + std::size_t max_length); + + std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override; + + void SendPacket(ProxyPacket& packet); + + std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) override; + + Errno SetLinger(bool enable, u32 linger) override; + + Errno SetReuseAddr(bool enable) override; + + Errno SetBroadcast(bool enable) override; + + Errno SetKeepAlive(bool enable) override; + + Errno SetSndBuf(u32 value) override; + + Errno SetRcvBuf(u32 value) override; + + Errno SetSndTimeo(u32 value) override; + + Errno SetRcvTimeo(u32 value) override; + + Errno SetNonBlock(bool enable) override; + + template <typename T> + Errno SetSockOpt(SOCKET fd, int option, T value); + + bool IsOpened() const override; + +private: + bool broadcast = false; + bool closed = false; + u32 send_timeout = 0; + u32 receive_timeout = 0; + bool is_bound = false; + SockAddrIn local_endpoint{}; + bool blocking = true; + std::queue<ProxyPacket> received_packets; + Protocol protocol; + + std::mutex packets_mutex; + + RoomNetwork& room_network; +}; + +} // namespace Network diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h index 77e27e928..a70429b19 100644 --- a/src/core/internal_network/sockets.h +++ b/src/core/internal_network/sockets.h @@ -14,20 +14,88 @@ #include "common/common_types.h" #include "core/internal_network/network.h" +#include "network/network.h" // TODO: C++20 Replace std::vector usages with std::span namespace Network { -class Socket { +class SocketBase { public: +#ifdef YUZU_UNIX + using SOCKET = int; + static constexpr SOCKET INVALID_SOCKET = -1; + static constexpr SOCKET SOCKET_ERROR = -1; +#endif + struct AcceptResult { - std::unique_ptr<Socket> socket; + std::unique_ptr<SocketBase> socket; SockAddrIn sockaddr_in; }; + virtual ~SocketBase() = default; + + virtual SocketBase& operator=(const SocketBase&) = delete; + + // Avoid closing sockets implicitly + virtual SocketBase& operator=(SocketBase&&) noexcept = delete; + + virtual Errno Initialize(Domain domain, Type type, Protocol protocol) = 0; + + virtual Errno Close() = 0; + + virtual std::pair<AcceptResult, Errno> Accept() = 0; + + virtual Errno Connect(SockAddrIn addr_in) = 0; + + virtual std::pair<SockAddrIn, Errno> GetPeerName() = 0; + + virtual std::pair<SockAddrIn, Errno> GetSockName() = 0; + + virtual Errno Bind(SockAddrIn addr) = 0; + + virtual Errno Listen(s32 backlog) = 0; + + virtual Errno Shutdown(ShutdownHow how) = 0; + + virtual std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) = 0; + + virtual std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, + SockAddrIn* addr) = 0; + + virtual std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) = 0; + + virtual std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) = 0; + + virtual Errno SetLinger(bool enable, u32 linger) = 0; - explicit Socket() = default; - ~Socket(); + virtual Errno SetReuseAddr(bool enable) = 0; + + virtual Errno SetKeepAlive(bool enable) = 0; + + virtual Errno SetBroadcast(bool enable) = 0; + + virtual Errno SetSndBuf(u32 value) = 0; + + virtual Errno SetRcvBuf(u32 value) = 0; + + virtual Errno SetSndTimeo(u32 value) = 0; + + virtual Errno SetRcvTimeo(u32 value) = 0; + + virtual Errno SetNonBlock(bool enable) = 0; + + virtual bool IsOpened() const = 0; + + virtual void HandleProxyPacket(const ProxyPacket& packet) = 0; + + SOCKET fd = INVALID_SOCKET; +}; + +class Socket : public SocketBase { +public: + Socket() = default; + ~Socket() override; Socket(const Socket&) = delete; Socket& operator=(const Socket&) = delete; @@ -37,57 +105,57 @@ public: // Avoid closing sockets implicitly Socket& operator=(Socket&&) noexcept = delete; - Errno Initialize(Domain domain, Type type, Protocol protocol); + Errno Initialize(Domain domain, Type type, Protocol protocol) override; - Errno Close(); + Errno Close() override; - std::pair<AcceptResult, Errno> Accept(); + std::pair<AcceptResult, Errno> Accept() override; - Errno Connect(SockAddrIn addr_in); + Errno Connect(SockAddrIn addr_in) override; - std::pair<SockAddrIn, Errno> GetPeerName(); + std::pair<SockAddrIn, Errno> GetPeerName() override; - std::pair<SockAddrIn, Errno> GetSockName(); + std::pair<SockAddrIn, Errno> GetSockName() override; - Errno Bind(SockAddrIn addr); + Errno Bind(SockAddrIn addr) override; - Errno Listen(s32 backlog); + Errno Listen(s32 backlog) override; - Errno Shutdown(ShutdownHow how); + Errno Shutdown(ShutdownHow how) override; - std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message); + std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override; - std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr); + std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override; - std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags); + std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override; - std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, const SockAddrIn* addr); + std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, + const SockAddrIn* addr) override; - Errno SetLinger(bool enable, u32 linger); + Errno SetLinger(bool enable, u32 linger) override; - Errno SetReuseAddr(bool enable); + Errno SetReuseAddr(bool enable) override; - Errno SetKeepAlive(bool enable); + Errno SetKeepAlive(bool enable) override; - Errno SetBroadcast(bool enable); + Errno SetBroadcast(bool enable) override; - Errno SetSndBuf(u32 value); + Errno SetSndBuf(u32 value) override; - Errno SetRcvBuf(u32 value); + Errno SetRcvBuf(u32 value) override; - Errno SetSndTimeo(u32 value); + Errno SetSndTimeo(u32 value) override; - Errno SetRcvTimeo(u32 value); + Errno SetRcvTimeo(u32 value) override; - Errno SetNonBlock(bool enable); + Errno SetNonBlock(bool enable) override; - bool IsOpened() const; + template <typename T> + Errno SetSockOpt(SOCKET fd, int option, T value); -#if defined(_WIN32) - SOCKET fd = INVALID_SOCKET; -#elif YUZU_UNIX - int fd = -1; -#endif + bool IsOpened() const override; + + void HandleProxyPacket(const ProxyPacket& packet) override; }; std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout); diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp index 9af46a0f7..d8a1bf82a 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp @@ -14,7 +14,7 @@ namespace Loader { namespace { constexpr u32 PageAlignSize(u32 size) { - return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); + return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK); } } // Anonymous namespace diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 1b0bb0876..73d04d7ee 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -125,7 +125,7 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) { } static constexpr u32 PageAlignSize(u32 size) { - return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); + return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK); } static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) { diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 8dd956fc6..4c3b3c655 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -45,7 +45,7 @@ std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, } constexpr u32 PageAlignSize(u32 size) { - return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK); + return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK); } } // Anonymous namespace diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 1b44280b5..34ad7cadd 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -36,10 +36,11 @@ struct Memory::Impl { } void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); + ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", base); ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}", target); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); + MapPages(page_table, base / YUZU_PAGESIZE, size / YUZU_PAGESIZE, target, + Common::PageType::Memory); if (Settings::IsFastmemEnabled()) { system.DeviceMemory().buffer.Map(base, target - DramMemoryMap::Base, size); @@ -47,9 +48,10 @@ struct Memory::Impl { } void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { - ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); - ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); - MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped); + ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size); + ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", base); + MapPages(page_table, base / YUZU_PAGESIZE, size / YUZU_PAGESIZE, 0, + Common::PageType::Unmapped); if (Settings::IsFastmemEnabled()) { system.DeviceMemory().buffer.Unmap(base, size); @@ -57,7 +59,7 @@ struct Memory::Impl { } [[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(VAddr vaddr) const { - const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]}; + const PAddr paddr{current_page_table->backing_addr[vaddr >> YUZU_PAGEBITS]}; if (!paddr) { return {}; @@ -67,7 +69,7 @@ struct Memory::Impl { } [[nodiscard]] u8* GetPointerFromDebugMemory(VAddr vaddr) const { - const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]}; + const PAddr paddr{current_page_table->backing_addr[vaddr >> YUZU_PAGEBITS]}; if (paddr == 0) { return {}; @@ -176,13 +178,14 @@ struct Memory::Impl { auto on_unmapped, auto on_memory, auto on_rasterizer, auto increment) { const auto& page_table = process.PageTable().PageTableImpl(); std::size_t remaining_size = size; - std::size_t page_index = addr >> PAGE_BITS; - std::size_t page_offset = addr & PAGE_MASK; + std::size_t page_index = addr >> YUZU_PAGEBITS; + std::size_t page_offset = addr & YUZU_PAGEMASK; while (remaining_size) { const std::size_t copy_amount = - std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); - const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); + std::min(static_cast<std::size_t>(YUZU_PAGESIZE) - page_offset, remaining_size); + const auto current_vaddr = + static_cast<VAddr>((page_index << YUZU_PAGEBITS) + page_offset); const auto [pointer, type] = page_table.pointers[page_index].PointerType(); switch (type) { @@ -192,7 +195,7 @@ struct Memory::Impl { } case Common::PageType::Memory: { DEBUG_ASSERT(pointer); - u8* mem_ptr = pointer + page_offset + (page_index << PAGE_BITS); + u8* mem_ptr = pointer + page_offset + (page_index << YUZU_PAGEBITS); on_memory(copy_amount, mem_ptr); break; } @@ -339,10 +342,10 @@ struct Memory::Impl { // Iterate over a contiguous CPU address space, marking/unmarking the region. // The region is at a granularity of CPU pages. - const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; - for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { + const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1; + for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) { const Common::PageType page_type{ - current_page_table->pointers[vaddr >> PAGE_BITS].Type()}; + current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Type()}; if (debug) { // Switch page type to debug if now debug switch (page_type) { @@ -354,7 +357,7 @@ struct Memory::Impl { // Page is already marked. break; case Common::PageType::Memory: - current_page_table->pointers[vaddr >> PAGE_BITS].Store( + current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store( nullptr, Common::PageType::DebugMemory); break; default: @@ -371,9 +374,9 @@ struct Memory::Impl { // Don't mess with already non-debug or rasterizer memory. break; case Common::PageType::DebugMemory: { - u8* const pointer{GetPointerFromDebugMemory(vaddr & ~PAGE_MASK)}; - current_page_table->pointers[vaddr >> PAGE_BITS].Store( - pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory); + u8* const pointer{GetPointerFromDebugMemory(vaddr & ~YUZU_PAGEMASK)}; + current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store( + pointer - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory); break; } default: @@ -398,10 +401,10 @@ struct Memory::Impl { // granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size // is different). This assumes the specified GPU address region is contiguous as well. - const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; - for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { + const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1; + for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) { const Common::PageType page_type{ - current_page_table->pointers[vaddr >> PAGE_BITS].Type()}; + current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Type()}; if (cached) { // Switch page type to cached if now cached switch (page_type) { @@ -411,7 +414,7 @@ struct Memory::Impl { break; case Common::PageType::DebugMemory: case Common::PageType::Memory: - current_page_table->pointers[vaddr >> PAGE_BITS].Store( + current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store( nullptr, Common::PageType::RasterizerCachedMemory); break; case Common::PageType::RasterizerCachedMemory: @@ -434,16 +437,16 @@ struct Memory::Impl { // that this area is already unmarked as cached. break; case Common::PageType::RasterizerCachedMemory: { - u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~PAGE_MASK)}; + u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~YUZU_PAGEMASK)}; if (pointer == nullptr) { // It's possible that this function has been called while updating the // pagetable after unmapping a VMA. In that case the underlying VMA will no // longer exist, and we should just leave the pagetable entry blank. - current_page_table->pointers[vaddr >> PAGE_BITS].Store( + current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store( nullptr, Common::PageType::Unmapped); } else { - current_page_table->pointers[vaddr >> PAGE_BITS].Store( - pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory); + current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Store( + pointer - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory); } break; } @@ -465,8 +468,8 @@ struct Memory::Impl { */ void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target, Common::PageType type) { - LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", target, base * PAGE_SIZE, - (base + size) * PAGE_SIZE); + LOG_DEBUG(HW_Memory, "Mapping {:016X} onto {:016X}-{:016X}", target, base * YUZU_PAGESIZE, + (base + size) * YUZU_PAGESIZE); // During boot, current_page_table might not be set yet, in which case we need not flush if (system.IsPoweredOn()) { @@ -474,7 +477,7 @@ struct Memory::Impl { for (u64 i = 0; i < size; i++) { const auto page = base + i; if (page_table.pointers[page].Type() == Common::PageType::RasterizerCachedMemory) { - gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE); + gpu.FlushAndInvalidateRegion(page << YUZU_PAGEBITS, YUZU_PAGESIZE); } } } @@ -485,7 +488,7 @@ struct Memory::Impl { if (!target) { ASSERT_MSG(type != Common::PageType::Memory, - "Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE); + "Mapping memory page without a pointer @ {:016x}", base * YUZU_PAGESIZE); while (base != end) { page_table.pointers[base].Store(nullptr, type); @@ -496,14 +499,14 @@ struct Memory::Impl { } else { while (base != end) { page_table.pointers[base].Store( - system.DeviceMemory().GetPointer(target) - (base << PAGE_BITS), type); - page_table.backing_addr[base] = target - (base << PAGE_BITS); + system.DeviceMemory().GetPointer(target) - (base << YUZU_PAGEBITS), type); + page_table.backing_addr[base] = target - (base << YUZU_PAGEBITS); ASSERT_MSG(page_table.pointers[base].Pointer(), "memory mapping base yield a nullptr within the table"); base += 1; - target += PAGE_SIZE; + target += YUZU_PAGESIZE; } } } @@ -518,7 +521,7 @@ struct Memory::Impl { } // Avoid adding any extra logic to this fast-path block - const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw(); + const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> YUZU_PAGEBITS].Raw(); if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) { return &pointer[vaddr]; } @@ -657,7 +660,7 @@ void Memory::UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { bool Memory::IsValidVirtualAddress(const VAddr vaddr) const { const Kernel::KProcess& process = *system.CurrentProcess(); const auto& page_table = process.PageTable().PageTableImpl(); - const size_t page = vaddr >> PAGE_BITS; + const size_t page = vaddr >> YUZU_PAGEBITS; if (page >= page_table.pointers.size()) { return false; } @@ -668,9 +671,9 @@ bool Memory::IsValidVirtualAddress(const VAddr vaddr) const { bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const { VAddr end = base + size; - VAddr page = Common::AlignDown(base, PAGE_SIZE); + VAddr page = Common::AlignDown(base, YUZU_PAGESIZE); - for (; page < end; page += PAGE_SIZE) { + for (; page < end; page += YUZU_PAGESIZE) { if (!IsValidVirtualAddress(page)) { return false; } diff --git a/src/core/memory.h b/src/core/memory.h index 2a21fbcfd..a11ff8766 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -27,9 +27,9 @@ namespace Core::Memory { * Page size used by the ARM architecture. This is the smallest granularity with which memory can * be mapped. */ -constexpr std::size_t PAGE_BITS = 12; -constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS; -constexpr u64 PAGE_MASK = PAGE_SIZE - 1; +constexpr std::size_t YUZU_PAGEBITS = 12; +constexpr u64 YUZU_PAGESIZE = 1ULL << YUZU_PAGEBITS; +constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1; /// Virtual user-space memory regions enum : VAddr { diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt new file mode 100644 index 000000000..b674b915b --- /dev/null +++ b/src/dedicated_room/CMakeLists.txt @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2017 Citra Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) + +add_executable(yuzu-room + yuzu_room.cpp + yuzu_room.rc +) + +create_target_directory_groups(yuzu-room) + +target_link_libraries(yuzu-room PRIVATE common core network) +if (ENABLE_WEB_SERVICE) + target_compile_definitions(yuzu-room PRIVATE -DENABLE_WEB_SERVICE) + target_link_libraries(yuzu-room PRIVATE web_service) +endif() + +target_link_libraries(yuzu-room PRIVATE mbedtls) +if (MSVC) + target_link_libraries(yuzu-room PRIVATE getopt) +endif() +target_link_libraries(yuzu-room PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) + +if(UNIX AND NOT APPLE) + install(TARGETS yuzu-room RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") +endif() diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp new file mode 100644 index 000000000..482e772fb --- /dev/null +++ b/src/dedicated_room/yuzu_room.cpp @@ -0,0 +1,375 @@ +// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <chrono> +#include <fstream> +#include <iostream> +#include <memory> +#include <regex> +#include <string> +#include <thread> + +#ifdef _WIN32 +// windows.h needs to be included before shellapi.h +#include <windows.h> + +#include <shellapi.h> +#endif + +#include <mbedtls/base64.h> +#include "common/common_types.h" +#include "common/detached_tasks.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/announce_multiplayer_session.h" +#include "core/core.h" +#include "network/network.h" +#include "network/room.h" +#include "network/verify_user.h" + +#ifdef ENABLE_WEB_SERVICE +#include "web_service/verify_user_jwt.h" +#endif + +#undef _UNICODE +#include <getopt.h> +#ifndef _MSC_VER +#include <unistd.h> +#endif + +static void PrintHelp(const char* argv0) { + LOG_INFO(Network, + "Usage: {}" + " [options] <filename>\n" + "--room-name The name of the room\n" + "--room-description The room description\n" + "--port The port used for the room\n" + "--max_members The maximum number of players for this room\n" + "--password The password for the room\n" + "--preferred-game The preferred game for this room\n" + "--preferred-game-id The preferred game-id for this room\n" + "--username The username used for announce\n" + "--token The token used for announce\n" + "--web-api-url yuzu Web API url\n" + "--ban-list-file The file for storing the room ban list\n" + "--log-file The file for storing the room log\n" + "--enable-yuzu-mods Allow yuzu Community Moderators to moderate on your room\n" + "-h, --help Display this help and exit\n" + "-v, --version Output version information and exit\n", + argv0); +} + +static void PrintVersion() { + LOG_INFO(Network, "yuzu dedicated room {} {} Libnetwork: {}", Common::g_scm_branch, + Common::g_scm_desc, Network::network_version); +} + +/// The magic text at the beginning of a yuzu-room ban list file. +static constexpr char BanListMagic[] = "YuzuRoom-BanList-1"; + +static constexpr char token_delimiter{':'}; + +static std::string UsernameFromDisplayToken(const std::string& display_token) { + std::size_t outlen; + + std::array<unsigned char, 512> output{}; + mbedtls_base64_decode(output.data(), output.size(), &outlen, + reinterpret_cast<const unsigned char*>(display_token.c_str()), + display_token.length()); + std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen); + return decoded_display_token.substr(0, decoded_display_token.find(token_delimiter)); +} + +static std::string TokenFromDisplayToken(const std::string& display_token) { + std::size_t outlen; + + std::array<unsigned char, 512> output{}; + mbedtls_base64_decode(output.data(), output.size(), &outlen, + reinterpret_cast<const unsigned char*>(display_token.c_str()), + display_token.length()); + std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen); + return decoded_display_token.substr(decoded_display_token.find(token_delimiter) + 1); +} + +static Network::Room::BanList LoadBanList(const std::string& path) { + std::ifstream file; + Common::FS::OpenFileStream(file, path, std::ios_base::in); + if (!file || file.eof()) { + LOG_ERROR(Network, "Could not open ban list!"); + return {}; + } + std::string magic; + std::getline(file, magic); + if (magic != BanListMagic) { + LOG_ERROR(Network, "Ban list is not valid!"); + return {}; + } + + // false = username ban list, true = ip ban list + bool ban_list_type = false; + Network::Room::UsernameBanList username_ban_list; + Network::Room::IPBanList ip_ban_list; + while (!file.eof()) { + std::string line; + std::getline(file, line); + line.erase(std::remove(line.begin(), line.end(), '\0'), line.end()); + line = Common::StripSpaces(line); + if (line.empty()) { + // An empty line marks start of the IP ban list + ban_list_type = true; + continue; + } + if (ban_list_type) { + ip_ban_list.emplace_back(line); + } else { + username_ban_list.emplace_back(line); + } + } + + return {username_ban_list, ip_ban_list}; +} + +static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) { + std::ofstream file; + Common::FS::OpenFileStream(file, path, std::ios_base::out); + if (!file) { + LOG_ERROR(Network, "Could not save ban list!"); + return; + } + + file << BanListMagic << "\n"; + + // Username ban list + for (const auto& username : ban_list.first) { + file << username << "\n"; + } + file << "\n"; + + // IP ban list + for (const auto& ip : ban_list.second) { + file << ip << "\n"; + } +} + +static void InitializeLogging(const std::string& log_file) { + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); + Common::Log::Start(); +} + +/// Application entry point +int main(int argc, char** argv) { + Common::DetachedTasks detached_tasks; + int option_index = 0; + char* endarg; + + std::string room_name; + std::string room_description; + std::string password; + std::string preferred_game; + std::string username; + std::string token; + std::string web_api_url; + std::string ban_list_file; + std::string log_file = "yuzu-room.log"; + u64 preferred_game_id = 0; + u32 port = Network::DefaultRoomPort; + u32 max_members = 16; + bool enable_yuzu_mods = false; + + static struct option long_options[] = { + {"room-name", required_argument, 0, 'n'}, + {"room-description", required_argument, 0, 'd'}, + {"port", required_argument, 0, 'p'}, + {"max_members", required_argument, 0, 'm'}, + {"password", required_argument, 0, 'w'}, + {"preferred-game", required_argument, 0, 'g'}, + {"preferred-game-id", required_argument, 0, 'i'}, + {"username", optional_argument, 0, 'u'}, + {"token", required_argument, 0, 't'}, + {"web-api-url", required_argument, 0, 'a'}, + {"ban-list-file", required_argument, 0, 'b'}, + {"log-file", required_argument, 0, 'l'}, + {"enable-yuzu-mods", no_argument, 0, 'e'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0}, + }; + + InitializeLogging(log_file); + + while (optind < argc) { + int arg = getopt_long(argc, argv, "n:d:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index); + if (arg != -1) { + switch (static_cast<char>(arg)) { + case 'n': + room_name.assign(optarg); + break; + case 'd': + room_description.assign(optarg); + break; + case 'p': + port = strtoul(optarg, &endarg, 0); + break; + case 'm': + max_members = strtoul(optarg, &endarg, 0); + break; + case 'w': + password.assign(optarg); + break; + case 'g': + preferred_game.assign(optarg); + break; + case 'i': + preferred_game_id = strtoull(optarg, &endarg, 16); + break; + case 'u': + username.assign(optarg); + break; + case 't': + token.assign(optarg); + break; + case 'a': + web_api_url.assign(optarg); + break; + case 'b': + ban_list_file.assign(optarg); + break; + case 'l': + log_file.assign(optarg); + break; + case 'e': + enable_yuzu_mods = true; + break; + case 'h': + PrintHelp(argv[0]); + return 0; + case 'v': + PrintVersion(); + return 0; + } + } + } + + if (room_name.empty()) { + LOG_ERROR(Network, "Room name is empty!"); + PrintHelp(argv[0]); + return -1; + } + if (preferred_game.empty()) { + LOG_ERROR(Network, "Preferred game is empty!"); + PrintHelp(argv[0]); + return -1; + } + if (preferred_game_id == 0) { + LOG_ERROR(Network, + "preferred-game-id not set!\nThis should get set to allow users to find your " + "room.\nSet with --preferred-game-id id"); + } + if (max_members > Network::MaxConcurrentConnections || max_members < 2) { + LOG_ERROR(Network, "max_members needs to be in the range 2 - {}!", + Network::MaxConcurrentConnections); + PrintHelp(argv[0]); + return -1; + } + if (port > UINT16_MAX) { + LOG_ERROR(Network, "Port needs to be in the range 0 - 65535!"); + PrintHelp(argv[0]); + return -1; + } + if (ban_list_file.empty()) { + LOG_ERROR(Network, "Ban list file not set!\nThis should get set to load and save room ban " + "list.\nSet with --ban-list-file <file>"); + } + bool announce = true; + if (token.empty() && announce) { + announce = false; + LOG_INFO(Network, "Token is empty: Hosting a private room"); + } + if (web_api_url.empty() && announce) { + announce = false; + LOG_INFO(Network, "Endpoint url is empty: Hosting a private room"); + } + if (announce) { + if (username.empty()) { + LOG_INFO(Network, "Hosting a public room"); + Settings::values.web_api_url = web_api_url; + Settings::values.yuzu_username = UsernameFromDisplayToken(token); + username = Settings::values.yuzu_username.GetValue(); + Settings::values.yuzu_token = TokenFromDisplayToken(token); + } else { + LOG_INFO(Network, "Hosting a public room"); + Settings::values.web_api_url = web_api_url; + Settings::values.yuzu_username = username; + Settings::values.yuzu_token = token; + } + } + if (!announce && enable_yuzu_mods) { + enable_yuzu_mods = false; + LOG_INFO(Network, "Can not enable yuzu Moderators for private rooms"); + } + + // Load the ban list + Network::Room::BanList ban_list; + if (!ban_list_file.empty()) { + ban_list = LoadBanList(ban_list_file); + } + + std::unique_ptr<Network::VerifyUser::Backend> verify_backend; + if (announce) { +#ifdef ENABLE_WEB_SERVICE + verify_backend = + std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue()); +#else + LOG_INFO(Network, + "yuzu Web Services is not available with this build: validation is disabled."); + verify_backend = std::make_unique<Network::VerifyUser::NullBackend>(); +#endif + } else { + verify_backend = std::make_unique<Network::VerifyUser::NullBackend>(); + } + + Network::RoomNetwork network{}; + network.Init(); + if (auto room = network.GetRoom().lock()) { + AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, + .id = preferred_game_id}; + if (!room->Create(room_name, room_description, "", port, password, max_members, username, + preferred_game_info, std::move(verify_backend), ban_list, + enable_yuzu_mods)) { + LOG_INFO(Network, "Failed to create room: "); + return -1; + } + LOG_INFO(Network, "Room is open. Close with Q+Enter..."); + auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>(network); + if (announce) { + announce_session->Start(); + } + while (room->GetState() == Network::Room::State::Open) { + std::string in; + std::cin >> in; + if (in.size() > 0) { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + if (announce) { + announce_session->Stop(); + } + announce_session.reset(); + // Save the ban list + if (!ban_list_file.empty()) { + SaveBanList(room->GetBanList(), ban_list_file); + } + room->Destroy(); + } + network.Shutdown(); + detached_tasks.WaitForAllTasks(); + return 0; +} diff --git a/src/dedicated_room/yuzu_room.rc b/src/dedicated_room/yuzu_room.rc new file mode 100644 index 000000000..a08957684 --- /dev/null +++ b/src/dedicated_room/yuzu_room.rc @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "winresrc.h" +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +YUZU_ICON ICON "../../dist/yuzu.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +0 RT_MANIFEST "../../dist/yuzu.manifest" diff --git a/src/network/room.cpp b/src/network/room.cpp index 3fc3a0383..b06797bf1 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -20,9 +20,7 @@ namespace Network { class Room::RoomImpl { public: - // This MAC address is used to generate a 'Nintendo' like Mac address. - const MacAddress NintendoOUI; - std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress + std::mt19937 random_gen; ///< Random number generator. Used for GenerateFakeIPAddress ENetHost* server = nullptr; ///< Network interface. @@ -35,10 +33,9 @@ public: std::string password; ///< The password required to connect to this room. struct Member { - std::string nickname; ///< The nickname of the member. - std::string console_id_hash; ///< A hash of the console ID of the member. - GameInfo game_info; ///< The current game of the member - MacAddress mac_address; ///< The assigned mac address of the member. + std::string nickname; ///< The nickname of the member. + GameInfo game_info; ///< The current game of the member + IPv4Address fake_ip; ///< The assigned fake ip address of the member. /// Data of the user, often including authenticated forum username. VerifyUser::UserData user_data; ENetPeer* peer; ///< The remote peer. @@ -51,8 +48,7 @@ public: IPBanList ip_ban_list; ///< List of banned IP addresses mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists - RoomImpl() - : NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00}, random_gen(std::random_device()()) {} + RoomImpl() : random_gen(std::random_device()()) {} /// Thread that receives and dispatches network packets std::unique_ptr<std::thread> room_thread; @@ -101,16 +97,10 @@ public: bool IsValidNickname(const std::string& nickname) const; /** - * Returns whether the MAC address is valid, ie. isn't already taken by someone else in the + * Returns whether the fake ip address is valid, ie. isn't already taken by someone else in the * room. */ - bool IsValidMacAddress(const MacAddress& address) const; - - /** - * Returns whether the console ID (hash) is valid, ie. isn't already taken by someone else in - * the room. - */ - bool IsValidConsoleId(const std::string& console_id_hash) const; + bool IsValidFakeIPAddress(const IPv4Address& address) const; /** * Returns whether a user has mod permissions. @@ -128,15 +118,9 @@ public: void SendNameCollision(ENetPeer* client); /** - * Sends a ID_ROOM_MAC_COLLISION message telling the client that the MAC is invalid. + * Sends a ID_ROOM_IP_COLLISION message telling the client that the IP is invalid. */ - void SendMacCollision(ENetPeer* client); - - /** - * Sends a IdConsoleIdCollison message telling the client that another member with the same - * console ID exists. - */ - void SendConsoleIdCollision(ENetPeer* client); + void SendIPCollision(ENetPeer* client); /** * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid. @@ -152,13 +136,13 @@ public: * Notifies the member that its connection attempt was successful, * and it is now part of the room. */ - void SendJoinSuccess(ENetPeer* client, MacAddress mac_address); + void SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip); /** * Notifies the member that its connection attempt was successful, * and it is now part of the room, and it has been granted mod permissions. */ - void SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address); + void SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip); /** * Sends a IdHostKicked message telling the client that they have been kicked. @@ -210,7 +194,7 @@ public: * <u32> num_members: the number of currently joined clients * This is followed by the following three values for each member: * <String> nickname of that member - * <MacAddress> mac_address of that member + * <IPv4Address> fake_ip of that member * <String> game_name of that member */ void BroadcastRoomInformation(); @@ -219,13 +203,13 @@ public: * Generates a free MAC address to assign to a new client. * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32 */ - MacAddress GenerateMacAddress(); + IPv4Address GenerateFakeIPAddress(); /** * Broadcasts this packet to all members except the sender. * @param event The ENet event containing the data */ - void HandleWifiPacket(const ENetEvent* event); + void HandleProxyPacket(const ENetEvent* event); /** * Extracts a chat entry from a received ENet packet and adds it to the chat queue. @@ -250,7 +234,7 @@ public: void Room::RoomImpl::ServerLoop() { while (state != State::Closed) { ENetEvent event; - if (enet_host_service(server, &event, 16) > 0) { + if (enet_host_service(server, &event, 50) > 0) { switch (event.type) { case ENET_EVENT_TYPE_RECEIVE: switch (event.packet->data[0]) { @@ -260,8 +244,8 @@ void Room::RoomImpl::ServerLoop() { case IdSetGameInfo: HandleGameNamePacket(&event); break; - case IdWifiPacket: - HandleWifiPacket(&event); + case IdProxyPacket: + HandleProxyPacket(&event); break; case IdChatMessage: HandleChatPacket(&event); @@ -313,11 +297,8 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { std::string nickname; packet.Read(nickname); - std::string console_id_hash; - packet.Read(console_id_hash); - - MacAddress preferred_mac; - packet.Read(preferred_mac); + IPv4Address preferred_fake_ip; + packet.Read(preferred_fake_ip); u32 client_version; packet.Read(client_version); @@ -338,20 +319,15 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { return; } - if (preferred_mac != NoPreferredMac) { - // Verify if the preferred mac is available - if (!IsValidMacAddress(preferred_mac)) { - SendMacCollision(event->peer); + if (preferred_fake_ip != NoPreferredIP) { + // Verify if the preferred fake ip is available + if (!IsValidFakeIPAddress(preferred_fake_ip)) { + SendIPCollision(event->peer); return; } } else { - // Assign a MAC address of this client automatically - preferred_mac = GenerateMacAddress(); - } - - if (!IsValidConsoleId(console_id_hash)) { - SendConsoleIdCollision(event->peer); - return; + // Assign a fake ip address of this client automatically + preferred_fake_ip = GenerateFakeIPAddress(); } if (client_version != network_version) { @@ -361,8 +337,7 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { // At this point the client is ready to be added to the room. Member member{}; - member.mac_address = preferred_mac; - member.console_id_hash = console_id_hash; + member.fake_ip = preferred_fake_ip; member.nickname = nickname; member.peer = event->peer; @@ -408,9 +383,9 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { // Notify everyone that the room information has changed. BroadcastRoomInformation(); if (HasModPermission(event->peer)) { - SendJoinSuccessAsMod(event->peer, preferred_mac); + SendJoinSuccessAsMod(event->peer, preferred_fake_ip); } else { - SendJoinSuccess(event->peer, preferred_mac); + SendJoinSuccess(event->peer, preferred_fake_ip); } } @@ -575,19 +550,11 @@ bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const { [&nickname](const auto& member) { return member.nickname != nickname; }); } -bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const { - // A MAC address is valid if it is not already taken by anybody else in the room. +bool Room::RoomImpl::IsValidFakeIPAddress(const IPv4Address& address) const { + // An IP address is valid if it is not already taken by anybody else in the room. std::lock_guard lock(member_mutex); return std::all_of(members.begin(), members.end(), - [&address](const auto& member) { return member.mac_address != address; }); -} - -bool Room::RoomImpl::IsValidConsoleId(const std::string& console_id_hash) const { - // A Console ID is valid if it is not already taken by anybody else in the room. - std::lock_guard lock(member_mutex); - return std::all_of(members.begin(), members.end(), [&console_id_hash](const auto& member) { - return member.console_id_hash != console_id_hash; - }); + [&address](const auto& member) { return member.fake_ip != address; }); } bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const { @@ -621,19 +588,9 @@ void Room::RoomImpl::SendNameCollision(ENetPeer* client) { enet_host_flush(server); } -void Room::RoomImpl::SendMacCollision(ENetPeer* client) { +void Room::RoomImpl::SendIPCollision(ENetPeer* client) { Packet packet; - packet.Write(static_cast<u8>(IdMacCollision)); - - ENetPacket* enet_packet = - enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); - enet_peer_send(client, 0, enet_packet); - enet_host_flush(server); -} - -void Room::RoomImpl::SendConsoleIdCollision(ENetPeer* client) { - Packet packet; - packet.Write(static_cast<u8>(IdConsoleIdCollision)); + packet.Write(static_cast<u8>(IdIpCollision)); ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); @@ -672,20 +629,20 @@ void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) { enet_host_flush(server); } -void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) { +void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip) { Packet packet; packet.Write(static_cast<u8>(IdJoinSuccess)); - packet.Write(mac_address); + packet.Write(fake_ip); ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); enet_peer_send(client, 0, enet_packet); enet_host_flush(server); } -void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address) { +void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip) { Packet packet; packet.Write(static_cast<u8>(IdJoinSuccessAsMod)); - packet.Write(mac_address); + packet.Write(fake_ip); ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); enet_peer_send(client, 0, enet_packet); @@ -818,7 +775,7 @@ void Room::RoomImpl::BroadcastRoomInformation() { std::lock_guard lock(member_mutex); for (const auto& member : members) { packet.Write(member.nickname); - packet.Write(member.mac_address); + packet.Write(member.fake_ip); packet.Write(member.game_info.name); packet.Write(member.game_info.id); packet.Write(member.user_data.username); @@ -833,34 +790,43 @@ void Room::RoomImpl::BroadcastRoomInformation() { enet_host_flush(server); } -MacAddress Room::RoomImpl::GenerateMacAddress() { - MacAddress result_mac = - NintendoOUI; // The first three bytes of each MAC address will be the NintendoOUI - std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF +IPv4Address Room::RoomImpl::GenerateFakeIPAddress() { + IPv4Address result_ip{192, 168, 0, 0}; + std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE do { - for (std::size_t i = 3; i < result_mac.size(); ++i) { - result_mac[i] = dis(random_gen); + for (std::size_t i = 2; i < result_ip.size(); ++i) { + result_ip[i] = dis(random_gen); } - } while (!IsValidMacAddress(result_mac)); - return result_mac; + } while (!IsValidFakeIPAddress(result_ip)); + + return result_ip; } -void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { +void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) { Packet in_packet; in_packet.Append(event->packet->data, event->packet->dataLength); - in_packet.IgnoreBytes(sizeof(u8)); // Message type - in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Type - in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Channel - in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address - MacAddress destination_address; - in_packet.Read(destination_address); + in_packet.IgnoreBytes(sizeof(u8)); // Message type + + in_packet.IgnoreBytes(sizeof(u8)); // Domain + in_packet.IgnoreBytes(sizeof(IPv4Address)); // IP + in_packet.IgnoreBytes(sizeof(u16)); // Port + + in_packet.IgnoreBytes(sizeof(u8)); // Domain + IPv4Address remote_ip; + in_packet.Read(remote_ip); // IP + in_packet.IgnoreBytes(sizeof(u16)); // Port + + in_packet.IgnoreBytes(sizeof(u8)); // Protocol + bool broadcast; + in_packet.Read(broadcast); // Broadcast Packet out_packet; out_packet.Append(event->packet->data, event->packet->dataLength); ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); - if (destination_address == BroadcastMac) { // Send the data to everyone except the sender + const auto& destination_address = remote_ip; + if (broadcast) { // Send the data to everyone except the sender std::lock_guard lock(member_mutex); bool sent_packet = false; for (const auto& member : members) { @@ -877,16 +843,16 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { std::lock_guard lock(member_mutex); auto member = std::find_if(members.begin(), members.end(), [destination_address](const Member& member_entry) -> bool { - return member_entry.mac_address == destination_address; + return member_entry.fake_ip == destination_address; }); if (member != members.end()) { enet_peer_send(member->peer, 0, enet_packet); } else { LOG_ERROR(Network, - "Attempting to send to unknown MAC address: " - "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + "Attempting to send to unknown IP address: " + "{}.{}.{}.{}", destination_address[0], destination_address[1], destination_address[2], - destination_address[3], destination_address[4], destination_address[5]); + destination_address[3]); enet_packet_destroy(enet_packet); } } @@ -1073,7 +1039,7 @@ std::vector<Member> Room::GetRoomMemberList() const { member.username = member_impl.user_data.username; member.display_name = member_impl.user_data.display_name; member.avatar_url = member_impl.user_data.avatar_url; - member.mac_address = member_impl.mac_address; + member.fake_ip = member_impl.fake_ip; member.game = member_impl.game_info; member_list.push_back(member); } diff --git a/src/network/room.h b/src/network/room.h index 6f7e3b5b5..c2a4b1a70 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -9,12 +9,12 @@ #include <vector> #include "common/announce_multiplayer_room.h" #include "common/common_types.h" +#include "common/socket_types.h" #include "network/verify_user.h" namespace Network { using AnnounceMultiplayerRoom::GameInfo; -using AnnounceMultiplayerRoom::MacAddress; using AnnounceMultiplayerRoom::Member; using AnnounceMultiplayerRoom::RoomInformation; @@ -29,12 +29,9 @@ static constexpr u32 MaxConcurrentConnections = 254; constexpr std::size_t NumChannels = 1; // Number of channels used for the connection -/// A special MAC address that tells the room we're joining to assign us a MAC address +/// A special IP address that tells the room we're joining to assign us a IP address /// automatically. -constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - -// 802.11 broadcast MAC address -constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +constexpr IPv4Address NoPreferredIP = {0xFF, 0xFF, 0xFF, 0xFF}; // The different types of messages that can be sent. The first byte of each packet defines the type enum RoomMessageTypes : u8 { @@ -42,15 +39,14 @@ enum RoomMessageTypes : u8 { IdJoinSuccess, IdRoomInformation, IdSetGameInfo, - IdWifiPacket, + IdProxyPacket, IdChatMessage, IdNameCollision, - IdMacCollision, + IdIpCollision, IdVersionMismatch, IdWrongPassword, IdCloseRoom, IdRoomIsFull, - IdConsoleIdCollision, IdStatusMessage, IdHostKicked, IdHostBanned, diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index e4f823e98..9f08bf611 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -7,6 +7,7 @@ #include <set> #include <thread> #include "common/assert.h" +#include "common/socket_types.h" #include "enet/enet.h" #include "network/packet.h" #include "network/room_member.h" @@ -38,7 +39,7 @@ public: std::string username; ///< The username of this member. mutable std::mutex username_mutex; ///< Mutex for locking username. - MacAddress mac_address; ///< The mac_address of this member. + IPv4Address fake_ip; ///< The fake ip of this member. std::mutex network_mutex; ///< Mutex that controls access to the `client` variable. /// Thread that receives and dispatches network packets @@ -56,7 +57,7 @@ public: CallbackSet<T>& Get(); private: - CallbackSet<WifiPacket> callback_set_wifi_packet; + CallbackSet<ProxyPacket> callback_set_proxy_packet; CallbackSet<ChatEntry> callback_set_chat_messages; CallbackSet<StatusMessageEntry> callback_set_status_messages; CallbackSet<RoomInformation> callback_set_room_information; @@ -78,15 +79,15 @@ public: /** * Sends a request to the server, asking for permission to join a room with the specified - * nickname and preferred mac. + * nickname and preferred fake ip. * @params nickname The desired nickname. - * @params console_id_hash A hash of the Console ID. - * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells + * @params preferred_fake_ip The preferred IP address to use in the room, the NoPreferredIP + * tells * @params password The password for the room * the server to assign one for us. */ - void SendJoinRequest(const std::string& nickname_, const std::string& console_id_hash, - const MacAddress& preferred_mac = NoPreferredMac, + void SendJoinRequest(const std::string& nickname_, + const IPv4Address& preferred_fake_ip = NoPreferredIP, const std::string& password = "", const std::string& token = ""); /** @@ -101,10 +102,10 @@ public: void HandleRoomInformationPacket(const ENetEvent* event); /** - * Extracts a WifiPacket from a received ENet packet. + * Extracts a ProxyPacket from a received ENet packet. * @param event The ENet event that was received. */ - void HandleWifiPackets(const ENetEvent* event); + void HandleProxyPackets(const ENetEvent* event); /** * Extracts a chat entry from a received ENet packet and adds it to the chat queue. @@ -158,12 +159,12 @@ void RoomMember::RoomMemberImpl::MemberLoop() { while (IsConnected()) { std::lock_guard lock(network_mutex); ENetEvent event; - if (enet_host_service(client, &event, 16) > 0) { + if (enet_host_service(client, &event, 100) > 0) { switch (event.type) { case ENET_EVENT_TYPE_RECEIVE: switch (event.packet->data[0]) { - case IdWifiPacket: - HandleWifiPackets(&event); + case IdProxyPacket: + HandleProxyPackets(&event); break; case IdChatMessage: HandleChatPacket(&event); @@ -198,13 +199,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() { SetState(State::Idle); SetError(Error::NameCollision); break; - case IdMacCollision: + case IdIpCollision: SetState(State::Idle); - SetError(Error::MacCollision); - break; - case IdConsoleIdCollision: - SetState(State::Idle); - SetError(Error::ConsoleIdCollision); + SetError(Error::IpCollision); break; case IdVersionMismatch: SetState(State::Idle); @@ -275,15 +272,13 @@ void RoomMember::RoomMemberImpl::Send(Packet&& packet) { } void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_, - const std::string& console_id_hash, - const MacAddress& preferred_mac, + const IPv4Address& preferred_fake_ip, const std::string& password, const std::string& token) { Packet packet; packet.Write(static_cast<u8>(IdJoinRequest)); packet.Write(nickname_); - packet.Write(console_id_hash); - packet.Write(preferred_mac); + packet.Write(preferred_fake_ip); packet.Write(network_version); packet.Write(password); packet.Write(token); @@ -317,7 +312,7 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev for (auto& member : member_information) { packet.Read(member.nickname); - packet.Read(member.mac_address); + packet.Read(member.fake_ip); packet.Read(member.game_info.name); packet.Read(member.game_info.id); packet.Read(member.username); @@ -342,29 +337,38 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { packet.IgnoreBytes(sizeof(u8)); // Ignore the message type // Parse the MAC Address from the packet - packet.Read(mac_address); + packet.Read(fake_ip); } -void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { - WifiPacket wifi_packet{}; +void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) { + ProxyPacket proxy_packet{}; Packet packet; packet.Append(event->packet->data, event->packet->dataLength); // Ignore the first byte, which is the message id. packet.IgnoreBytes(sizeof(u8)); // Ignore the message type - // Parse the WifiPacket from the packet - u8 frame_type; - packet.Read(frame_type); - WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type); + // Parse the ProxyPacket from the packet + u8 local_family; + packet.Read(local_family); + proxy_packet.local_endpoint.family = static_cast<Domain>(local_family); + packet.Read(proxy_packet.local_endpoint.ip); + packet.Read(proxy_packet.local_endpoint.portno); + + u8 remote_family; + packet.Read(remote_family); + proxy_packet.remote_endpoint.family = static_cast<Domain>(remote_family); + packet.Read(proxy_packet.remote_endpoint.ip); + packet.Read(proxy_packet.remote_endpoint.portno); + + u8 protocol_type; + packet.Read(protocol_type); + proxy_packet.protocol = static_cast<Protocol>(protocol_type); - wifi_packet.type = type; - packet.Read(wifi_packet.channel); - packet.Read(wifi_packet.transmitter_address); - packet.Read(wifi_packet.destination_address); - packet.Read(wifi_packet.data); + packet.Read(proxy_packet.broadcast); + packet.Read(proxy_packet.data); - Invoke<WifiPacket>(wifi_packet); + Invoke<ProxyPacket>(proxy_packet); } void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { @@ -440,8 +444,8 @@ void RoomMember::RoomMemberImpl::Disconnect() { } template <> -RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() { - return callback_set_wifi_packet; +RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_proxy_packet; } template <> @@ -525,19 +529,18 @@ const std::string& RoomMember::GetUsername() const { return room_member_impl->username; } -const MacAddress& RoomMember::GetMacAddress() const { - ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected"); - return room_member_impl->mac_address; +const IPv4Address& RoomMember::GetFakeIpAddress() const { + ASSERT_MSG(IsConnected(), "Tried to get fake ip address while not connected"); + return room_member_impl->fake_ip; } RoomInformation RoomMember::GetRoomInformation() const { return room_member_impl->room_information; } -void RoomMember::Join(const std::string& nick, const std::string& console_id_hash, - const char* server_addr, u16 server_port, u16 client_port, - const MacAddress& preferred_mac, const std::string& password, - const std::string& token) { +void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port, + u16 client_port, const IPv4Address& preferred_fake_ip, + const std::string& password, const std::string& token) { // If the member is connected, kill the connection first if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) { Leave(); @@ -571,7 +574,7 @@ void RoomMember::Join(const std::string& nick, const std::string& console_id_has if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { room_member_impl->nickname = nick; room_member_impl->StartLoop(); - room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password, token); + room_member_impl->SendJoinRequest(nick, preferred_fake_ip, password, token); SendGameInfo(room_member_impl->current_game_info); } else { enet_peer_disconnect(room_member_impl->server, 0); @@ -584,14 +587,22 @@ bool RoomMember::IsConnected() const { return room_member_impl->IsConnected(); } -void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) { +void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) { Packet packet; - packet.Write(static_cast<u8>(IdWifiPacket)); - packet.Write(static_cast<u8>(wifi_packet.type)); - packet.Write(wifi_packet.channel); - packet.Write(wifi_packet.transmitter_address); - packet.Write(wifi_packet.destination_address); - packet.Write(wifi_packet.data); + packet.Write(static_cast<u8>(IdProxyPacket)); + + packet.Write(static_cast<u8>(proxy_packet.local_endpoint.family)); + packet.Write(proxy_packet.local_endpoint.ip); + packet.Write(proxy_packet.local_endpoint.portno); + + packet.Write(static_cast<u8>(proxy_packet.remote_endpoint.family)); + packet.Write(proxy_packet.remote_endpoint.ip); + packet.Write(proxy_packet.remote_endpoint.portno); + + packet.Write(static_cast<u8>(proxy_packet.protocol)); + packet.Write(proxy_packet.broadcast); + packet.Write(proxy_packet.data); + room_member_impl->Send(std::move(packet)); } @@ -645,8 +656,8 @@ RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError( return room_member_impl->Bind(callback); } -RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived( - std::function<void(const WifiPacket&)> callback) { +RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived( + std::function<void(const ProxyPacket&)> callback) { return room_member_impl->Bind(callback); } @@ -685,7 +696,7 @@ void RoomMember::Leave() { room_member_impl->client = nullptr; } -template void RoomMember::Unbind(CallbackHandle<WifiPacket>); +template void RoomMember::Unbind(CallbackHandle<ProxyPacket>); template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>); template void RoomMember::Unbind(CallbackHandle<RoomInformation>); diff --git a/src/network/room_member.h b/src/network/room_member.h index bbb7d13d4..4252b7146 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -9,6 +9,7 @@ #include <vector> #include "common/announce_multiplayer_room.h" #include "common/common_types.h" +#include "common/socket_types.h" #include "network/room.h" namespace Network { @@ -17,22 +18,12 @@ using AnnounceMultiplayerRoom::GameInfo; using AnnounceMultiplayerRoom::RoomInformation; /// Information about the received WiFi packets. -/// Acts as our own 802.11 header. -struct WifiPacket { - enum class PacketType : u8 { - Beacon, - Data, - Authentication, - AssociationResponse, - Deauthentication, - NodeMap - }; - PacketType type; ///< The type of 802.11 frame. - std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header - /// for management frames. - MacAddress transmitter_address; ///< Mac address of the transmitter. - MacAddress destination_address; ///< Mac address of the receiver. - u8 channel; ///< WiFi channel where this frame was transmitted. +struct ProxyPacket { + SockAddrIn local_endpoint; + SockAddrIn remote_endpoint; + Protocol protocol; + bool broadcast; + std::vector<u8> data; }; /// Represents a chat message. @@ -72,15 +63,14 @@ public: HostKicked, ///< Kicked by the host // Reasons why connection was rejected - UnknownError, ///< Some error [permissions to network device missing or something] - NameCollision, ///< Somebody is already using this name - MacCollision, ///< Somebody is already using that mac-address - ConsoleIdCollision, ///< Somebody in the room has the same Console ID - WrongVersion, ///< The room version is not the same as for this RoomMember - WrongPassword, ///< The password doesn't match the one from the Room - CouldNotConnect, ///< The room is not responding to a connection attempt - RoomIsFull, ///< Room is already at the maximum number of players - HostBanned, ///< The user is banned by the host + UnknownError, ///< Some error [permissions to network device missing or something] + NameCollision, ///< Somebody is already using this name + IpCollision, ///< Somebody is already using that fake-ip-address + WrongVersion, ///< The room version is not the same as for this RoomMember + WrongPassword, ///< The password doesn't match the one from the Room + CouldNotConnect, ///< The room is not responding to a connection attempt + RoomIsFull, ///< Room is already at the maximum number of players + HostBanned, ///< The user is banned by the host // Reasons why moderation request failed PermissionDenied, ///< The user does not have mod permissions @@ -92,9 +82,9 @@ public: std::string username; ///< The web services username of the member. Can be empty. std::string display_name; ///< The web services display name of the member. Can be empty. std::string avatar_url; ///< Url to the member's avatar. Can be empty. - GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're - /// not playing anything. - MacAddress mac_address; ///< MAC address associated with this member. + GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're + /// not playing anything. + IPv4Address fake_ip; ///< Fake Ip address associated with this member. }; using MemberList = std::vector<MemberInformation>; @@ -135,7 +125,7 @@ public: /** * Returns the MAC address of the RoomMember. */ - const MacAddress& GetMacAddress() const; + const IPv4Address& GetFakeIpAddress() const; /** * Returns information about the room we're currently connected to. @@ -149,19 +139,17 @@ public: /** * Attempts to join a room at the specified address and port, using the specified nickname. - * A console ID hash is passed in to check console ID conflicts. - * This may fail if the username or console ID is already taken. */ - void Join(const std::string& nickname, const std::string& console_id_hash, - const char* server_addr = "127.0.0.1", u16 server_port = DefaultRoomPort, - u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac, + void Join(const std::string& nickname, const char* server_addr = "127.0.0.1", + u16 server_port = DefaultRoomPort, u16 client_port = 0, + const IPv4Address& preferred_fake_ip = NoPreferredIP, const std::string& password = "", const std::string& token = ""); /** * Sends a WiFi packet to the room. * @param packet The WiFi packet to send. */ - void SendWifiPacket(const WifiPacket& packet); + void SendProxyPacket(const ProxyPacket& packet); /** * Sends a chat message to the room. @@ -207,14 +195,14 @@ public: CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback); /** - * Binds a function to an event that will be triggered every time a WifiPacket is received. + * Binds a function to an event that will be triggered every time a ProxyPacket is received. * The function wil be called everytime the event is triggered. * The callback function must not bind or unbind a function. Doing so will cause a deadlock * @param callback The function to call * @return A handle used for removing the function from the registered list */ - CallbackHandle<WifiPacket> BindOnWifiPacketReceived( - std::function<void(const WifiPacket&)> callback); + CallbackHandle<ProxyPacket> BindOnProxyPacketReceived( + std::function<void(const ProxyPacket&)> callback); /** * Binds a function to an event that will be triggered every time the RoomInformation changes. @@ -292,10 +280,8 @@ inline const char* GetErrorStr(const RoomMember::Error& e) { return "UnknownError"; case RoomMember::Error::NameCollision: return "NameCollision"; - case RoomMember::Error::MacCollision: - return "MaxCollision"; - case RoomMember::Error::ConsoleIdCollision: - return "ConsoleIdCollision"; + case RoomMember::Error::IpCollision: + return "IpCollision"; case RoomMember::Error::WrongVersion: return "WrongVersion"; case RoomMember::Error::WrongPassword: diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp index a1be8dcf1..71121e42a 100644 --- a/src/tests/video_core/buffer_base.cpp +++ b/src/tests/video_core/buffer_base.cpp @@ -22,8 +22,9 @@ constexpr VAddr c = 0x1328914000; class RasterizerInterface { public: void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { - const u64 page_start{addr >> Core::Memory::PAGE_BITS}; - const u64 page_end{(addr + size + Core::Memory::PAGE_SIZE - 1) >> Core::Memory::PAGE_BITS}; + const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS}; + const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >> + Core::Memory::YUZU_PAGEBITS}; for (u64 page = page_start; page < page_end; ++page) { int& value = page_table[page]; value += delta; @@ -37,7 +38,7 @@ public: } [[nodiscard]] int Count(VAddr addr) const noexcept { - const auto it = page_table.find(addr >> Core::Memory::PAGE_BITS); + const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS); return it == page_table.end() ? 0 : it->second; } diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index 3e20608ca..0b2bc67b1 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -36,7 +36,7 @@ struct NullBufferParams {}; template <class RasterizerInterface> class BufferBase { static constexpr u64 PAGES_PER_WORD = 64; - static constexpr u64 BYTES_PER_PAGE = Core::Memory::PAGE_SIZE; + static constexpr u64 BYTES_PER_PAGE = Core::Memory::YUZU_PAGESIZE; static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE; /// Vector tracking modified pages tightly packed with small vector optimization diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index b74ad7900..f015dae56 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -60,8 +60,8 @@ class BufferCache { // Page size for caching purposes. // This is unrelated to the CPU page size and it can be changed as it seems optimal. - static constexpr u32 PAGE_BITS = 16; - static constexpr u64 PAGE_SIZE = u64{1} << PAGE_BITS; + static constexpr u32 YUZU_PAGEBITS = 16; + static constexpr u64 YUZU_PAGESIZE = u64{1} << YUZU_PAGEBITS; static constexpr bool IS_OPENGL = P::IS_OPENGL; static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = @@ -216,8 +216,8 @@ private: template <typename Func> void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) { - const u64 page_end = Common::DivCeil(cpu_addr + size, PAGE_SIZE); - for (u64 page = cpu_addr >> PAGE_BITS; page < page_end;) { + const u64 page_end = Common::DivCeil(cpu_addr + size, YUZU_PAGESIZE); + for (u64 page = cpu_addr >> YUZU_PAGEBITS; page < page_end;) { const BufferId buffer_id = page_table[page]; if (!buffer_id) { ++page; @@ -227,7 +227,7 @@ private: func(buffer_id, buffer); const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); - page = Common::DivCeil(end_addr, PAGE_SIZE); + page = Common::DivCeil(end_addr, YUZU_PAGESIZE); } } @@ -262,8 +262,8 @@ private: } static bool IsRangeGranular(VAddr cpu_addr, size_t size) { - return (cpu_addr & ~Core::Memory::PAGE_MASK) == - ((cpu_addr + size) & ~Core::Memory::PAGE_MASK); + return (cpu_addr & ~Core::Memory::YUZU_PAGEMASK) == + ((cpu_addr + size) & ~Core::Memory::YUZU_PAGEMASK); } void RunGarbageCollector(); @@ -439,7 +439,7 @@ private: u64 minimum_memory = 0; u64 critical_memory = 0; - std::array<BufferId, ((1ULL << 39) >> PAGE_BITS)> page_table; + std::array<BufferId, ((1ULL << 39) >> YUZU_PAGEBITS)> page_table; }; template <class P> @@ -926,8 +926,8 @@ void BufferCache<P>::PopAsyncFlushes() {} template <class P> bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { - const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); - for (u64 page = addr >> PAGE_BITS; page < page_end;) { + const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE); + for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) { const BufferId image_id = page_table[page]; if (!image_id) { ++page; @@ -938,7 +938,7 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { return true; } const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); - page = Common::DivCeil(end_addr, PAGE_SIZE); + page = Common::DivCeil(end_addr, YUZU_PAGESIZE); } return false; } @@ -946,8 +946,8 @@ bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { template <class P> bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) { const VAddr end_addr = addr + size; - const u64 page_end = Common::DivCeil(end_addr, PAGE_SIZE); - for (u64 page = addr >> PAGE_BITS; page < page_end;) { + const u64 page_end = Common::DivCeil(end_addr, YUZU_PAGESIZE); + for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) { const BufferId buffer_id = page_table[page]; if (!buffer_id) { ++page; @@ -959,15 +959,15 @@ bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) { if (buf_start_addr < end_addr && addr < buf_end_addr) { return true; } - page = Common::DivCeil(end_addr, PAGE_SIZE); + page = Common::DivCeil(end_addr, YUZU_PAGESIZE); } return false; } template <class P> bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) { - const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); - for (u64 page = addr >> PAGE_BITS; page < page_end;) { + const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE); + for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) { const BufferId image_id = page_table[page]; if (!image_id) { ++page; @@ -978,7 +978,7 @@ bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) { return true; } const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); - page = Common::DivCeil(end_addr, PAGE_SIZE); + page = Common::DivCeil(end_addr, YUZU_PAGESIZE); } return false; } @@ -1472,7 +1472,7 @@ BufferId BufferCache<P>::FindBuffer(VAddr cpu_addr, u32 size) { if (cpu_addr == 0) { return NULL_BUFFER_ID; } - const u64 page = cpu_addr >> PAGE_BITS; + const u64 page = cpu_addr >> YUZU_PAGEBITS; const BufferId buffer_id = page_table[page]; if (!buffer_id) { return CreateBuffer(cpu_addr, size); @@ -1493,8 +1493,9 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu VAddr end = cpu_addr + wanted_size; int stream_score = 0; bool has_stream_leap = false; - for (; cpu_addr >> PAGE_BITS < Common::DivCeil(end, PAGE_SIZE); cpu_addr += PAGE_SIZE) { - const BufferId overlap_id = page_table[cpu_addr >> PAGE_BITS]; + for (; cpu_addr >> YUZU_PAGEBITS < Common::DivCeil(end, YUZU_PAGESIZE); + cpu_addr += YUZU_PAGESIZE) { + const BufferId overlap_id = page_table[cpu_addr >> YUZU_PAGEBITS]; if (!overlap_id) { continue; } @@ -1520,11 +1521,11 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu // as a stream buffer. Increase the size to skip constantly recreating buffers. has_stream_leap = true; if (expands_right) { - begin -= PAGE_SIZE * 256; + begin -= YUZU_PAGESIZE * 256; cpu_addr = begin; } if (expands_left) { - end += PAGE_SIZE * 256; + end += YUZU_PAGESIZE * 256; } } } @@ -1598,8 +1599,8 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) { } const VAddr cpu_addr_begin = buffer.CpuAddr(); const VAddr cpu_addr_end = cpu_addr_begin + size; - const u64 page_begin = cpu_addr_begin / PAGE_SIZE; - const u64 page_end = Common::DivCeil(cpu_addr_end, PAGE_SIZE); + const u64 page_begin = cpu_addr_begin / YUZU_PAGESIZE; + const u64 page_end = Common::DivCeil(cpu_addr_end, YUZU_PAGESIZE); for (u64 page = page_begin; page != page_end; ++page) { if constexpr (insert) { page_table[page] = buffer_id; diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index d373be0ba..bf9eb735d 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -369,8 +369,8 @@ bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const { if (!cpu_addr) { return false; } - const std::size_t page{(*cpu_addr & Core::Memory::PAGE_MASK) + size}; - return page <= Core::Memory::PAGE_SIZE; + const std::size_t page{(*cpu_addr & Core::Memory::YUZU_PAGEMASK) + size}; + return page <= Core::Memory::YUZU_PAGESIZE; } bool MemoryManager::IsContinousRange(GPUVAddr gpu_addr, std::size_t size) const { diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h index fcce87acb..889b606b3 100644 --- a/src/video_core/query_cache.h +++ b/src/video_core/query_cache.h @@ -214,8 +214,8 @@ private: return cache_begin < addr_end && addr_begin < cache_end; }; - const u64 page_end = addr_end >> PAGE_BITS; - for (u64 page = addr_begin >> PAGE_BITS; page <= page_end; ++page) { + const u64 page_end = addr_end >> YUZU_PAGEBITS; + for (u64 page = addr_begin >> YUZU_PAGEBITS; page <= page_end; ++page) { const auto& it = cached_queries.find(page); if (it == std::end(cached_queries)) { continue; @@ -235,14 +235,14 @@ private: /// Registers the passed parameters as cached and returns a pointer to the stored cached query. CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) { rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1); - const u64 page = static_cast<u64>(cpu_addr) >> PAGE_BITS; + const u64 page = static_cast<u64>(cpu_addr) >> YUZU_PAGEBITS; return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr, host_ptr); } /// Tries to a get a cached query. Returns nullptr on failure. CachedQuery* TryGet(VAddr addr) { - const u64 page = static_cast<u64>(addr) >> PAGE_BITS; + const u64 page = static_cast<u64>(addr) >> YUZU_PAGEBITS; const auto it = cached_queries.find(page); if (it == std::end(cached_queries)) { return nullptr; @@ -260,8 +260,8 @@ private: uncommitted_flushes->push_back(addr); } - static constexpr std::uintptr_t PAGE_SIZE = 4096; - static constexpr unsigned PAGE_BITS = 12; + static constexpr std::uintptr_t YUZU_PAGESIZE = 4096; + static constexpr unsigned YUZU_PAGEBITS = 12; VideoCore::RasterizerInterface& rasterizer; Tegra::Engines::Maxwell3D& maxwell3d; diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp index 87a29e144..4a197d65d 100644 --- a/src/video_core/rasterizer_accelerated.cpp +++ b/src/video_core/rasterizer_accelerated.cpp @@ -24,8 +24,8 @@ void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int del u64 cache_bytes = 0; std::atomic_thread_fence(std::memory_order_acquire); - const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); - for (u64 page = addr >> PAGE_BITS; page != page_end; ++page) { + const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE); + for (u64 page = addr >> YUZU_PAGEBITS; page != page_end; ++page) { std::atomic_uint16_t& count = cached_pages.at(page >> 2).Count(page); if (delta > 0) { @@ -44,26 +44,27 @@ void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int del if (uncache_bytes == 0) { uncache_begin = page; } - uncache_bytes += PAGE_SIZE; + uncache_bytes += YUZU_PAGESIZE; } else if (uncache_bytes > 0) { - cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false); + cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes, + false); uncache_bytes = 0; } if (count.load(std::memory_order::relaxed) == 1 && delta > 0) { if (cache_bytes == 0) { cache_begin = page; } - cache_bytes += PAGE_SIZE; + cache_bytes += YUZU_PAGESIZE; } else if (cache_bytes > 0) { - cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true); + cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true); cache_bytes = 0; } } if (uncache_bytes > 0) { - cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false); + cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes, false); } if (cache_bytes > 0) { - cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true); + cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true); } } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 01028cee0..34f3f7a67 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -478,13 +478,16 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { } } - ASSERT_MSG(framebuffer_crop_rect.top == 0, "Unimplemented"); ASSERT_MSG(framebuffer_crop_rect.left == 0, "Unimplemented"); + f32 left_start{}; + if (framebuffer_crop_rect.Top() > 0) { + left_start = static_cast<f32>(framebuffer_crop_rect.Top()) / + static_cast<f32>(framebuffer_crop_rect.Bottom()); + } f32 scale_u = static_cast<f32>(framebuffer_width) / static_cast<f32>(screen_info.texture.width); f32 scale_v = static_cast<f32>(framebuffer_height) / static_cast<f32>(screen_info.texture.height); - // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering // (e.g. handheld mode) on a 1920x1080 framebuffer. if (framebuffer_crop_rect.GetWidth() > 0) { @@ -503,10 +506,14 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { const auto& screen = layout.screen; const std::array vertices = { - ScreenRectVertex(screen.left, screen.top, texcoords.top * scale_u, left * scale_v), - ScreenRectVertex(screen.right, screen.top, texcoords.bottom * scale_u, left * scale_v), - ScreenRectVertex(screen.left, screen.bottom, texcoords.top * scale_u, right * scale_v), - ScreenRectVertex(screen.right, screen.bottom, texcoords.bottom * scale_u, right * scale_v), + ScreenRectVertex(screen.left, screen.top, texcoords.top * scale_u, + left_start + left * scale_v), + ScreenRectVertex(screen.right, screen.top, texcoords.bottom * scale_u, + left_start + left * scale_v), + ScreenRectVertex(screen.left, screen.bottom, texcoords.top * scale_u, + left_start + right * scale_v), + ScreenRectVertex(screen.right, screen.bottom, texcoords.bottom * scale_u, + left_start + right * scale_v), }; glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), std::data(vertices)); diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 7d1431b6d..bdb71dc53 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -172,7 +172,7 @@ struct FormatTuple { {VK_FORMAT_R8G8_SINT, Attachable | Storage}, // R8G8_SINT {VK_FORMAT_R8G8_UINT, Attachable | Storage}, // R8G8_UINT {VK_FORMAT_R32G32_UINT, Attachable | Storage}, // R32G32_UINT - {VK_FORMAT_UNDEFINED}, // R16G16B16X16_FLOAT + {VK_FORMAT_R16G16B16A16_SFLOAT, Attachable | Storage}, // R16G16B16X16_FLOAT {VK_FORMAT_R32_UINT, Attachable | Storage}, // R32_UINT {VK_FORMAT_R32_SINT, Attachable | Storage}, // R32_SINT {VK_FORMAT_ASTC_8x8_UNORM_BLOCK}, // ASTC_2D_8X8_UNORM diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 27e6ebf94..444c29f68 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -1402,12 +1402,15 @@ void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& break; } - UNIMPLEMENTED_IF(framebuffer_crop_rect.top != 0); UNIMPLEMENTED_IF(framebuffer_crop_rect.left != 0); + f32 left_start{}; + if (framebuffer_crop_rect.Top() > 0) { + left_start = static_cast<f32>(framebuffer_crop_rect.Top()) / + static_cast<f32>(framebuffer_crop_rect.Bottom()); + } f32 scale_u = static_cast<f32>(framebuffer.width) / static_cast<f32>(screen_info.width); f32 scale_v = static_cast<f32>(framebuffer.height) / static_cast<f32>(screen_info.height); - // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering // (e.g. handheld mode) on a 1920x1080 framebuffer. if (!fsr) { @@ -1426,10 +1429,13 @@ void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& const auto y = static_cast<f32>(screen.top); const auto w = static_cast<f32>(screen.GetWidth()); const auto h = static_cast<f32>(screen.GetHeight()); - data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left * scale_v); - data.vertices[1] = ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left * scale_v); - data.vertices[2] = ScreenRectVertex(x, y + h, texcoords.top * scale_u, right * scale_v); - data.vertices[3] = ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v); + data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left_start + left * scale_v); + data.vertices[1] = + ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left_start + left * scale_v); + data.vertices[2] = + ScreenRectVertex(x, y + h, texcoords.top * scale_u, left_start + right * scale_v); + data.vertices[3] = + ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, left_start + right * scale_v); } void BlitScreen::CreateFSR() { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 16e46d3e5..7e40c2df1 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -69,10 +69,17 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in const float width = conv(src.scale_x * 2.0f); float y = conv(src.translate_y - src.scale_y); float height = conv(src.scale_y * 2.0f); - if (regs.screen_y_control.y_negate) { + bool y_negate = regs.screen_y_control.y_negate; + + if (!device.IsNvViewportSwizzleSupported()) { + y_negate = y_negate != (src.swizzle.y == Maxwell::ViewportSwizzle::NegativeY); + } + + if (y_negate) { y += height; height = -height; } + const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f; VkViewport viewport{ .x = x, diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index fa8efd22e..a69ae7725 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -33,9 +33,10 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats) } VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) { - // Mailbox doesn't lock the application like fifo (vsync), prefer it + // Mailbox (triple buffering) doesn't lock the application like fifo (vsync), + // prefer it if vsync option is not selected const auto found_mailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR); - if (found_mailbox != modes.end()) { + if (found_mailbox != modes.end() && !Settings::values.use_vsync.GetValue()) { return VK_PRESENT_MODE_MAILBOX_KHR; } if (!Settings::values.use_speed_limit.GetValue()) { diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp index 4b1101f7c..164e4ee0e 100644 --- a/src/video_core/shader_cache.cpp +++ b/src/video_core/shader_cache.cpp @@ -123,8 +123,8 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t const VAddr addr_end = addr + size; Entry* const entry = NewEntry(addr, addr_end, data.get()); - const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS; - for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) { + const u64 page_end = (addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS; + for (u64 page = addr >> YUZU_PAGEBITS; page < page_end; ++page) { invalidation_cache[page].push_back(entry); } @@ -135,8 +135,8 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) { const VAddr addr_end = addr + size; - const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS; - for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) { + const u64 page_end = (addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS; + for (u64 page = addr >> YUZU_PAGEBITS; page < page_end; ++page) { auto it = invalidation_cache.find(page); if (it == invalidation_cache.end()) { continue; @@ -189,8 +189,8 @@ void ShaderCache::InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr } void ShaderCache::RemoveEntryFromInvalidationCache(const Entry* entry) { - const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS; - for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) { + const u64 page_end = (entry->addr_end + YUZU_PAGESIZE - 1) >> YUZU_PAGEBITS; + for (u64 page = entry->addr_start >> YUZU_PAGEBITS; page < page_end; ++page) { const auto entries_it = invalidation_cache.find(page); ASSERT(entries_it != invalidation_cache.end()); std::vector<Entry*>& entries = entries_it->second; diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h index 1109cfe83..f67cea8c4 100644 --- a/src/video_core/shader_cache.h +++ b/src/video_core/shader_cache.h @@ -29,8 +29,8 @@ struct ShaderInfo { }; class ShaderCache { - static constexpr u64 PAGE_BITS = 14; - static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS; + static constexpr u64 YUZU_PAGEBITS = 14; + static constexpr u64 YUZU_PAGESIZE = u64(1) << YUZU_PAGEBITS; static constexpr size_t NUM_PROGRAMS = 6; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index cf3ca06a6..1dbe01bc0 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -589,7 +589,7 @@ void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst, template <class P> typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_addr) { // TODO: Properly implement this - const auto it = page_table.find(cpu_addr >> PAGE_BITS); + const auto it = page_table.find(cpu_addr >> YUZU_PAGEBITS); if (it == page_table.end()) { return nullptr; } @@ -1485,14 +1485,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) { std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>>& selected_page_table) { const auto page_it = selected_page_table.find(page); if (page_it == selected_page_table.end()) { - ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); + ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS); return; } std::vector<ImageId>& image_ids = page_it->second; const auto vector_it = std::ranges::find(image_ids, image_id); if (vector_it == image_ids.end()) { ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", - page << PAGE_BITS); + page << YUZU_PAGEBITS); return; } image_ids.erase(vector_it); @@ -1504,14 +1504,14 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) { ForEachCPUPage(image.cpu_addr, image.guest_size_bytes, [this, map_id](u64 page) { const auto page_it = page_table.find(page); if (page_it == page_table.end()) { - ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); + ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS); return; } std::vector<ImageMapId>& image_map_ids = page_it->second; const auto vector_it = std::ranges::find(image_map_ids, map_id); if (vector_it == image_map_ids.end()) { ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", - page << PAGE_BITS); + page << YUZU_PAGEBITS); return; } image_map_ids.erase(vector_it); @@ -1532,7 +1532,7 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) { ForEachCPUPage(cpu_addr, size, [this, image_id](u64 page) { const auto page_it = page_table.find(page); if (page_it == page_table.end()) { - ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PAGE_BITS); + ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS); return; } std::vector<ImageMapId>& image_map_ids = page_it->second; diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index e2f8f84c9..7e6c6cef2 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -47,7 +47,7 @@ struct ImageViewInOut { template <class P> class TextureCache { /// Address shift for caching images into a hash table - static constexpr u64 PAGE_BITS = 20; + static constexpr u64 YUZU_PAGEBITS = 20; /// Enables debugging features to the texture cache static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION; @@ -178,8 +178,8 @@ private: template <typename Func> static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) { static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; - const u64 page_end = (addr + size - 1) >> PAGE_BITS; - for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { + const u64 page_end = (addr + size - 1) >> YUZU_PAGEBITS; + for (u64 page = addr >> YUZU_PAGEBITS; page <= page_end; ++page) { if constexpr (RETURNS_BOOL) { if (func(page)) { break; @@ -193,8 +193,8 @@ private: template <typename Func> static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) { static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>; - const u64 page_end = (addr + size - 1) >> PAGE_BITS; - for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { + const u64 page_end = (addr + size - 1) >> YUZU_PAGEBITS; + for (u64 page = addr >> YUZU_PAGEBITS; page <= page_end; ++page) { if constexpr (RETURNS_BOOL) { if (func(page)) { break; diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 9b6b8527b..913f8ebcb 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -15,6 +15,24 @@ namespace Tegra::Texture { namespace { +template <u32 mask> +constexpr u32 pdep(u32 value) { + u32 result = 0; + u32 m = mask; + for (u32 bit = 1; m; bit += bit) { + if (value & bit) + result |= m & -m; + m &= m - 1; + } + return result; +} + +template <u32 mask, u32 incr_amount> +void incrpdep(u32& value) { + constexpr u32 swizzled_incr = pdep<mask>(incr_amount); + value = ((value | ~mask) + swizzled_incr) & mask; +} + template <bool TO_LINEAR, u32 BYTES_PER_PIXEL> void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { @@ -44,18 +62,20 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 ((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height)); for (u32 line = 0; line < height; ++line) { const u32 y = line + origin_y; - const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y]; + const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y); const u32 block_y = y >> GOB_SIZE_Y_SHIFT; const u32 offset_y = (block_y >> block_height) * block_size + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); - for (u32 column = 0; column < width; ++column) { + u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL); + for (u32 column = 0; column < width; + ++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) { const u32 x = (column + origin_x) * BYTES_PER_PIXEL; const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift; const u32 base_swizzled_offset = offset_z + offset_y + offset_x; - const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X]; + const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y); const u32 unswizzled_offset = slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL; @@ -103,12 +123,15 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 const u32 gob_address_y = (dst_y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs + ((dst_y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE; - const auto& table = SWIZZLE_TABLE[dst_y % GOB_SIZE_Y]; - for (u32 x = 0; x < subrect_width; ++x) { + + const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(dst_y); + u32 swizzled_x = pdep<SWIZZLE_X_BITS>(offset_x * BYTES_PER_PIXEL); + for (u32 x = 0; x < subrect_width; + ++x, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) { const u32 dst_x = x + offset_x; const u32 gob_address = gob_address_y + (dst_x * BYTES_PER_PIXEL / GOB_SIZE_X) * GOB_SIZE * block_height; - const u32 swizzled_offset = gob_address + table[(dst_x * BYTES_PER_PIXEL) % GOB_SIZE_X]; + const u32 swizzled_offset = gob_address + (swizzled_x | swizzled_y); const u32 unswizzled_offset = line * source_pitch + x * BYTES_PER_PIXEL; const u8* const source_line = unswizzled_data + unswizzled_offset; @@ -130,16 +153,19 @@ void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width, for (u32 line = 0; line < line_count; ++line) { const u32 src_y = line + origin_y; - const auto& table = SWIZZLE_TABLE[src_y % GOB_SIZE_Y]; + const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(src_y); const u32 block_y = src_y >> GOB_SIZE_Y_SHIFT; const u32 src_offset_y = (block_y >> block_height) * block_size + ((block_y & block_height_mask) << GOB_SIZE_SHIFT); - for (u32 column = 0; column < line_length_in; ++column) { + + u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL); + for (u32 column = 0; column < line_length_in; + ++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) { const u32 src_x = (column + origin_x) * BYTES_PER_PIXEL; const u32 src_offset_x = (src_x >> GOB_SIZE_X_SHIFT) << x_shift; - const u32 swizzled_offset = src_offset_y + src_offset_x + table[src_x % GOB_SIZE_X]; + const u32 swizzled_offset = src_offset_y + src_offset_x + (swizzled_x | swizzled_y); const u32 unswizzled_offset = line * pitch + column * BYTES_PER_PIXEL; std::memcpy(output + unswizzled_offset, input + swizzled_offset, BYTES_PER_PIXEL); @@ -162,13 +188,15 @@ void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 widt const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height + block_depth; for (u32 line = 0; line < line_count; ++line) { - const auto& table = SWIZZLE_TABLE[line % GOB_SIZE_Y]; + const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(line); const u32 block_y = line / GOB_SIZE_Y; const u32 dst_offset_y = (block_y >> block_height) * block_size + (block_y & block_height_mask) * GOB_SIZE; - for (u32 x = 0; x < line_length_in; ++x) { + + u32 swizzled_x = 0; + for (u32 x = 0; x < line_length_in; ++x, incrpdep<SWIZZLE_X_BITS, 1>(swizzled_x)) { const u32 dst_offset = - ((x / GOB_SIZE_X) << x_shift) + dst_offset_y + table[x % GOB_SIZE_X]; + ((x / GOB_SIZE_X) << x_shift) + dst_offset_y + (swizzled_x | swizzled_y); const u32 src_offset = x * BYTES_PER_PIXEL + line * pitch; std::memcpy(output + dst_offset, input + src_offset, BYTES_PER_PIXEL); } @@ -267,11 +295,13 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 const std::size_t gob_address_y = (y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs + ((y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE; - const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y]; - for (std::size_t x = dst_x; x < width && count < copy_size; ++x) { + const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(static_cast<u32>(y)); + u32 swizzled_x = pdep<SWIZZLE_X_BITS>(dst_x); + for (std::size_t x = dst_x; x < width && count < copy_size; + ++x, incrpdep<SWIZZLE_X_BITS, 1>(swizzled_x)) { const std::size_t gob_address = gob_address_y + (x / GOB_SIZE_X) * GOB_SIZE * block_height; - const std::size_t swizzled_offset = gob_address + table[x % GOB_SIZE_X]; + const std::size_t swizzled_offset = gob_address + (swizzled_x | swizzled_y); const u8* source_line = source_data + count; u8* dest_addr = swizzle_data + swizzled_offset; count++; diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index 59dfd1621..31a11708f 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -20,6 +20,9 @@ constexpr u32 GOB_SIZE_Y_SHIFT = 3; constexpr u32 GOB_SIZE_Z_SHIFT = 0; constexpr u32 GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; +constexpr u32 SWIZZLE_X_BITS = 0b100101111; +constexpr u32 SWIZZLE_Y_BITS = 0b011010000; + using SwizzleTable = std::array<std::array<u32, GOB_SIZE_X>, GOB_SIZE_Y>; /** diff --git a/src/web_service/verify_user_jwt.cpp b/src/web_service/verify_user_jwt.cpp index 3bff46f0a..129eb1968 100644 --- a/src/web_service/verify_user_jwt.cpp +++ b/src/web_service/verify_user_jwt.cpp @@ -39,8 +39,10 @@ Network::VerifyUser::UserData VerifyUserJWT::LoadUserData(const std::string& ver const std::string audience = fmt::format("external-{}", verify_uid); using namespace jwt::params; std::error_code error; + + // We use the Citra backend so the issuer is citra-core auto decoded = - jwt::decode(token, algorithms({"rs256"}), error, secret(pub_key), issuer("yuzu-core"), + jwt::decode(token, algorithms({"rs256"}), error, secret(pub_key), issuer("citra-core"), aud(audience), validate_iat(true), validate_jti(true)); if (error) { LOG_INFO(WebService, "Verification failed: category={}, code={}, message={}", diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index f6b389ede..50007338f 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -221,6 +221,9 @@ if (ENABLE_QT_TRANSLATION) # Update source TS file if enabled if (GENERATE_QT_TRANSLATION) get_target_property(SRCS yuzu SOURCES) + # these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals + # so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm + set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations") qt_create_translation(QM_FILES ${SRCS} ${UIS} @@ -229,7 +232,13 @@ if (ENABLE_QT_TRANSLATION) -source-language en_US -target-language en_US ) - add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts) + + # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts + set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts) + set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals") + qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US) + + add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE}) endif() # Find all TS files except en.ts @@ -239,6 +248,9 @@ if (ENABLE_QT_TRANSLATION) # Compile TS files to QM files qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) + # Compile english plurals TS file to en.qm + qt_add_translation(LANGUAGES_QM ${PROJECT_SOURCE_DIR}/dist/english_plurals/en.ts) + # Build a QRC file from the QM file list set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n") diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui index c4ffb293e..aea82809d 100644 --- a/src/yuzu/aboutdialog.ui +++ b/src/yuzu/aboutdialog.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>616</width> - <height>261</height> + <height>294</height> </rect> </property> <property name="windowTitle"> @@ -165,6 +165,7 @@ p, li { white-space: pre-wrap; } </widget> <resources> <include location="../../dist/qt_themes_default/default/default.qrc"/> + <include location="../../dist/qt_themes/default/default.qrc"/> </resources> <connections> <connection> diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index c262d0a2b..d3fbdb09d 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -815,6 +815,12 @@ void GRenderWindow::InitializeCamera() { if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() || Settings::values.ir_sensor_device.GetValue() == "Auto") { camera = std::make_unique<QCamera>(cameraInfo); + if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) && + !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { + LOG_ERROR(Frontend, + "Camera doesn't support CaptureViewfinder or CaptureStillImage"); + continue; + } camera_found = true; break; } @@ -825,10 +831,22 @@ void GRenderWindow::InitializeCamera() { } camera_capture = std::make_unique<QCameraImageCapture>(camera.get()); + + if (!camera_capture->isCaptureDestinationSupported( + QCameraImageCapture::CaptureDestination::CaptureToBuffer)) { + LOG_ERROR(Frontend, "Camera doesn't support saving to buffer"); + return; + } + + camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer); connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, &GRenderWindow::OnCameraCapture); camera->unload(); - camera->setCaptureMode(QCamera::CaptureViewfinder); + if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) { + camera->setCaptureMode(QCamera::CaptureViewfinder); + } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { + camera->setCaptureMode(QCamera::CaptureStillImage); + } camera->load(); camera->start(); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 58f1239bf..da6e5aa88 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -73,7 +73,7 @@ const std::array<int, 2> Config::default_ringcon_analogs{{ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("+"), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index a5bcee415..6034d8581 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -120,10 +120,10 @@ </sizepolicy> </property> <property name="maximum"> - <number>100</number> + <number>200</number> </property> <property name="pageStep"> - <number>10</number> + <number>5</number> </property> <property name="orientation"> <enum>Qt::Horizontal</enum> diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp index 73cdcf3f2..2a61de2a1 100644 --- a/src/yuzu/configuration/configure_camera.cpp +++ b/src/yuzu/configuration/configure_camera.cpp @@ -42,6 +42,12 @@ void ConfigureCamera::PreviewCamera() { LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(), cameraInfo.deviceName().toStdString()); camera = std::make_unique<QCamera>(cameraInfo); + if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) && + !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { + LOG_ERROR(Frontend, + "Camera doesn't support CaptureViewfinder or CaptureStillImage"); + continue; + } camera_found = true; break; } @@ -57,10 +63,22 @@ void ConfigureCamera::PreviewCamera() { } camera_capture = std::make_unique<QCameraImageCapture>(camera.get()); + + if (!camera_capture->isCaptureDestinationSupported( + QCameraImageCapture::CaptureDestination::CaptureToBuffer)) { + LOG_ERROR(Frontend, "Camera doesn't support saving to buffer"); + return; + } + + camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer); connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, &ConfigureCamera::DisplayCapturedFrame); camera->unload(); - camera->setCaptureMode(QCamera::CaptureViewfinder); + if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) { + camera->setCaptureMode(QCamera::CaptureViewfinder); + } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { + camera->setCaptureMode(QCamera::CaptureStillImage); + } camera->load(); camera->start(); diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 96de0b3d1..d6d819364 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -75,7 +75,7 @@ <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string> </property> <property name="text"> - <string>Use VSync (OpenGL only)</string> + <string>Use VSync</string> </property> </widget> </item> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 00bee85b2..109689c88 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -1475,7 +1475,7 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { void ConfigureInputPlayer::CreateProfile() { const auto profile_name = - LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20, + LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 30, LimitableInputDialog::InputLimiter::Filesystem); if (profile_name.isEmpty()) { diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 2e98ede8e..48f71b53c 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -219,6 +219,7 @@ void ConfigureUi::InitializeLanguageComboBox() { for (const auto& lang : languages) { if (QString::fromLatin1(lang.id) == QStringLiteral("en")) { ui->language_combobox->addItem(lang.name, QStringLiteral("en")); + language_files.removeOne(QStringLiteral("en.qm")); continue; } for (int i = 0; i < language_files.size(); ++i) { diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 041e6ac11..b127badc2 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -126,10 +126,8 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { layout_filter = new QHBoxLayout; layout_filter->setContentsMargins(8, 8, 8, 8); label_filter = new QLabel; - label_filter->setText(tr("Filter:")); edit_filter = new QLineEdit; edit_filter->clear(); - edit_filter->setPlaceholderText(tr("Enter pattern to filter")); edit_filter->installEventFilter(key_release_eater); edit_filter->setClearButtonEnabled(true); connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged); @@ -149,6 +147,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { layout_filter->addWidget(label_filter_result); layout_filter->addWidget(button_filter_close); setLayout(layout_filter); + RetranslateUI(); } /** @@ -286,7 +285,7 @@ void GameList::OnUpdateThemedIcons() { } case GameListItemType::AddDir: child->setData( - QIcon::fromTheme(QStringLiteral("plus")) + QIcon::fromTheme(QStringLiteral("list-add")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::DecorationRole); @@ -333,13 +332,9 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); item_model->insertColumns(0, COLUMN_COUNT); - item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); - item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); + RetranslateUI(); - item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); - item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); - item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); item_model->setSortRole(GameListItemPath::SortRole); connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); @@ -753,6 +748,35 @@ void GameList::LoadCompatibilityList() { } } +void GameList::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QWidget::changeEvent(event); +} + +void GameList::RetranslateUI() { + item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); + item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); + item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); + item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); + item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); +} + +void GameListSearchField::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QWidget::changeEvent(event); +} + +void GameListSearchField::RetranslateUI() { + label_filter->setText(tr("Filter:")); + edit_filter->setPlaceholderText(tr("Enter pattern to filter")); +} + QStandardItemModel* GameList::GetModel() const { return item_model; } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index f783283c9..cdf085019 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -140,6 +140,9 @@ private: void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); void AddFavoritesPopup(QMenu& context_menu); + void changeEvent(QEvent*) override; + void RetranslateUI(); + std::shared_ptr<FileSys::VfsFilesystem> vfs; FileSys::ManualContentProvider* provider; GameListSearchField* search_field; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index e7667cf60..6198d1e4e 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -294,7 +294,7 @@ public: const int icon_size = UISettings::values.folder_icon_size.GetValue(); - setData(QIcon::fromTheme(QStringLiteral("plus")) + setData(QIcon::fromTheme(QStringLiteral("list-add")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::DecorationRole); @@ -353,6 +353,9 @@ public: void setFocus(); private: + void changeEvent(QEvent*) override; + void RetranslateUI(); + class KeyReleaseEater : public QObject { public: explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 653280642..e103df977 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -257,6 +257,18 @@ static QString PrettyProductName() { return QSysInfo::prettyProductName(); } +bool GMainWindow::CheckDarkMode() { +#ifdef __linux__ + const QPalette test_palette(qApp->palette()); + const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text); + const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window); + return (text_color.value() > window_color.value()); +#else + // TODO: Windows + return false; +#endif // __linux__ +} + GMainWindow::GMainWindow(bool has_broken_vulkan) : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, @@ -274,6 +286,13 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) ui->setupUi(this); statusBar()->hide(); + // Check dark mode before a theme is loaded + os_dark_mode = CheckDarkMode(); + startup_icon_theme = QIcon::themeName(); + // fallback can only be set once, colorful theme icons are okay on both light/dark + QIcon::setFallbackThemeName(QStringLiteral("colorful")); + QIcon::setFallbackSearchPaths(QStringList(QStringLiteral(":/icons"))); + default_theme_paths = QIcon::themeSearchPaths(); UpdateUITheme(); @@ -473,8 +492,6 @@ GMainWindow::~GMainWindow() { delete render_window; } - system->GetRoomNetwork().Shutdown(); - #ifdef __linux__ ::close(sig_interrupt_fds[0]); ::close(sig_interrupt_fds[1]); @@ -1075,7 +1092,7 @@ void GMainWindow::InitializeHotkeys() { connect_shortcut(QStringLiteral("Audio Mute/Unmute"), [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); connect_shortcut(QStringLiteral("Audio Volume Down"), [] { - const auto current_volume = static_cast<int>(Settings::values.volume.GetValue()); + const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue()); int step = 5; if (current_volume <= 30) { step = 2; @@ -1083,11 +1100,10 @@ void GMainWindow::InitializeHotkeys() { if (current_volume <= 6) { step = 1; } - const auto new_volume = std::max(current_volume - step, 0); - Settings::values.volume.SetValue(static_cast<u8>(new_volume)); + Settings::values.volume.SetValue(std::max(current_volume - step, 0)); }); connect_shortcut(QStringLiteral("Audio Volume Up"), [] { - const auto current_volume = static_cast<int>(Settings::values.volume.GetValue()); + const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue()); int step = 5; if (current_volume < 30) { step = 2; @@ -1095,8 +1111,7 @@ void GMainWindow::InitializeHotkeys() { if (current_volume < 6) { step = 1; } - const auto new_volume = std::min(current_volume + step, 100); - Settings::values.volume.SetValue(static_cast<u8>(new_volume)); + Settings::values.volume.SetValue(current_volume + step); }); connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); @@ -3814,6 +3829,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { render_window->close(); multiplayer_state->Close(); + system->GetRoomNetwork().Shutdown(); QWidget::closeEvent(event); } @@ -3935,8 +3951,21 @@ void GMainWindow::filterBarSetChecked(bool state) { emit(OnToggleFilterBar()); } +static void AdjustLinkColor() { + QPalette new_pal(qApp->palette()); + if (UISettings::IsDarkTheme()) { + new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255)); + } else { + new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255)); + } + if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) { + qApp->setPalette(new_pal); + } +} + void GMainWindow::UpdateUITheme() { - const QString default_theme = QStringLiteral("default"); + const QString default_theme = + QString::fromUtf8(UISettings::themes[static_cast<size_t>(Config::default_theme)].second); QString current_theme = UISettings::values.theme; QStringList theme_paths(default_theme_paths); @@ -3944,6 +3973,23 @@ void GMainWindow::UpdateUITheme() { current_theme = default_theme; } +#ifdef _WIN32 + QIcon::setThemeName(current_theme); + AdjustLinkColor(); +#else + if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) { + QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme + : startup_icon_theme); + QIcon::setThemeSearchPaths(theme_paths); + if (CheckDarkMode()) { + current_theme = QStringLiteral("default_dark"); + } + } else { + QIcon::setThemeName(current_theme); + QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons"))); + AdjustLinkColor(); + } +#endif if (current_theme != default_theme) { QString theme_uri{QStringLiteral(":%1/style.qss").arg(current_theme)}; QFile f(theme_uri); @@ -3966,25 +4012,9 @@ void GMainWindow::UpdateUITheme() { qApp->setStyleSheet({}); setStyleSheet({}); } - - QPalette new_pal(qApp->palette()); - if (UISettings::IsDarkTheme()) { - new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255)); - } else { - new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255)); - } - qApp->setPalette(new_pal); - - QIcon::setThemeName(current_theme); - QIcon::setThemeSearchPaths(theme_paths); } void GMainWindow::LoadTranslation() { - // If the selected language is English, no need to install any translation - if (UISettings::values.language == QStringLiteral("en")) { - return; - } - bool loaded; if (UISettings::values.language.isEmpty()) { @@ -4027,6 +4057,26 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } +void GMainWindow::changeEvent(QEvent* event) { +#ifdef __linux__ + // PaletteChange event appears to only reach so far into the GUI, explicitly asking to + // UpdateUITheme is a decent work around + if (event->type() == QEvent::PaletteChange) { + const QPalette test_palette(qApp->palette()); + const QString current_theme = UISettings::values.theme; + // Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too + static QColor last_window_color; + const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window); + if (last_window_color != window_color && (current_theme == QStringLiteral("default") || + current_theme == QStringLiteral("colorful"))) { + UpdateUITheme(); + } + last_window_color = window_color; + } +#endif // __linux__ + QWidget::changeEvent(event); +} + #ifdef main #undef main #endif @@ -4072,6 +4122,15 @@ int main(int argc, char* argv[]) { QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QApplication app(argc, argv); + // Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021 + // so we can see if we get \u3008 instead + // TL;DR all other number formats are consecutive in unicode code points + // This bug is fixed in Qt6, specifically 6.0.0-alpha1 + const QLocale locale = QLocale::system(); + if (QStringLiteral("\u3008") == locale.toString(1)) { + QLocale::setDefault(QLocale::system().name()); + } + // Qt changes the locale and causes issues in float conversion using std::to_string() when // generating shaders setlocale(LC_ALL, "C"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index e13b38b24..1ae2b93d9 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -251,6 +251,7 @@ private: bool ConfirmForceLockedExit(); void RequestGameExit(); void RequestGameResume(); + void changeEvent(QEvent* event) override; void closeEvent(QCloseEvent* event) override; #ifdef __linux__ @@ -347,6 +348,7 @@ private: void OpenURL(const QUrl& url); void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); + bool CheckDarkMode(); QString GetTasStateDescription() const; @@ -392,6 +394,9 @@ private: QTimer mouse_hide_timer; QTimer mouse_center_timer; + QString startup_icon_theme; + bool os_dark_mode = false; + // FS std::shared_ptr<FileSys::VfsFilesystem> vfs; std::unique_ptr<FileSys::ManualContentProvider> provider; diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 5837b36ab..1968a3c75 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -316,21 +316,19 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_ } void ChatRoom::OnSendChat() { - if (auto room = room_network->GetRoomMember().lock()) { - if (room->GetState() != Network::RoomMember::State::Joined && - room->GetState() != Network::RoomMember::State::Moderator) { - + if (auto room_member = room_network->GetRoomMember().lock()) { + if (!room_member->IsConnected()) { return; } auto message = ui->chat_message->text().toStdString(); if (!ValidateMessage(message)) { return; } - auto nick = room->GetNickname(); - auto username = room->GetUsername(); + auto nick = room_member->GetNickname(); + auto username = room_member->GetUsername(); Network::ChatEntry chat{nick, username, message}; - auto members = room->GetMemberInformation(); + auto members = room_member->GetMemberInformation(); auto it = std::find_if(members.begin(), members.end(), [&chat](const Network::RoomMember::MemberInformation& member) { return member.nickname == chat.nickname && @@ -341,7 +339,7 @@ void ChatRoom::OnSendChat() { } auto player = std::distance(members.begin(), it); ChatMessage m(chat, *room_network); - room->SendChatMessage(message); + room_member->SendChatMessage(message); AppendChatMessage(m.GetPlayerChatMessage(player)); ui->chat_message->clear(); } diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp index a9859ed70..86baafbf0 100644 --- a/src/yuzu/multiplayer/client_room.cpp +++ b/src/yuzu/multiplayer/client_room.cpp @@ -74,7 +74,6 @@ void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) { void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) { if (state == Network::RoomMember::State::Joined || state == Network::RoomMember::State::Moderator) { - ui->chat->Clear(); ui->chat->AppendStatusMessage(tr("Connected")); SetModPerms(state == Network::RoomMember::State::Moderator); diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 9000c4531..4c0ea0a6b 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -97,9 +97,9 @@ void DirectConnectWindow::Connect() { QFuture<void> f = QtConcurrent::run([&] { if (auto room_member = room_network.GetRoomMember().lock()) { auto port = UISettings::values.multiplayer_port.GetValue(); - room_member->Join(ui->nickname->text().toStdString(), "", - ui->ip->text().toStdString().c_str(), port, 0, - Network::NoPreferredMac, ui->password->text().toStdString().c_str()); + room_member->Join(ui->nickname->text().toStdString(), + ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP, + ui->password->text().toStdString().c_str()); } }); watcher->setFuture(f); @@ -121,9 +121,7 @@ void DirectConnectWindow::OnConnection() { EndConnecting(); if (auto room_member = room_network.GetRoomMember().lock()) { - if (room_member->GetState() == Network::RoomMember::State::Joined || - room_member->GetState() == Network::RoomMember::State::Moderator) { - + if (room_member->IsConnected()) { close(); } } diff --git a/src/yuzu/multiplayer/direct_connect.ui b/src/yuzu/multiplayer/direct_connect.ui index 681b6bf69..57d6ec25a 100644 --- a/src/yuzu/multiplayer/direct_connect.ui +++ b/src/yuzu/multiplayer/direct_connect.ui @@ -83,7 +83,7 @@ <number>5</number> </property> <property name="placeholderText"> - <string>24872</string> + <string notr="true" extracomment="placeholder string that tells user default port">24872</string> </property> </widget> </item> diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index cb9464b2b..d70a9a3c8 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -201,8 +201,8 @@ void HostRoomWindow::Host() { } #endif // TODO: Check what to do with this - member->Join(ui->username->text().toStdString(), "", "127.0.0.1", port, 0, - Network::NoPreferredMac, password, token); + member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0, + Network::NoPreferredIP, password, token); // Store settings UISettings::values.multiplayer_room_nickname = ui->username->text(); diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 23c2f21ab..1cc518279 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -169,7 +169,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { } #endif if (auto room_member = room_network.GetRoomMember().lock()) { - room_member->Join(nickname, "", ip.c_str(), port, 0, Network::NoPreferredMac, password, + room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password, token); } }); diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp index 76ec276ad..94d7a38b8 100644 --- a/src/yuzu/multiplayer/message.cpp +++ b/src/yuzu/multiplayer/message.cpp @@ -43,11 +43,8 @@ const ConnectionError ErrorManager::LOST_CONNECTION( QT_TR_NOOP("Connection to room lost. Try to reconnect.")); const ConnectionError ErrorManager::HOST_KICKED( QT_TR_NOOP("You have been kicked by the room host.")); -const ConnectionError ErrorManager::MAC_COLLISION( - QT_TR_NOOP("MAC address is already in use. Please choose another.")); -const ConnectionError ErrorManager::CONSOLE_ID_COLLISION(QT_TR_NOOP( - "Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation " - "> Configure > System to regenerate your Console ID.")); +const ConnectionError ErrorManager::IP_COLLISION( + QT_TR_NOOP("IP address is already in use. Please choose another.")); const ConnectionError ErrorManager::PERMISSION_DENIED( QT_TR_NOOP("You do not have enough permission to perform this action.")); const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( diff --git a/src/yuzu/multiplayer/message.h b/src/yuzu/multiplayer/message.h index eb5c8d1be..812495c72 100644 --- a/src/yuzu/multiplayer/message.h +++ b/src/yuzu/multiplayer/message.h @@ -40,8 +40,7 @@ public: static const ConnectionError GENERIC_ERROR; static const ConnectionError LOST_CONNECTION; static const ConnectionError HOST_KICKED; - static const ConnectionError MAC_COLLISION; - static const ConnectionError CONSOLE_ID_COLLISION; + static const ConnectionError IP_COLLISION; static const ConnectionError PERMISSION_DENIED; static const ConnectionError NO_SUCH_USER; /** diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index 4149b5232..dba76b22b 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -59,7 +59,9 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis }); } -MultiplayerState::~MultiplayerState() { +MultiplayerState::~MultiplayerState() = default; + +void MultiplayerState::Close() { if (state_callback_handle) { if (auto member = room_network.GetRoomMember().lock()) { member->Unbind(state_callback_handle); @@ -71,9 +73,6 @@ MultiplayerState::~MultiplayerState() { member->Unbind(error_callback_handle); } } -} - -void MultiplayerState::Close() { if (host_room) { host_room->close(); } @@ -95,7 +94,6 @@ void MultiplayerState::retranslateUi() { status_text->setText(tr("Not Connected. Click here to find a room!")); } else if (current_state == Network::RoomMember::State::Joined || current_state == Network::RoomMember::State::Moderator) { - status_text->setText(tr("Connected")); } else { status_text->setText(tr("Not Connected")); @@ -151,11 +149,8 @@ void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) { NetworkMessage::ErrorManager::ShowError( NetworkMessage::ErrorManager::USERNAME_NOT_VALID_SERVER); break; - case Network::RoomMember::Error::MacCollision: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::MAC_COLLISION); - break; - case Network::RoomMember::Error::ConsoleIdCollision: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::CONSOLE_ID_COLLISION); + case Network::RoomMember::Error::IpCollision: + NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_COLLISION); break; case Network::RoomMember::Error::RoomIsFull: NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOM_IS_FULL); diff --git a/src/yuzu/multiplayer/validation.h b/src/yuzu/multiplayer/validation.h index 7d48e589d..dabf860be 100644 --- a/src/yuzu/multiplayer/validation.h +++ b/src/yuzu/multiplayer/validation.h @@ -10,7 +10,7 @@ class Validation { public: Validation() - : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, 65535) {} + : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, UINT16_MAX) {} ~Validation() = default; diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 25d1bf1e6..e12d414d9 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -104,11 +104,12 @@ struct Values { // multiplayer settings Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"}; Settings::Setting<QString> multiplayer_ip{{}, "ip"}; - Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, 65535, "port"}; + Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"}; Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"}; Settings::Setting<QString> multiplayer_room_name{{}, "room_name"}; Settings::SwitchableSetting<uint, true> multiplayer_max_player{8, 0, 8, "max_player"}; - Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, 65535, "room_port"}; + Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, UINT16_MAX, + "room_port"}; Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"}; Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"}; Settings::Setting<QString> multiplayer_room_description{{}, "room_description"}; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 003890c07..3a0f33cba 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -108,15 +108,11 @@ static void OnNetworkError(const Network::RoomMember::Error& error) { "You tried to use the same nickname as another user that is connected to the Room"); exit(1); break; - case Network::RoomMember::Error::MacCollision: - LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is " + case Network::RoomMember::Error::IpCollision: + LOG_ERROR(Network, "You tried to use the same fake IP-Address as another user that is " "connected to the Room"); exit(1); break; - case Network::RoomMember::Error::ConsoleIdCollision: - LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room"); - exit(1); - break; case Network::RoomMember::Error::WrongPassword: LOG_ERROR(Network, "Room replied with: Wrong password"); exit(1); @@ -365,7 +361,7 @@ int main(int argc, char** argv) { member->BindOnError(OnNetworkError); LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname); - member->Join(nickname, "", address.c_str(), port, 0, Network::NoPreferredMac, password); + member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password); } else { LOG_ERROR(Network, "Could not access RoomMember"); return 0; |