Repository: pythonology/BeatTogether
Branch: master
Commit: 82fd4a48370c
Files: 22
Total size: 51.6 KB
Directory structure:
gitextract_ln948ubu/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── Bug_Report.yml
│ │ └── config.yml
│ └── workflows/
│ ├── Build.yml
│ └── PR_Build.yml
├── .gitignore
├── BeatTogether/
│ ├── BeatTogether.csproj
│ ├── Config.cs
│ ├── Directory.Build.props
│ ├── Directory.Build.targets
│ ├── Installers/
│ │ ├── BtAppInstaller.cs
│ │ └── BtMenuInstaller.cs
│ ├── Models/
│ │ ├── ServerDetails.cs
│ │ └── TemporaryServerDetails.cs
│ ├── Plugin.cs
│ ├── Registries/
│ │ └── ServerDetailsRegistry.cs
│ ├── UI/
│ │ ├── ServerSelectionController.bsml
│ │ └── ServerSelectionController.cs
│ └── manifest.json
├── BeatTogether.sln
├── LICENSE
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
patreon: BeatTogether
================================================
FILE: .github/ISSUE_TEMPLATE/Bug_Report.yml
================================================
name: "Bug report 🐛"
description: Report issues, errors or unexpected behavior
title: "[Bug]: "
labels: [bug]
assignees:
- michael-r-elp
body:
- type: markdown
attributes:
value: |
Please make sure to search for existing issues before making a new one!
- type: textarea
attributes:
label: Describe the bug
placeholder: "Thing 'x' isn't working when I do 'y', etc."
description: |
A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
placeholder: Steps to reproduce the behavior.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: If applicable, add screenshots to help explain your problem.
placeholder: What were you expecting?
validations:
required: false
- type: textarea
attributes:
label: Actual Behavior
placeholder: What happened instead?
validations:
required: true
- type: input
attributes:
label: Game Version
placeholder: "1.29.0"
description: |
The version of the game you were running
validations:
required: true
- type: input
attributes:
label: Mod Version
placeholder: "0.1.0"
description: |
The version of the mod you were using
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: Add any other context about the problem here.
validations:
required: false
#- type: checkboxes
# id: terms
# attributes:
# label: Code of Conduct
# description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/BeatTogether/.github/blob/main/CODE_OF_CONDUCT.md)
# options:
# - label: I agree to follow this project's Code of Conduct
# required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true # Enabled for now until all issue templates are added
contact_links:
- name: BeatTogether Community Discord
url: https://discord.com/invite/gezGrFG4tz
about: Please ask and answer questions here.
================================================
FILE: .github/workflows/Build.yml
================================================
name: Build
on:
push:
branches: [ master ]
paths:
- 'BeatTogether.sln'
- 'BeatTogether/**'
- '.github/workflows/Build.yml'
jobs:
Build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup dotnet
uses: actions/setup-dotnet@v3
with:
dotnet-version: '5.0.x'
- name: Fetch SIRA References
uses: ProjectSIRA/download-sira-stripped@1.0.0
with:
manifest: ./BeatTogether/manifest.json
sira-server-code: ${{ secrets.SIRA_SERVER_CODE }}
- name: Fetch Mod References
uses: Goobwabber/download-beatmods-deps@1.1
with:
manifest: ./BeatTogether/manifest.json
- name: Build
id: Build
env:
FrameworkPathOverride: /usr/lib/mono/4.8-api
run: dotnet build --configuration Release
- name: GitStatus
run: git status
- name: Echo Filename
run: echo $BUILDTEXT \($ASSEMBLYNAME\)
env:
BUILDTEXT: Filename=${{ steps.Build.outputs.filename }}
ASSEMBLYNAME: AssemblyName=${{ steps.Build.outputs.assemblyname }}
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
name: ${{ steps.Build.outputs.filename }}
path: ${{ steps.Build.outputs.artifactpath }}
================================================
FILE: .github/workflows/PR_Build.yml
================================================
name: Pull Request Build
on:
pull_request:
branches: [ master ]
paths:
- 'BeatTogether.sln'
- 'BeatTogether/**'
- '.github/workflows/PR_Build.yml'
jobs:
Build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup dotnet
uses: actions/setup-dotnet@v3
with:
dotnet-version: 5.0.x
- name: Fetch SIRA References
uses: ProjectSIRA/download-sira-stripped@1.0.0
with:
manifest: ./BeatTogether/manifest.json
sira-server-code: ${{ secrets.SIRA_SERVER_CODE }}
- name: Fetch Mod References
uses: Goobwabber/download-beatmods-deps@1.1
with:
manifest: ./BeatTogether/manifest.json
- name: Build
id: Build
env:
FrameworkPathOverride: /usr/lib/mono/4.8-api
run: dotnet build --configuration Release
- name: GitStatus
run: git status
- name: Echo Filename
run: echo $BUILDTEXT \($ASSEMBLYNAME\)
env:
BUILDTEXT: Filename=${{ steps.Build.outputs.filename }}
ASSEMBLYNAME: AssemblyName=${{ steps.Build.outputs.assemblyname }}
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
name: ${{ steps.Build.outputs.filename }}
path: ${{ steps.Build.outputs.artifactpath }}
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
launchSettings.json
================================================
FILE: BeatTogether/BeatTogether.csproj
================================================
Library
Properties
BeatTogether
2.2.1
net472
true
portable
..\Refs
$(LocalRefsDir)
prompt
4
9.0
enable
Unofficial
local
false
DEBUG;TRACE
true
True
True
True
$(BeatSaberDir)\Libs\0Harmony.dll
False
..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Beat Saber\Beat Saber_Data\Managed\BeatSaber.ViewSystem.dll
False
$(BeatSaberDir)\Plugins\BSML.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll
False
False
True
$(BeatSaberDir)\Plugins\MultiplayerCore.dll
False
False
$(BeatSaberDir)\Plugins\SiraUtil.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\DataModels.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\BGLib.Polyglot.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\HMLib.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\HMUI.dll
False
True
$(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UI.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\Zenject.dll
False
$(BeatSaberDir)\Beat Saber_Data\Managed\Zenject-usage.dll
False
all
runtime; build; native; contentfiles; analyzers; buildtransitive
all
runtime; build; native; contentfiles; analyzers; buildtransitive
Official
$(VersionType)-$(GitBranch)-$(CommitHash)
$(VersionType)-$(GitBranch)-$(CommitHash)-$(GitModified)
================================================
FILE: BeatTogether/Config.cs
================================================
using System.Collections.Generic;
using System.Linq;
using BeatTogether.Models;
using IPA.Config.Stores.Attributes;
using IPA.Config.Stores.Converters;
namespace BeatTogether
{
public class Config
{
// Official master server name that will be seen by players
public const string OfficialServerName = "Official Servers";
// BeatTogether master server config
public const int DefaultApiPort = 8989;
public const string BeatTogetherServerName = "BeatTogether";
public const string BeatTogetherHostName = "master.beattogether.systems";
public const string BeatTogetherApiUri = "http://master.beattogether.systems:8989";
public const string BeatTogetherStatusUri = "http://master.beattogether.systems/status";
public const int BeatTogetherMaxPartySize = 100;
public virtual string SelectedServer { get; set; } = BeatTogetherServerName;
[NonNullable, UseConverter(typeof(CollectionConverter>))]
public virtual List Servers { get; set; } = new();
public virtual void OnReload()
{
var haveBtServer = false;
foreach (var server in Servers)
{
if (server.ServerName == BeatTogetherServerName)
haveBtServer = true;
// Try to auto migrate API URL if missing from older configs
if (string.IsNullOrEmpty(server.ApiUrl))
server.ApiUrl = $"http://{server.HostName}:{DefaultApiPort}";
}
if (!haveBtServer)
{
Servers.Insert(0, new ServerDetails
{
ServerName = BeatTogetherServerName,
HostName = BeatTogetherHostName,
ApiUrl = BeatTogetherApiUri,
StatusUri = BeatTogetherStatusUri,
MaxPartySize = BeatTogetherMaxPartySize,
DisableSsl = true
});
}
}
public virtual void CopyFrom(Config other)
{
SelectedServer = other.SelectedServer;
Servers = other.Servers;
}
}
}
================================================
FILE: BeatTogether/Directory.Build.props
================================================
true
true
true
false
true
true
================================================
FILE: BeatTogether/Directory.Build.targets
================================================
2.0
false
$(OutputPath)$(AssemblyName)
$(OutputPath)Final
True
$(PluginVersion)
$(PluginVersion)
$(PluginVersion)
$(AssemblyName)
$(ArtifactName)-$(PluginVersion)
$(ArtifactName)-bs$(GameVersion)
$(ArtifactName)-$(CommitHash)
$(AssemblyName)
$(AssemblyName)
$(OutDir)zip\
$(BeatSaberDir)\Plugins
True
Unable to copy assembly to game folder, did you set 'BeatSaberDir' correctly in your 'csproj.user' file? Plugins folder doesn't exist: '$(PluginDir)'.
Unable to copy to Plugins folder, '$(BeatSaberDir)' does not appear to be a Beat Saber game install.
Unable to copy to Plugins folder, 'BeatSaberDir' has not been set in your 'csproj.user' file.
False
$(BeatSaberDir)\IPA\Pending\Plugins
================================================
FILE: BeatTogether/Installers/BtAppInstaller.cs
================================================
using BeatTogether.Registries;
using Zenject;
namespace BeatTogether.Installers
{
class BtAppInstaller : Installer
{
private readonly Config _config;
public BtAppInstaller(
Config config)
{
_config = config;
}
public override void InstallBindings()
{
Container.BindInstance(_config).AsSingle();
Container.BindInterfacesAndSelfTo().AsSingle();
}
}
}
================================================
FILE: BeatTogether/Installers/BtMenuInstaller.cs
================================================
using BeatTogether.UI;
using Zenject;
namespace BeatTogether.Installers
{
class BtMenuInstaller : Installer
{
public override void InstallBindings()
{
Container.BindInterfacesAndSelfTo().AsSingle();
}
}
}
================================================
FILE: BeatTogether/Models/ServerDetails.cs
================================================
using System;
namespace BeatTogether.Models
{
public class ServerDetails
{
///
/// Display name for UI
///
public string ServerName { get; set; } = string.Empty;
///
/// Legacy hostname for master server (no longer used, except for automatic config migrations)
///
public string HostName { get; set; } = string.Empty;
///
/// The multiplayer API url / graph url
///
public string ApiUrl { get; set; } = string.Empty;
///
/// Optional status check URL for the server
///
public string StatusUri { get; set; } = string.Empty;
///
/// Max amount of players per instance
///
public int MaxPartySize { get; set; } = 5;
///
/// If set: disable SSL and certificate validation for all Ignorance/ENet client connections.
///
public bool DisableSsl { get; set; } = true;
public bool IsOfficial => ServerName == Config.OfficialServerName;
///
/// Gets whether this server matches against a given override URL.
/// Only checks whether the hostname and port match.
///
public bool MatchesApiUrl(string? apiUrl)
{
if (apiUrl == ApiUrl)
// Exact match
return true;
if (string.IsNullOrEmpty(apiUrl))
return false;
// Loose match
try
{
var urlOurs = new Uri(ApiUrl);
var urlTheirs = new Uri(apiUrl);
return urlOurs.Host == urlTheirs.Host &&
urlOurs.Port == urlTheirs.Port;
}
catch (UriFormatException)
{
return false;
}
}
public override string ToString() => ServerName;
}
}
================================================
FILE: BeatTogether/Models/TemporaryServerDetails.cs
================================================
using System;
namespace BeatTogether.Models
{
public class TemporaryServerDetails : ServerDetails
{
public TemporaryServerDetails(string graphApiUrl, string? statusUrl)
{
try
{
var urlParsed = new Uri(graphApiUrl);
ServerName = urlParsed.Host;
HostName = urlParsed.Host;
}
catch (UriFormatException)
{
ServerName = graphApiUrl;
HostName = graphApiUrl;
}
ApiUrl = graphApiUrl;
StatusUri = statusUrl ?? graphApiUrl;
MaxPartySize = IsOfficial ? 5 : 128;
DisableSsl = true;
}
}
}
================================================
FILE: BeatTogether/Plugin.cs
================================================
using BeatTogether.Installers;
using HarmonyLib;
using IPA;
using IPA.Config.Stores;
using IPA.Loader;
using SiraUtil.Zenject;
using Conf = IPA.Config.Config;
using IPALogger = IPA.Logging.Logger;
namespace BeatTogether
{
[Plugin(RuntimeOptions.SingleStartInit)]
class Plugin
{
private readonly Harmony _harmony;
private readonly PluginMetadata _metadata;
public const string ID = "com.Python.BeatTogether";
[Init]
public Plugin(IPALogger logger, Conf conf, PluginMetadata metadata, Zenjector zenjector)
{
Config config = conf.Generated();
_harmony = new Harmony(ID);
_metadata = metadata;
zenjector.UseLogger(logger);
zenjector.Install(Location.App, config);
zenjector.Install(Location.Menu);
}
[OnEnable]
public void OnEnable()
{
_harmony.PatchAll(_metadata.Assembly);
}
[OnDisable]
public void OnDisable()
{
_harmony.UnpatchSelf();
}
}
}
================================================
FILE: BeatTogether/Registries/ServerDetailsRegistry.cs
================================================
using BeatTogether.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BeatTogether.Registries
{
public class ServerDetailsRegistry
{
public ServerDetails SelectedServer
=> TemporarySelectedServer
?? Servers.FirstOrDefault(details => details.ServerName == _config.SelectedServer)
?? Servers.FirstOrDefault(details => details.ServerName == Config.BeatTogetherServerName);
public IReadOnlyList Servers
=> _config.Servers.Concat(_servers).Append(OfficialServer).ToList();
private readonly Config _config;
private readonly List _servers = new();
public readonly ServerDetails OfficialServer = new()
{
ServerName = Config.OfficialServerName
};
public TemporaryServerDetails? TemporarySelectedServer { get; private set; }
internal ServerDetailsRegistry(
Config config)
{
_config = config;
}
public void AddServer(ServerDetails server)
{
if (Servers.Any(details => details.ServerName == server.ServerName))
throw new ArgumentException($"A server already exists with the name {server.ServerName}.");
_servers.Add(server);
}
public void SetSelectedServer(ServerDetails server)
{
if (server is TemporaryServerDetails tmpServer)
{
TemporarySelectedServer = tmpServer;
}
else
{
_config.SelectedServer = server.ServerName;
TemporarySelectedServer = null;
}
}
}
}
================================================
FILE: BeatTogether/UI/ServerSelectionController.bsml
================================================
================================================
FILE: BeatTogether/UI/ServerSelectionController.cs
================================================
using BeatSaberMarkupLanguage;
using BeatSaberMarkupLanguage.Attributes;
using BeatSaberMarkupLanguage.Components.Settings;
using BeatSaberMarkupLanguage.FloatingScreen;
using BeatTogether.Models;
using BeatTogether.Registries;
using HMUI;
using IPA.Utilities;
using MultiplayerCore.Patchers;
using SiraUtil.Affinity;
using SiraUtil.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using UnityEngine;
using Zenject;
using System.Threading;
using BGLib.Polyglot;
using MultiplayerCore.Models;
using MultiplayerCore.Repositories;
namespace BeatTogether.UI
{
internal class ServerSelectionController : IInitializable, IAffinity, INotifyPropertyChanged
{
public const string ResourcePath = "BeatTogether.UI.ServerSelectionController.bsml";
private FloatingScreen _screen = null!;
private readonly MultiplayerModeSelectionFlowCoordinator _modeSelectionFlow;
private readonly JoiningLobbyViewController _joiningLobbyView;
private readonly NetworkConfigPatcher _networkConfig;
private readonly MpStatusRepository _mpStatusRepository;
private readonly ServerDetailsRegistry _serverRegistry;
private readonly SiraLog _logger;
private bool _isFirstActivation;
private uint _allowSelectionOnce;
[UIComponent("server-list")] private ListSetting _serverList = null!;
[UIValue("server")]
private ServerDetails _serverValue
{
get => _serverRegistry.SelectedServer;
set => ApplySelectedServer(value);
}
[UIValue("server-options")] private List