mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-19 18:53:32 +00:00
Merge pull request 'fix: Multiplayer network fixes and airplane mode' (#35) from fix/multiplayer-network-improvements into main
Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/35
This commit is contained in:
@@ -31,7 +31,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
SHOW_RAM_METER("show_ram_meter"),
|
SHOW_RAM_METER("show_ram_meter"),
|
||||||
SHOW_SHADER_BUILDING_OVERLAY("show_shader_building_overlay"),
|
SHOW_SHADER_BUILDING_OVERLAY("show_shader_building_overlay"),
|
||||||
SHOW_PERFORMANCE_GRAPH("show_performance_graph"),
|
SHOW_PERFORMANCE_GRAPH("show_performance_graph"),
|
||||||
USE_CONDITIONAL_RENDERING("use_conditional_rendering");
|
USE_CONDITIONAL_RENDERING("use_conditional_rendering"),
|
||||||
|
AIRPLANE_MODE("airplane_mode");
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||||
NativeConfig.getBoolean(key, needsGlobal)
|
NativeConfig.getBoolean(key, needsGlobal)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ object Settings {
|
|||||||
SECTION_INPUT_PLAYER_EIGHT,
|
SECTION_INPUT_PLAYER_EIGHT,
|
||||||
SECTION_THEME(R.string.preferences_theme),
|
SECTION_THEME(R.string.preferences_theme),
|
||||||
SECTION_DEBUG(R.string.preferences_debug),
|
SECTION_DEBUG(R.string.preferences_debug),
|
||||||
|
SECTION_NETWORK(R.string.preferences_network),
|
||||||
SECTION_ZEP_ZONE(R.string.preferences_zep_zone),
|
SECTION_ZEP_ZONE(R.string.preferences_zep_zone),
|
||||||
SECTION_APPLETS_ANDROID(R.string.preferences_applets_android);
|
SECTION_APPLETS_ANDROID(R.string.preferences_applets_android);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ class SettingsFragmentPresenter(
|
|||||||
MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7)
|
MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7)
|
||||||
MenuTag.SECTION_THEME -> addThemeSettings(sl)
|
MenuTag.SECTION_THEME -> addThemeSettings(sl)
|
||||||
MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
||||||
|
MenuTag.SECTION_NETWORK -> addNetworkSettings(sl)
|
||||||
MenuTag.SECTION_ZEP_ZONE -> addZepZoneSettings(sl)
|
MenuTag.SECTION_ZEP_ZONE -> addZepZoneSettings(sl)
|
||||||
MenuTag.SECTION_APPLETS_ANDROID -> addAppletsAndroidSettings(sl)
|
MenuTag.SECTION_APPLETS_ANDROID -> addAppletsAndroidSettings(sl)
|
||||||
}
|
}
|
||||||
@@ -144,6 +145,14 @@ class SettingsFragmentPresenter(
|
|||||||
menuKey = MenuTag.SECTION_DEBUG
|
menuKey = MenuTag.SECTION_DEBUG
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
SubmenuSetting(
|
||||||
|
titleId = R.string.preferences_network,
|
||||||
|
descriptionId = R.string.preferences_network_description,
|
||||||
|
iconId = R.drawable.ic_settings,
|
||||||
|
menuKey = MenuTag.SECTION_NETWORK
|
||||||
|
)
|
||||||
|
)
|
||||||
add(
|
add(
|
||||||
SubmenuSetting(
|
SubmenuSetting(
|
||||||
titleId = R.string.preferences_zep_zone,
|
titleId = R.string.preferences_zep_zone,
|
||||||
@@ -1002,6 +1011,13 @@ class SettingsFragmentPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addNetworkSettings(sl: ArrayList<SettingsItem>) {
|
||||||
|
sl.apply {
|
||||||
|
add(HeaderSetting(R.string.network_settings_header))
|
||||||
|
add(BooleanSetting.AIRPLANE_MODE.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addZepZoneSettings(sl: ArrayList<SettingsItem>) {
|
private fun addZepZoneSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(HeaderSetting(R.string.memory_layout_header))
|
add(HeaderSetting(R.string.memory_layout_header))
|
||||||
|
|||||||
@@ -421,11 +421,16 @@
|
|||||||
<string name="preferences_theme">Theme and color</string>
|
<string name="preferences_theme">Theme and color</string>
|
||||||
<string name="preferences_debug">Debug</string>
|
<string name="preferences_debug">Debug</string>
|
||||||
<string name="preferences_debug_description">CPU/GPU debugging, graphics API, fastmem</string>
|
<string name="preferences_debug_description">CPU/GPU debugging, graphics API, fastmem</string>
|
||||||
|
<string name="preferences_network">Network</string>
|
||||||
|
<string name="preferences_network_description">Network interface and airplane mode settings</string>
|
||||||
<string name="preferences_zep_zone">Zep Zone</string>
|
<string name="preferences_zep_zone">Zep Zone</string>
|
||||||
<string name="preferences_zep_zone_description">Advanced emulation settings</string>
|
<string name="preferences_zep_zone_description">Advanced emulation settings</string>
|
||||||
<string name="preferences_applets_android">Applets on Android</string>
|
<string name="preferences_applets_android">Applets on Android</string>
|
||||||
<string name="preferences_applets_android_description">System applet configuration settings</string>
|
<string name="preferences_applets_android_description">System applet configuration settings</string>
|
||||||
|
|
||||||
|
<!-- Network Settings Headers -->
|
||||||
|
<string name="network_settings_header">Network Settings</string>
|
||||||
|
|
||||||
<!-- Zep Zone Headers -->
|
<!-- Zep Zone Headers -->
|
||||||
<string name="memory_layout_header">Memory Layout</string>
|
<string name="memory_layout_header">Memory Layout</string>
|
||||||
<string name="astc_settings_header">ASTC Settings</string>
|
<string name="astc_settings_header">ASTC Settings</string>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
@@ -23,6 +24,7 @@ ConfigureNetwork::ConfigureNetwork(const Core::System& system_, QWidget* parent)
|
|||||||
ConfigureNetwork::~ConfigureNetwork() = default;
|
ConfigureNetwork::~ConfigureNetwork() = default;
|
||||||
|
|
||||||
void ConfigureNetwork::ApplyConfiguration() {
|
void ConfigureNetwork::ApplyConfiguration() {
|
||||||
|
Settings::values.airplane_mode = ui->airplane_mode->isChecked();
|
||||||
Settings::values.network_interface = ui->network_interface->currentText().toStdString();
|
Settings::values.network_interface = ui->network_interface->currentText().toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +43,15 @@ void ConfigureNetwork::RetranslateUI() {
|
|||||||
void ConfigureNetwork::SetConfiguration() {
|
void ConfigureNetwork::SetConfiguration() {
|
||||||
const bool runtime_lock = !system.IsPoweredOn();
|
const bool runtime_lock = !system.IsPoweredOn();
|
||||||
|
|
||||||
|
ui->airplane_mode->setChecked(Settings::values.airplane_mode.GetValue());
|
||||||
|
ui->airplane_mode->setEnabled(runtime_lock);
|
||||||
|
|
||||||
const std::string& network_interface = Settings::values.network_interface.GetValue();
|
const std::string& network_interface = Settings::values.network_interface.GetValue();
|
||||||
|
|
||||||
ui->network_interface->setCurrentText(QString::fromStdString(network_interface));
|
ui->network_interface->setCurrentText(QString::fromStdString(network_interface));
|
||||||
ui->network_interface->setEnabled(runtime_lock);
|
ui->network_interface->setEnabled(runtime_lock && !ui->airplane_mode->isChecked());
|
||||||
|
|
||||||
|
connect(ui->airplane_mode, &QCheckBox::toggled, this, [this](bool checked) {
|
||||||
|
ui->network_interface->setEnabled(!checked && !system.IsPoweredOn());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,16 @@
|
|||||||
<string>General</string>
|
<string>General</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QCheckBox" name="airplane_mode">
|
||||||
|
<property name="text">
|
||||||
|
<string>Airplane Mode</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Disable all network functionality, similar to Nintendo Switch airplane mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QComboBox" name="network_interface"/>
|
<widget class="QComboBox" name="network_interface"/>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QRegularExpressionValidator>
|
#include <QRegularExpressionValidator>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QtConcurrent/QtConcurrentRun>
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
|
#include "common/logging/log.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/internal_network/network_interface.h"
|
#include "core/internal_network/network_interface.h"
|
||||||
@@ -57,6 +58,11 @@ void DirectConnectWindow::RetranslateUi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DirectConnectWindow::Connect() {
|
void DirectConnectWindow::Connect() {
|
||||||
|
if (!Network::GetSelectedNetworkInterface()) {
|
||||||
|
LOG_INFO(WebService, "Automatically selected network interface for room network.");
|
||||||
|
Network::SelectFirstNetworkInterface();
|
||||||
|
}
|
||||||
|
|
||||||
if (!Network::GetSelectedNetworkInterface()) {
|
if (!Network::GetSelectedNetworkInterface()) {
|
||||||
NetworkMessage::ErrorManager::ShowError(
|
NetworkMessage::ErrorManager::ShowError(
|
||||||
NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
|
NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBacken
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HostRoomWindow::Host() {
|
void HostRoomWindow::Host() {
|
||||||
|
if (!Network::GetSelectedNetworkInterface()) {
|
||||||
|
LOG_INFO(WebService, "Automatically selected network interface for room network.");
|
||||||
|
Network::SelectFirstNetworkInterface();
|
||||||
|
}
|
||||||
|
|
||||||
if (!Network::GetSelectedNetworkInterface()) {
|
if (!Network::GetSelectedNetworkInterface()) {
|
||||||
NetworkMessage::ErrorManager::ShowError(
|
NetworkMessage::ErrorManager::ShowError(
|
||||||
NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
|
NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
|
||||||
@@ -208,12 +213,17 @@ void HostRoomWindow::Host() {
|
|||||||
Settings::values.citron_username.GetValue(),
|
Settings::values.citron_username.GetValue(),
|
||||||
Settings::values.citron_token.GetValue());
|
Settings::values.citron_token.GetValue());
|
||||||
if (auto room = room_network.GetRoom().lock()) {
|
if (auto room = room_network.GetRoom().lock()) {
|
||||||
token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
|
const std::string verify_uid = room->GetVerifyUID();
|
||||||
}
|
if (!verify_uid.empty()) {
|
||||||
if (token.empty()) {
|
token = client.GetExternalJWT(verify_uid).returned_data;
|
||||||
LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
|
if (token.empty()) {
|
||||||
} else {
|
LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
|
||||||
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
|
} else {
|
||||||
|
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG(WebService, "Skipping JWT request: verify_uid is empty (room may not require verification)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
|||||||
std::string token;
|
std::string token;
|
||||||
#ifdef ENABLE_WEB_SERVICE
|
#ifdef ENABLE_WEB_SERVICE
|
||||||
if (!Settings::values.citron_username.GetValue().empty() &&
|
if (!Settings::values.citron_username.GetValue().empty() &&
|
||||||
!Settings::values.citron_token.GetValue().empty()) {
|
!Settings::values.citron_token.GetValue().empty() && !verify_uid.empty()) {
|
||||||
WebService::Client client(Settings::values.web_api_url.GetValue(),
|
WebService::Client client(Settings::values.web_api_url.GetValue(),
|
||||||
Settings::values.citron_username.GetValue(),
|
Settings::values.citron_username.GetValue(),
|
||||||
Settings::values.citron_token.GetValue());
|
Settings::values.citron_token.GetValue());
|
||||||
@@ -198,6 +198,8 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
|||||||
} else {
|
} else {
|
||||||
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
|
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
|
||||||
}
|
}
|
||||||
|
} else if (verify_uid.empty()) {
|
||||||
|
LOG_DEBUG(WebService, "Skipping JWT request: verify_uid is empty (room may not require verification)");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||||
|
|||||||
@@ -660,6 +660,7 @@ struct Values {
|
|||||||
Setting<bool> use_dev_keys{linkage, false, "use_dev_keys", Category::Miscellaneous};
|
Setting<bool> use_dev_keys{linkage, false, "use_dev_keys", Category::Miscellaneous};
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
|
Setting<bool> airplane_mode{linkage, false, "airplane_mode", Category::Network};
|
||||||
Setting<std::string> network_interface{linkage, std::string(), "network_interface",
|
Setting<std::string> network_interface{linkage, std::string(), "network_interface",
|
||||||
Category::Network};
|
Category::Network};
|
||||||
Setting<std::string> lobby_api_url{linkage, "https://api.ynet-fun.xyz", "lobby_api_url",
|
Setting<std::string> lobby_api_url{linkage, "https://api.ynet-fun.xyz", "lobby_api_url",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ enum class ErrorModule : u32 {
|
|||||||
Util = 33,
|
Util = 33,
|
||||||
TIPC = 35,
|
TIPC = 35,
|
||||||
ANIF = 37,
|
ANIF = 37,
|
||||||
|
Module38 = 38, // Unknown/Undefined module - stubbed for multiplayer compatibility
|
||||||
CRT = 39,
|
CRT = 39,
|
||||||
ETHC = 100,
|
ETHC = 100,
|
||||||
I2C = 101,
|
I2C = 101,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -65,16 +66,30 @@ enum class FatalType : u32 {
|
|||||||
|
|
||||||
static void GenerateErrorReport(Core::System& system, Result error_code, const FatalInfo& info) {
|
static void GenerateErrorReport(Core::System& system, Result error_code, const FatalInfo& info) {
|
||||||
const auto title_id = system.GetApplicationProcessProgramID();
|
const auto title_id = system.GetApplicationProcessProgramID();
|
||||||
|
const auto module = static_cast<u32>(error_code.GetModule());
|
||||||
|
const auto description = static_cast<u32>(error_code.GetDescription());
|
||||||
|
|
||||||
|
// Check if this is module 38 (undefined/unknown module)
|
||||||
|
std::string module_note;
|
||||||
|
if (module == 38) {
|
||||||
|
module_note = fmt::format(
|
||||||
|
"\n⚠️ WARNING: Error module 38 is undefined/unknown!\n"
|
||||||
|
"This error may be game-generated or from an unimplemented service.\n"
|
||||||
|
"Error code: 2038-{:04d} (0x{:08X})\n"
|
||||||
|
"If you're experiencing multiplayer issues, this may be a stubbing issue.\n\n",
|
||||||
|
description, error_code.raw);
|
||||||
|
}
|
||||||
|
|
||||||
std::string crash_report = fmt::format(
|
std::string crash_report = fmt::format(
|
||||||
"Citron {}-{} crash report\n"
|
"Citron {}-{} crash report\n"
|
||||||
"Title ID: {:016x}\n"
|
"Title ID: {:016x}\n"
|
||||||
"Result: 0x{:X} ({:04}-{:04d})\n"
|
"Result: 0x{:X} ({:04}-{:04d})\n"
|
||||||
"Set flags: 0x{:16X}\n"
|
"Set flags: 0x{:16X}\n"
|
||||||
"Program entry point: 0x{:16X}\n"
|
"Program entry point: 0x{:16X}\n"
|
||||||
|
"{}"
|
||||||
"\n",
|
"\n",
|
||||||
Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
|
Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
|
||||||
2000 + static_cast<u32>(error_code.GetModule()),
|
2000 + module, description, info.set_flags, info.program_entry_point, module_note);
|
||||||
static_cast<u32>(error_code.GetDescription()), info.set_flags, info.program_entry_point);
|
|
||||||
if (info.backtrace_size != 0x0) {
|
if (info.backtrace_size != 0x0) {
|
||||||
crash_report += "Registers:\n";
|
crash_report += "Registers:\n";
|
||||||
for (size_t i = 0; i < info.registers.size(); i++) {
|
for (size_t i = 0; i < info.registers.size(); i++) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -24,4 +25,13 @@ constexpr Result ResultLocalCommunicationIdNotFound{ErrorModule::LDN, 97};
|
|||||||
constexpr Result ResultLocalCommunicationVersionTooLow{ErrorModule::LDN, 113};
|
constexpr Result ResultLocalCommunicationVersionTooLow{ErrorModule::LDN, 113};
|
||||||
constexpr Result ResultLocalCommunicationVersionTooHigh{ErrorModule::LDN, 114};
|
constexpr Result ResultLocalCommunicationVersionTooHigh{ErrorModule::LDN, 114};
|
||||||
|
|
||||||
|
// Module 38 error codes - Unknown/undefined module
|
||||||
|
// These are stubbed to prevent crashes during multiplayer
|
||||||
|
// Error code format: 2038-XXXX where XXXX is the description
|
||||||
|
constexpr Result ResultModule38Error2618{ErrorModule::Module38, 2618}; // Reported during multiplayer
|
||||||
|
constexpr Result ResultModule38Generic{ErrorModule::Module38, 0}; // Generic module 38 error
|
||||||
|
constexpr Result ResultModule38NetworkError{ErrorModule::Module38, 100}; // Network-related
|
||||||
|
constexpr Result ResultModule38ConnectionFailed{ErrorModule::Module38, 200}; // Connection failure
|
||||||
|
constexpr Result ResultModule38Timeout{ErrorModule::Module38, 300}; // Operation timeout
|
||||||
|
|
||||||
} // namespace Service::LDN
|
} // namespace Service::LDN
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -187,6 +188,11 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::optional<NetworkInterface> GetSelectedNetworkInterface() {
|
std::optional<NetworkInterface> GetSelectedNetworkInterface() {
|
||||||
|
// If airplane mode is enabled, return no interface (similar to Switch's airplane mode)
|
||||||
|
if (Settings::values.airplane_mode.GetValue()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
const auto& selected_network_interface = Settings::values.network_interface.GetValue();
|
const auto& selected_network_interface = Settings::values.network_interface.GetValue();
|
||||||
const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
|
const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
|
||||||
if (network_interfaces.empty()) {
|
if (network_interfaces.empty()) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/socket_types.h"
|
#include "common/socket_types.h"
|
||||||
|
#include "core/internal_network/network_interface.h"
|
||||||
#include "enet/enet.h"
|
#include "enet/enet.h"
|
||||||
#include "network/packet.h"
|
#include "network/packet.h"
|
||||||
#include "network/room_member.h"
|
#include "network/room_member.h"
|
||||||
@@ -601,11 +602,23 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv
|
|||||||
room_member_impl->loop_thread.reset();
|
room_member_impl->loop_thread.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!room_member_impl->client) {
|
// Always recreate the client to ensure it uses the current network interface settings
|
||||||
room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
|
// This is necessary because the client might have been created with different settings
|
||||||
ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
|
if (room_member_impl->client) {
|
||||||
|
enet_host_destroy(room_member_impl->client);
|
||||||
|
room_member_impl->client = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For client connections, bind to ENET_HOST_ANY (0.0.0.0) to allow the OS to route
|
||||||
|
// based on the destination address. The selected network interface will be used
|
||||||
|
// by the OS routing table when connecting to the server.
|
||||||
|
ENetAddress bind_address{};
|
||||||
|
bind_address.host = ENET_HOST_ANY;
|
||||||
|
bind_address.port = 0; // Let the system choose an available port
|
||||||
|
|
||||||
|
room_member_impl->client = enet_host_create(&bind_address, 1, NumChannels, 0, 0);
|
||||||
|
ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
|
||||||
|
|
||||||
room_member_impl->SetState(State::Joining);
|
room_member_impl->SetState(State::Joining);
|
||||||
|
|
||||||
ENetAddress address{};
|
ENetAddress address{};
|
||||||
|
|||||||
@@ -138,7 +138,15 @@ struct Client::Impl {
|
|||||||
return WebResult{WebResult::Code::WrongContent, "", ""};
|
return WebResult{WebResult::Code::WrongContent, "", ""};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content_type->second.find(accept) == std::string::npos) {
|
// For GetExternalJWT, be more lenient with content-type to handle error responses
|
||||||
|
// The API may return text/plain for errors, but we still want to process the response
|
||||||
|
bool content_type_valid = content_type->second.find(accept) != std::string::npos;
|
||||||
|
if (!content_type_valid && accept == "text/html") {
|
||||||
|
// Also accept text/plain for JWT endpoints (error responses)
|
||||||
|
content_type_valid = content_type->second.find("text/plain") != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content_type_valid) {
|
||||||
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
|
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
|
||||||
content_type->second);
|
content_type->second);
|
||||||
return WebResult{WebResult::Code::WrongContent, "Wrong content", ""};
|
return WebResult{WebResult::Code::WrongContent, "Wrong content", ""};
|
||||||
|
|||||||
Reference in New Issue
Block a user