using Breath_of_the_Wild_Multiplayer.Source_files; using Breath_of_the_Wild_Multiplayer.MVVM.Model; using System; using System.Collections.ObjectModel; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; using System.Diagnostics; using Breath_of_the_Wild_Multiplayer.MVVM.Model.DTO; using System.Linq; using System.Threading.Tasks; using System.Text; using System.Xml; namespace Breath_of_the_Wild_Multiplayer.MVVM.ViewModel { public class ServerBrowserModel : ObservableObject { private ObservableCollection _serversToShow = new ObservableCollection(); public ObservableCollection serversToShow { get { return _serversToShow; } set { _serversToShow = value; OnPropertyChanged(); } } private bool _isMaxOffset; public bool isMaxOffset { get { return _isMaxOffset; } set { _isMaxOffset = value; OnPropertyChanged(); } } private int _scrollState; public int scrollState { get { return _scrollState; } set { _scrollState = value; OnPropertyChanged(); } } private ObservableCollection _selectedServer; public ObservableCollection selectedServer { get { return _selectedServer; } set { _selectedServer = value; OnPropertyChanged(); } } public bool IsModInstalled { get { string modActorsPack = $@"{BcmlSettings.MergedDir}\Actor\Pack"; // If the directory for the directory doesn't even exist. That is a clear indicator that the mod is not installed if (!Directory.Exists(modActorsPack)) return false; // One of the unique assets created by the mod are the actors called JugadorXX.sbactorpack. // If we don't have any Jugador, that means the mod is not installed. return Directory.GetFiles(modActorsPack).Any(actor => actor.Contains("Jugador") && Path.GetExtension(actor) == ".sbactorpack"); } } private Dictionary ServerMapping; public List ServerList; public RelayCommand serverButtonClick { get; set; } public AsyncRelayCommand connectClick { get; set; } public RelayCommand refreshClick { get; set; } public RelayCommand addServerClick { get; set; } public RelayCommand directConnectClick { get; set; } public int lastSelected = -1; public ServerBrowserModel() { SharedData.ServerBrowser = this; //findCemuData(); isMaxOffset = true; scrollState = 0; LoadServers(); serverButtonClick = new RelayCommand(o => this.changeSelected(o)); connectClick = new AsyncRelayCommand(o => this.connectToServer(null)); refreshClick = new RelayCommand(o => { foreach (var server in this._serversToShow) _ = Task.Run(() => server.pingServer()); }); addServerClick = new RelayCommand(o => { SharedData.MainView.updateTopView(new ServerEditorModel()); }); directConnectClick = new RelayCommand(o => { var DirectConnectEditor = new ServerEditorModel(); DirectConnectEditor.Setup(-2); SharedData.MainView.updateTopView(DirectConnectEditor); }); } void changeSelected(object newSelection) { if(lastSelected != -1) { _serversToShow[lastSelected].selected = false; } _serversToShow[(int)newSelection].selected = true; _selectedServer[0] = _serversToShow[(int)newSelection]; lastSelected = (int)newSelection; } void LoadServers() { serverDataModel.serversAdded = 0; serversToShow.Clear(); this.ServerList = JsonConvert.DeserializeObject>(Properties.Settings.Default.serversAdded)!; ServerMapping = new Dictionary(); foreach (var server in this.ServerList.Where(sv => sv.Favorite)) { serversToShow.Add(new serverDataModel(!string.IsNullOrEmpty(BcmlSettings.GameDir) && !string.IsNullOrEmpty(BcmlSettings.CemuDir), name: server.Name, ip: server.IP, port: server.Port, favorite: server.Favorite, password: server.Password)); ServerMapping[serverDataModel.serversAdded - 1] = server; } foreach (var server in this.ServerList.Where(sv => !sv.Favorite)) { serversToShow.Add(new serverDataModel(!string.IsNullOrEmpty(BcmlSettings.GameDir) && !string.IsNullOrEmpty(BcmlSettings.CemuDir), name: server.Name, ip: server.IP, port: server.Port, favorite: server.Favorite, password: server.Password)); ServerMapping[serverDataModel.serversAdded - 1] = server; } selectedServer = new ObservableCollection(); if (_serversToShow.Count == 0) { selectedServer.Add(new serverDataModel(!string.IsNullOrEmpty(BcmlSettings.GameDir) && !string.IsNullOrEmpty(BcmlSettings.CemuDir), name: "", ip: "", port: 0, favorite: false, password: "")); } else { selectedServer.Add(_serversToShow[0]); serversToShow[0].selected = true; lastSelected = 0; } } //void findCemuData() //{ // try // { // string CemuSettings = File.ReadAllText(Properties.Settings.Default.bcmlLocation); // Dictionary settings = JsonConvert.DeserializeObject>(CemuSettings)!; // CemuDir = settings["cemu_dir"]; // GameDir = settings["game_dir"]; // } // catch(Exception ex) // { // CemuDir = ""; // GameDir = ""; // } //} async Task connectToServer(serverDataModel serverToJoin = null) { bool directConnection = serverToJoin != null; if (serverToJoin == null) serverToJoin = _selectedServer[0]; if (string.IsNullOrEmpty(BcmlSettings.GameDir) || string.IsNullOrEmpty(BcmlSettings.CemuDir)) throw new ApplicationException("Bcml not setup."); if (!IsModInstalled) throw new ApplicationException("Mod is not setup on BCML. Make sure to follow the instructions to install the mod."); string cemuSettingsPath = Path.Combine(BcmlSettings.CemuDir, "settings.xml"); // Does the settings file for cemu exist? if (!File.Exists(cemuSettingsPath)) throw new ApplicationException("Cemu settings file doesn't exist. Make sure to have open cemu and setup the graphic packs"); XmlDocument doc = new XmlDocument(); doc.Load(cemuSettingsPath); XmlNode GraphicPacksNode = doc.DocumentElement.SelectSingleNode("/content/GraphicPack"); Dictionary nodesToValidate = new Dictionary() { {" · Utilities", "bcmlPatches\\MilkBarLauncher\\rules.txt"}, {" · Extended Memory", "BreathOfTheWild\\Mods\\ExtendedMemory\\rules.txt" } }; List missingGraphicPacks = new List(); try { missingGraphicPacks = nodesToValidate .Where(sNode => !GraphicPacksNode.ChildNodes.Cast().Any(node => node.Attributes.Cast().Any(attribute => attribute.Value.Contains(sNode.Value)))).Select(sNode => sNode.Key).ToList(); } catch(Exception ex) { // If any issue happened while reading settings xml. throw new ApplicationException("Failed to read cemu graphic packs settings. Please make sure that your cemu is setup correctly."); } if(missingGraphicPacks.Any()) throw new ApplicationException("The following needed graphic packs are not selected on Cemu\n\n" + string.Join("\n", missingGraphicPacks) + "\n\nPlease make sure to select them before joining a server."); if (string.IsNullOrEmpty(serverToJoin.IP) || !serverToJoin.open) return; serverToJoin.pingServer(); if (!serverToJoin.open) { if (directConnection) throw new Exception("Could not connect to server. The server may not be open."); else return; } CharacterModel charModel = JsonConvert.DeserializeObject(Properties.Settings.Default.playerModel); SharedData.SetLoadingMessage("Loading player data..."); await Task.Run(() => GameFilesModifier.CreateModifiedModel()); await Task.Run(() => GameFilesModifier.ChangeAttentionForJugadores(serverToJoin.playStyle != "Prop hunt")); await Task.Run(() => GameFilesModifier.CleanAnimations()); if (!Properties.Settings.Default.playAsModel) { await Task.Run(() => GameFilesModifier.ModifyGameROMPlayerModel("Link", "Link", false)); } else { switch (charModel.Type) { case CharacterModel.ModelType.ArmorSync: // TODO: Add implementation for other armors with armor sync await Task.Run(() => GameFilesModifier.ModifyGameROMPlayerModel(charModel.Name, charModel.Name, false)); break; case CharacterModel.ModelType.CustomModel: // We found that the game is only capable of having NPCs instead of Link. Therefore, we only change Link's model if it is an NPC if (charModel.Model.StartsWith("Npc_")) await Task.Run(() => GameFilesModifier.ModifyGameROMPlayerModel(charModel.Model, charModel.Model, true)); else await Task.Run(() => GameFilesModifier.ModifyGameROMPlayerModel("Link", "Link", false)); break; case CharacterModel.ModelType.Mii: // TODO: Miis are not implemented. We found that trying to change Link to a Mii, you get issues when trying to find certain things in memory. // If you want to work on this, go to the InjectDLL project, into the file MemoryAccess/LocalInstance.h // There you can find the scan method. There we are looking for all the necessary addresses in memory. The specific memories that we found have issues are Animation and IsEquipped. await Task.Run(() => GameFilesModifier.ModifyGameROMPlayerModel("Link", "Link", false)); break; } } await Task.Delay(2000); List ProcessesToFilter = Injector.GetProcesses("Cemu"); SharedData.SetLoadingMessage("Starting Cemu..."); await Task.Run(() => Process.Start($"{BcmlSettings.CemuDir}/cemu.exe", $"-g \"{BcmlSettings.GameDir.Replace("content", "code")}/U-King.rpx\"")); await Task.Delay(500); SharedData.SetLoadingMessage("Injecting Cemu..."); Process CemuProcess = null; await Task.Run(() => { CemuProcess = Injector.Inject("Cemu", Directory.GetCurrentDirectory() + "\\Resources\\InjectDLL.dll", ProcessesToFilter); }); SharedData.SetLoadingMessage("Starting pipe..."); try { await Task.Run(NamedPipes.StartServer); } catch (Exception ex) { CemuProcess.Kill(); throw ex; } SharedData.SetLoadingMessage("Connecting to server..."); List instruction = Encoding.UTF8.GetBytes($"!connect;{serverToJoin.IP};{serverToJoin.Port};{serverToJoin.Password};{Properties.Settings.Default.playerName};{serverToJoin.Name};{(int)charModel.Type};").ToList(); if ((int)charModel.Type < 2) { if(charModel.Model.StartsWith("Npc_")) { instruction.AddRange(Encoding.UTF8.GetBytes($"{charModel.Model}:{charModel.Model}")); } else { instruction.AddRange(Encoding.UTF8.GetBytes($"{charModel.Model}:{charModel.Name}")); } } else instruction.AddRange(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(charModel.BumiiData))); instruction.AddRange(Encoding.UTF8.GetBytes(";[END]")); await Task.Run(() => { if (!NamedPipes.sendInstruction(instruction.ToArray())) { CemuProcess.Kill(); throw new ApplicationException("Could not connect to server. Internal connection failed."); } Task.Delay(50); if (!NamedPipes.sendInstruction($"!startServerLoop")) { CemuProcess.Kill(); throw new ApplicationException("Could not start server loop."); } }); SharedData.SetLoadingMessage("Starting overlay..."); await Task.Delay(2000); SharedData.MainView.currentView = SharedData.MainView.IngameMenuVM; SharedData.MainView.disableBackground = true; SharedData.MainView.Window.Topmost = true; CemuFollower.Setup(CemuProcess); NamedPipes.StartListenThread(); SharedData.SetLoadingMessage(); } void SaveServerSettings() { Properties.Settings.Default.serversAdded = JsonConvert.SerializeObject(this.ServerList); Properties.Settings.Default.Save(); } public void ModifyServer(ServerInfoModel serverinfo) { if(serverinfo.serverIndex == -1) { var serverToAdd = new LocalServerDTO() { Name = serverinfo.name, IP = serverinfo.ip, Port = Int32.Parse(serverinfo.port), Password = serverinfo.password, Favorite = false }; this.ServerList.Add(serverToAdd); this.serversToShow.Add(new serverDataModel(!string.IsNullOrEmpty(BcmlSettings.GameDir) && !string.IsNullOrEmpty(BcmlSettings.CemuDir), name: serverinfo.name, ip: serverinfo.ip, port: Int32.Parse(serverinfo.port), favorite: false, password: serverinfo.password)); this.ServerMapping[serverDataModel.serversAdded - 1] = serverToAdd; changeSelected(this._serversToShow.Count - 1); SaveServerSettings(); } else if(serverinfo.serverIndex == -2) { serverDataModel serverToconnect = new serverDataModel(!string.IsNullOrEmpty(BcmlSettings.GameDir) && !string.IsNullOrEmpty(BcmlSettings.CemuDir), name: serverinfo.name, ip: serverinfo.ip, port: Int32.Parse(serverinfo.port), favorite: false, password: serverinfo.password, async: false); if (!serverToconnect.open) throw new Exception("Could not connect to server. Check if the server is open."); this.connectToServer(serverToconnect); } else { this.ServerMapping[serverinfo.serverIndex].Name = serverinfo.name; this.ServerMapping[serverinfo.serverIndex].IP = serverinfo.ip; this.ServerMapping[serverinfo.serverIndex].Port = Int32.Parse(serverinfo.port); this.ServerMapping[serverinfo.serverIndex].Password = serverinfo.password; this.serversToShow[serverinfo.serverIndex].Name = serverinfo.name; this.serversToShow[serverinfo.serverIndex].IP = serverinfo.ip; this.serversToShow[serverinfo.serverIndex].Port = Int32.Parse(serverinfo.port); this.serversToShow[serverinfo.serverIndex].Password = serverinfo.password; SaveServerSettings(); this.selectedServer[0].pingServer(); } } public void EditServer() { var DirectConnectEditor = new ServerEditorModel(); DirectConnectEditor.Setup(this.selectedServer[0].serverIndex, this.selectedServer[0].Name, this.selectedServer[0].IP, this.selectedServer[0].Port.ToString(), this.selectedServer[0].Password.ToString()); SharedData.MainView.updateTopView(DirectConnectEditor); } public void RemoveServer() { this.ServerList.Remove(this.ServerMapping[this.selectedServer[0].serverIndex]); SaveServerSettings(); LoadServers(); } public void ChangeFavorite(int serverIndex) { this.ServerMapping[serverIndex].Favorite = this.serversToShow[serverIndex].favorite; SaveServerSettings(); } } }