MainWindowViewModel.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. using Avalonia.Controls.ApplicationLifetimes;
  2. using ReactiveUI;
  3. using System;
  4. using System.Collections.ObjectModel;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Text;
  8. using System.Text.Json;
  9. using System.Threading.Tasks;
  10. using VeloeMinecraftLauncher.Entity.LauncherProfiles;
  11. using VeloeMinecraftLauncher.MinecraftLauncher;
  12. using VeloeMinecraftLauncher.Views;
  13. using Microsoft.AspNetCore.SignalR.Client;
  14. using VeloeMinecraftLauncher.Entity.McStatus;
  15. using System.Timers;
  16. using System.Reflection;
  17. using Serilog;
  18. using Avalonia.Controls;
  19. using Avalonia.Threading;
  20. using System.Net;
  21. using System.IO.Compression;
  22. using ReactiveUI.Validation.Extensions;
  23. using Avalonia.Media;
  24. using Avalonia.Data;
  25. using System.Collections.Generic;
  26. using VeloeMinecraftLauncher.Models.Entity;
  27. using VeloeMinecraftLauncher.Models;
  28. namespace VeloeMinecraftLauncher.ViewModels;
  29. public class MainWindowViewModel : ViewModelBase
  30. {
  31. public MainWindowViewModel()
  32. {
  33. Task.Run(() =>
  34. {
  35. try
  36. {
  37. //creating logger
  38. EventSink eventSink = new(null);
  39. eventSink.DataReceived += LogHandler;
  40. _logger = new LoggerConfiguration()
  41. .MinimumLevel.Debug()
  42. .WriteTo.Sink(eventSink , Settings.consoleLogEventLevel)
  43. .WriteTo.File("launcher.log", Settings.fileLogEventLevel, fileSizeLimitBytes: Settings.MaxLog * 1024, rollOnFileSizeLimit: true)// restricted... is Optional
  44. .CreateLogger();
  45. Settings.logger = _logger;
  46. //loading settings
  47. _logger.Debug("Loading settings.");
  48. Settings.LoadSettings();
  49. _username = Settings.Username;
  50. //loading local verions
  51. _logger.Debug("Loading local versions.");
  52. updateAvailable();
  53. }
  54. catch (Exception ex)
  55. {
  56. OpenErrorWindow(ex);
  57. }
  58. this.ValidationRule(
  59. viewModel => viewModel.Username,
  60. value => { return !string.IsNullOrEmpty(value); },
  61. "Empty username.");
  62. UpdateUpdater();
  63. try
  64. {
  65. //check launcher update
  66. _logger.Information("Checking launcher versions updates.");
  67. _latestLauncherInfo = Downloader.DownloadAndDeserializeJsonData<LatestLauncherVersion>("https://files.veloe.link/launcher/update/versions.json");
  68. if (_latestLauncherInfo is not null)
  69. {
  70. _logger.Information("Launcher version on server: {0}", _latestLauncherInfo.Latest);
  71. _logger.Information("Launcher version: {0}", Assembly.GetExecutingAssembly().GetName().Version);
  72. if (new Version(_latestLauncherInfo.Latest) > Assembly.GetExecutingAssembly().GetName().Version)
  73. {
  74. _logger.Debug("Update available!");
  75. IsUpdateAvailable = true;
  76. }
  77. }
  78. }
  79. catch (Exception ex)
  80. {
  81. OpenErrorWindow(ex);
  82. }
  83. try
  84. {
  85. var changelog = Downloader.DownloadAndDeserializeJsonData<List<Changelog>>("https://files.veloe.link/launcher/changelog.json");
  86. if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  87. {
  88. Dispatcher.UIThread.InvokeAsync(() =>
  89. {
  90. var stackpanel = desktop.MainWindow.GetControl<StackPanel>("ChangeLogStackPanel");
  91. foreach (var version in changelog)
  92. {
  93. if (version.Title is not null)
  94. {
  95. stackpanel.Children.Add(new TextBlock()
  96. {
  97. Text = version.Title,
  98. VerticalAlignment = Avalonia.Layout.VerticalAlignment.Stretch,
  99. TextWrapping = TextWrapping.Wrap,
  100. FontSize = 16
  101. });
  102. }
  103. stackpanel.Children.Add(new TextBlock()
  104. {
  105. Text = version.Text,
  106. VerticalAlignment = Avalonia.Layout.VerticalAlignment.Stretch,
  107. TextWrapping = TextWrapping.Wrap
  108. });
  109. }
  110. });
  111. }
  112. }
  113. catch (Exception ex)
  114. {
  115. OpenErrorWindow(ex);
  116. }
  117. try
  118. {
  119. _logger.Debug("Connecting to WebSoket");
  120. //conection to my servers
  121. var serverNames = Downloader.DownloadAndDeserializeJsonData<List<string>>("https://files.veloe.link/launcher/servers.json");
  122. _connection = new HubConnectionBuilder()
  123. .WithUrl("https://monitor.veloe.link/hubs/data")
  124. .WithAutomaticReconnect()
  125. .Build();
  126. Func<Exception, Task> reconnecting = ex => Task.Run(() =>
  127. {
  128. _logger.Warning("Reconnecting to WebCoket...");
  129. });
  130. Func<string, Task> reconnected = str => Task.Run(() =>
  131. {
  132. _logger.Warning("Reconnected to WebCoket.");
  133. foreach (var server in serverNames)
  134. {
  135. _connection.InvokeAsync("ConnectToGroup", server);
  136. }
  137. });
  138. _connection.Reconnecting += reconnecting;
  139. _connection.Reconnected += reconnected;
  140. if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  141. {
  142. Dispatcher.UIThread.InvokeAsync(() =>
  143. {
  144. var stackpanel = desktop.MainWindow.GetControl<StackPanel>("ServersStackPanel");
  145. foreach (var server in serverNames)
  146. stackpanel.Children.Add(CreateServerPanel(server));
  147. });
  148. }
  149. _connection.StartAsync();
  150. foreach (var server in serverNames)
  151. {
  152. _connection.InvokeAsync("ConnectToGroup", server);
  153. }
  154. _logger.Debug("Connected to WebSoket");
  155. _consoleOutputTimer.Elapsed += UpdateConsoleOutput;
  156. _consoleOutputTimer.AutoReset = false;
  157. }
  158. catch (Exception ex)
  159. {
  160. OpenErrorWindow(ex);
  161. }
  162. });
  163. }
  164. System.Timers.Timer _consoleOutputTimer = new(250);
  165. private HubConnection _connection;
  166. private string _downloadButton = "Download versions";
  167. private string _startButton = "Start Minecraft";
  168. private string _username = "";
  169. private StringBuilder _consoleText = new StringBuilder();
  170. private int _downloadedIndex;
  171. private string _argumentsBox;
  172. private bool _isNoGameRunning = true;
  173. private bool _isUpdateAvailable = false;
  174. private string _settingsButton = "Settings";
  175. private string _startButtonOutput;
  176. ILogger _logger;
  177. LatestLauncherVersion _latestLauncherInfo;
  178. DownloadedVersion _downloadedVersion;
  179. ObservableCollection<DownloadedVersion> _downloadedVersions;
  180. public ObservableCollection<DownloadedVersion> DownloadedVersions {
  181. get => _downloadedVersions;
  182. set
  183. {
  184. this.RaiseAndSetIfChanged(ref _downloadedVersions, value);
  185. }
  186. }
  187. public DownloadedVersion DownloadedVerion
  188. {
  189. get => _downloadedVersion;
  190. set
  191. {
  192. this.RaiseAndSetIfChanged(ref _downloadedVersion, value);
  193. //Settings.lastChosenVersion = value.version;
  194. //logger.Debug("Version choosen: {0}",value.version);
  195. //logger.Debug("Version json: {0}", value.path);
  196. }
  197. }
  198. public int DownloadedIndex
  199. {
  200. get => _downloadedIndex;
  201. set
  202. {
  203. this.RaiseAndSetIfChanged(ref _downloadedIndex, value);
  204. if (value >= 0 && value < DownloadedVersions.Count)
  205. DownloadedVerion = DownloadedVersions[value];
  206. }
  207. }
  208. public string Greeting => "Welcome to Cringe Launcher!";
  209. public string DownloadButton {
  210. get => _downloadButton;
  211. set => this.RaiseAndSetIfChanged(ref _downloadButton, value);
  212. }
  213. public string StartButton
  214. {
  215. get => _startButton;
  216. set => this.RaiseAndSetIfChanged(ref _startButton, value);
  217. }
  218. public string Username
  219. {
  220. get => _username;
  221. set
  222. {
  223. this.RaiseAndSetIfChanged(ref _username, value);
  224. this.RaisePropertyChanged(nameof(IsStartButtonEnabled));
  225. }
  226. }
  227. public string ConsoleText
  228. {
  229. get
  230. {
  231. if (_consoleText.Length < UInt16.MaxValue)
  232. return _consoleText.ToString();
  233. else
  234. return _consoleText.ToString(_consoleText.Length - UInt16.MaxValue, UInt16.MaxValue);
  235. }
  236. set
  237. {
  238. _consoleText.Clear();
  239. _consoleText.Append(value);
  240. this.RaisePropertyChanged(nameof(ConsoleText));
  241. ConsoleTextCaretIndex = int.MaxValue;
  242. }
  243. }
  244. public string StartButtonOutput
  245. {
  246. get => _startButtonOutput;
  247. set => this.RaiseAndSetIfChanged(ref _startButtonOutput, value);
  248. }
  249. public string ArgumentsBox
  250. {
  251. get => _argumentsBox;
  252. set => this.RaiseAndSetIfChanged(ref _argumentsBox, value);
  253. }
  254. public string SettingsButton
  255. {
  256. get => _settingsButton;
  257. set => this.RaiseAndSetIfChanged(ref _settingsButton, value);
  258. }
  259. public bool IsNoGameRunning
  260. {
  261. get => _isNoGameRunning;
  262. set
  263. {
  264. this.RaiseAndSetIfChanged(ref _isNoGameRunning, value);
  265. this.RaisePropertyChanged(nameof(IsStartButtonEnabled));
  266. }
  267. }
  268. public bool IsStartButtonEnabled
  269. {
  270. get => IsNoGameRunning && !string.IsNullOrEmpty(Username);
  271. }
  272. public bool IsUpdateAvailable
  273. {
  274. get => _isUpdateAvailable;
  275. set => this.RaiseAndSetIfChanged(ref _isUpdateAvailable, value);
  276. }
  277. public int ConsoleTextCaretIndex
  278. {
  279. get => int.MaxValue;
  280. set => this.RaisePropertyChanged(nameof(ConsoleTextCaretIndex));
  281. }
  282. public void OnClickCommand()
  283. {
  284. var versionsDownloader = new VersionsDownloader
  285. {
  286. DataContext = new VersionsDownloaderViewModel()
  287. };
  288. if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  289. {
  290. versionsDownloader.Closed += updateAvailable;
  291. versionsDownloader.ShowDialog(desktop.MainWindow);
  292. }
  293. }
  294. public async void StartMinecraft()
  295. {
  296. Task.Run(async() =>
  297. {
  298. try
  299. {
  300. _logger.Debug("Starting minecraft.");
  301. if (DownloadedVerion is null)
  302. {
  303. IsNoGameRunning = true;
  304. return;
  305. }
  306. int version = 0;
  307. using (StreamReader reader = new StreamReader(DownloadedVerion.path))
  308. {
  309. string json = reader.ReadToEnd();
  310. var versionJson = JsonSerializer.Deserialize<Entity.Version.Version>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
  311. if (Settings.checkGameAssets)
  312. {
  313. TaskStatus result;
  314. if (versionJson.InheritsFrom is null)
  315. result = Downloader.StartDownload(value => StartButtonOutput = value, value => IsNoGameRunning = value, value => { }, value => { }, versionJson).Result;
  316. else
  317. {
  318. using (StreamReader inheritsFromReader = new StreamReader(Settings.MinecraftForlderPath + "versions/" + versionJson.InheritsFrom + "/" + versionJson.InheritsFrom + ".json"))
  319. {
  320. string jsonInheritsFrom = inheritsFromReader.ReadToEnd();
  321. var inheritsFromJson = JsonSerializer.Deserialize<Entity.Version.Version>(jsonInheritsFrom, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
  322. result = Downloader.StartDownload(value => StartButtonOutput = value, value => IsNoGameRunning = value, value => { }, value => { }, inheritsFromJson).Result;
  323. }
  324. }
  325. if (result != TaskStatus.RanToCompletion)
  326. {
  327. IsNoGameRunning = true;
  328. return;
  329. }
  330. }
  331. string arguments = StartCommandBuilder.Build(versionJson, Username);
  332. if (!Settings.useCustomJava)
  333. {
  334. if (versionJson.JavaVersion != null)
  335. {
  336. _logger.Debug("Java version required: {0}", versionJson.JavaVersion.MajorVersion);
  337. version = versionJson.JavaVersion.MajorVersion;
  338. }
  339. else
  340. {
  341. if (versionJson.InheritsFrom != null)
  342. {
  343. using (StreamReader inheritsFromReader = new StreamReader(Settings.MinecraftForlderPath + "versions/" + versionJson.InheritsFrom + "/" + versionJson.InheritsFrom + ".json"))
  344. {
  345. string jsonInheritsFrom = inheritsFromReader.ReadToEnd();
  346. var inheritsFromJson = JsonSerializer.Deserialize<Entity.Version.Version>(jsonInheritsFrom, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
  347. if (inheritsFromJson.JavaVersion != null)
  348. {
  349. _logger.Debug("Java version required: {0}", inheritsFromJson.JavaVersion.MajorVersion);
  350. version = inheritsFromJson.JavaVersion.MajorVersion;
  351. }
  352. }
  353. }
  354. }
  355. }
  356. ConsoleText += arguments;
  357. ArgumentsBox = arguments;
  358. }
  359. if (DownloadedVerion is null)
  360. return;
  361. string javaPath = Settings.JavaPath;
  362. if (!Settings.useCustomJava)
  363. {
  364. javaPath = Settings.MinecraftForlderPath + "javaruntime/" + version + "/bin/java";
  365. if (OperatingSystem.IsWindows())
  366. javaPath += ".exe";
  367. }
  368. _logger.Debug("Java version path: {0}", javaPath);
  369. _logger.Debug("Minecraft arguments: {0}", ArgumentsBox);
  370. ProcessStartInfo proc;
  371. proc = new ProcessStartInfo
  372. {
  373. UseShellExecute = false,
  374. RedirectStandardOutput = true,
  375. RedirectStandardError = true,
  376. WindowStyle = ProcessWindowStyle.Hidden,
  377. CreateNoWindow = true,
  378. FileName = System.IO.Path.Combine(Settings.MinecraftForlderPath, javaPath),
  379. StandardErrorEncoding = Encoding.UTF8,
  380. WorkingDirectory = Settings.MinecraftForlderPath,
  381. Arguments = ArgumentsBox
  382. };
  383. Process minecraft = new Process();
  384. minecraft.StartInfo = proc;
  385. Task.Run(() =>
  386. {
  387. try
  388. {
  389. _logger.Debug("Starting java process.");
  390. minecraft.OutputDataReceived += OutputHandler;
  391. minecraft.ErrorDataReceived += OutputHandler;
  392. //* Start process and handlers
  393. //minecraft.WaitForExit();
  394. minecraft.EnableRaisingEvents = true;
  395. IsNoGameRunning = false;
  396. minecraft.Start();
  397. minecraft.Exited += ProcessExited;
  398. minecraft.BeginOutputReadLine();
  399. minecraft.BeginErrorReadLine();
  400. if (!Settings.gameLogToLauncher)
  401. {
  402. minecraft.OutputDataReceived -= OutputHandler;
  403. minecraft.CancelOutputRead();
  404. }
  405. return Task.CompletedTask;
  406. }
  407. catch (Exception ex)
  408. {
  409. OpenErrorWindow(ex);
  410. return Task.CompletedTask;
  411. }
  412. });
  413. _logger.Debug("Updating Settings");
  414. Settings.Username = _username;
  415. Settings.lastChosenVersion = DownloadedVerion.version;
  416. Settings.SaveSettings();
  417. }
  418. catch (Exception ex)
  419. {
  420. OpenErrorWindow(ex);
  421. }
  422. });
  423. }
  424. void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
  425. {
  426. //Todo create multiple TextBlocks if char limit
  427. if (!_consoleOutputTimer.Enabled)
  428. {
  429. _consoleOutputTimer.Stop();
  430. _consoleOutputTimer.Start();
  431. }
  432. _consoleText.Append(outLine.Data + "\n");
  433. }
  434. void LogHandler(object sendingProcess, EventArgs args)
  435. {
  436. if (!_consoleOutputTimer.Enabled)
  437. {
  438. _consoleOutputTimer.Stop();
  439. _consoleOutputTimer.Start();
  440. }
  441. _consoleText.Append(((MyEventArgs)args).Data);
  442. }
  443. void UpdateConsoleOutput(Object source, ElapsedEventArgs e)
  444. {
  445. this.RaisePropertyChanged(nameof(ConsoleText));
  446. ScrollToEnd("ConsoleScroll");
  447. }
  448. void ScrollToEnd(string ScrollName)
  449. {
  450. if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  451. {
  452. Dispatcher.UIThread.InvokeAsync(() =>
  453. {
  454. var scroll = desktop.MainWindow.GetControl<ScrollViewer>("ConsoleScroll");
  455. scroll.ScrollToEnd();
  456. });
  457. }
  458. }
  459. void ProcessExited(object sendingProcess, EventArgs e)
  460. {
  461. ((Process)sendingProcess).Exited -= ProcessExited;
  462. _logger.Debug("Exit code: {0}", ((Process)sendingProcess).ExitCode);
  463. StartButtonOutput = "";
  464. IsNoGameRunning = true;
  465. if (((Process)sendingProcess).ExitCode != 0)
  466. OpenErrorWindow(new Exception("Minecraft process exited with an error.\nCheck log in game folder or in launcher console."));
  467. ((Process)sendingProcess).Dispose();
  468. }
  469. public void updateAvailable()
  470. {
  471. if (DownloadedVersions is null)
  472. DownloadedVersions = new();
  473. DownloadedVersions.Clear();
  474. DirectoryInfo versions = new( Settings.MinecraftForlderPath + "versions");
  475. try
  476. {
  477. var dirs = versions.GetDirectories("*", SearchOption.TopDirectoryOnly);
  478. LauncherProfiles profiles = new LauncherProfiles();
  479. profiles.SelectedProfile = "NotImplemented";
  480. foreach (var dir in dirs)
  481. {
  482. _logger.Debug("Checking folder {0}", dir.Name);
  483. string checkedPath;
  484. if (File.Exists(checkedPath = dir.FullName + "/" + dir.Name + ".json"))
  485. {
  486. _logger.Debug("Found version {0}",dir.Name);
  487. DownloadedVersions.Add(new DownloadedVersion() { path = checkedPath, version = dir.Name });
  488. profiles.Profiles.Add($"Version {dir.Name}", new Profile() { Name = dir.Name, LastVersionId = dir.Name, LauncherVisibilityOnGameClose = "keep the launcher open" });
  489. }
  490. }
  491. if (Settings.lastChosenVersion != null)
  492. {
  493. DownloadedIndex = 0;
  494. for (int i = 0; i < DownloadedVersions.Count; i++)
  495. {
  496. if (DownloadedVersions[i].version == Settings.lastChosenVersion)
  497. {
  498. DownloadedIndex = i;
  499. break;
  500. }
  501. }
  502. }
  503. if (!File.Exists(Settings.MinecraftForlderPath + "launcher_profiles.json"))
  504. {
  505. var file = File.Create(Settings.MinecraftForlderPath + "launcher_profiles.json");
  506. file.Close();
  507. file.Dispose();
  508. }
  509. var lpString = JsonSerializer.Serialize(profiles, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
  510. File.WriteAllText(Settings.MinecraftForlderPath + "launcher_profiles.json", lpString);
  511. }
  512. catch (DirectoryNotFoundException ex)
  513. {
  514. Directory.CreateDirectory(Settings.MinecraftForlderPath + "versions");
  515. return;
  516. }
  517. }
  518. public void updateAvailable(object sendingObject, EventArgs e)
  519. {
  520. try
  521. {
  522. updateAvailable();
  523. }
  524. catch (Exception ex)
  525. {
  526. OpenErrorWindow(ex);
  527. }
  528. switch (sendingObject)
  529. {
  530. case VersionsDownloader:
  531. ((VersionsDownloader)sendingObject).Closed -= updateAvailable;
  532. break;
  533. case SettingsWindow:
  534. ((SettingsWindow)sendingObject).Closed -= updateAvailable;
  535. break;
  536. }
  537. }
  538. public void OpenSettings()
  539. {
  540. var settingsWindow = new SettingsWindow { DataContext = new SettingsWindowViewModel() };
  541. if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  542. {
  543. settingsWindow.Closed += updateAvailable;
  544. settingsWindow.ShowDialog(desktop.MainWindow);
  545. }
  546. }
  547. public void DownloadUpdate()
  548. {
  549. _logger.Debug("Started updater.exe");
  550. Process updater = new Process();
  551. updater.StartInfo.FileName = "Updater";
  552. if (OperatingSystem.IsWindows())
  553. updater.StartInfo.FileName += ".exe";
  554. if (!File.Exists(updater.StartInfo.FileName))
  555. UpdateUpdater();
  556. if (!File.Exists(updater.StartInfo.FileName))
  557. {
  558. updater.Start();
  559. if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  560. {
  561. desktop.MainWindow.Close();
  562. }
  563. }
  564. else
  565. OpenErrorWindow(new FileNotFoundException($"File {updater.StartInfo.FileName} not found!"));
  566. }
  567. private void UpdateUpdater()
  568. {
  569. try
  570. {
  571. string fileName = "Updater";
  572. if (OperatingSystem.IsWindows())
  573. fileName += ".exe";
  574. if (File.Exists(fileName))
  575. {
  576. FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
  577. LatestLauncherVersion latestLauncherVersion = Downloader.DownloadAndDeserializeJsonData<LatestLauncherVersion>("https://files.veloe.link/launcher/update/versions.json");
  578. if (OperatingSystem.IsLinux())
  579. {
  580. _logger.Information("Manual updates only.");
  581. return;
  582. }
  583. if (latestLauncherVersion != null)
  584. {
  585. _logger.Information("Latest updater version on server: {0}", latestLauncherVersion.Updater);
  586. _logger.Information("Updater version: {0}", fileVersionInfo.FileVersion);
  587. if (!(new Version(latestLauncherVersion.Updater) > new Version(fileVersionInfo.FileVersion)))
  588. {
  589. _logger.Information($"No update for \"{fileName}\" required.");
  590. return;
  591. }
  592. }
  593. }
  594. WebClient webClient = new WebClient();
  595. try
  596. {
  597. _logger.Information("Downloading updater.zip");
  598. string url = String.Empty;
  599. if (OperatingSystem.IsWindows())
  600. url = "https://files.veloe.link/launcher/update/updater.zip";
  601. if (OperatingSystem.IsLinux())
  602. url = "https://files.veloe.link/launcher/update/updater_linux-x64.zip";
  603. if (url == String.Empty)
  604. return;
  605. webClient.DownloadFile(new System.Uri(url), "updater.zip");
  606. }
  607. catch (Exception ex)
  608. {
  609. _logger.Error("Error occured on getting updater.zip from the server.");
  610. throw;
  611. }
  612. webClient.Dispose();
  613. _logger.Information("Unpacking updater.zip");
  614. ZipFile.ExtractToDirectory($"updater.zip", Directory.GetCurrentDirectory(), true);
  615. _logger.Information("Deleting updater.zip");
  616. File.Delete($"updater.zip");
  617. }
  618. catch (Exception ex)
  619. {
  620. OpenErrorWindow(ex);
  621. }
  622. }
  623. private Panel CreateServerPanel(string name)
  624. {
  625. ServerPanelModel serverPanelModel = new() { Name = name , Status = "Wait for update...", Tip = "No players.", Players = string.Empty};
  626. Panel panel = new Panel()
  627. {
  628. VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top,
  629. HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch,
  630. Height = 75,
  631. Width = 150,
  632. Margin = Avalonia.Thickness.Parse("0 0 10 10")
  633. };
  634. panel.Children.Add(new Border()
  635. {
  636. Background = Brushes.Black,
  637. Opacity = 0.2,
  638. CornerRadius = Avalonia.CornerRadius.Parse("15")
  639. });
  640. var tip = new ToolTip()
  641. {
  642. Content = new TextBlock() {TextWrapping = TextWrapping.Wrap, [!TextBlock.TextProperty] = new Binding { Source = serverPanelModel,Path = nameof(serverPanelModel.Tip) } }
  643. };
  644. ToolTip.SetTip(panel, tip);
  645. StackPanel textPanel = new() { VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center };
  646. textPanel.Children.Add(new TextBlock() { VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, [!TextBlock.TextProperty] = new Binding { Source = serverPanelModel, Path = nameof(serverPanelModel.Status) } });
  647. textPanel.Children.Add(new TextBlock() { FontSize = 20, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, [!TextBlock.TextProperty] = new Binding { Source = serverPanelModel, Path = nameof(serverPanelModel.Players) } });
  648. panel.Children.Add(textPanel);
  649. System.Timers.Timer timeoutTimer = new System.Timers.Timer(30000);
  650. timeoutTimer.Elapsed += (Object source, ElapsedEventArgs e) =>
  651. {
  652. serverPanelModel.Status = $"{serverPanelModel.Name}: Offline";
  653. serverPanelModel.Players = string.Empty;
  654. };
  655. timeoutTimer.Start();
  656. _connection.On<string>($"Update{serverPanelModel.Name}", (message) =>
  657. {
  658. McStatus status = JsonSerializer.Deserialize<McStatus>(message, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
  659. serverPanelModel.Status = $"{serverPanelModel.Name}: Online";
  660. serverPanelModel.Players = $"{status.NumPlayers}/{status.MaxPlayers}";
  661. serverPanelModel.Tip = String.Empty;
  662. if (UInt16.Parse(status.NumPlayers) > 0)
  663. foreach (var player in status.Players)
  664. {
  665. serverPanelModel.Tip += player;
  666. serverPanelModel.Tip += "\n";
  667. }
  668. else
  669. serverPanelModel.Tip = "No players.";
  670. timeoutTimer.Stop();
  671. timeoutTimer.Start();
  672. });
  673. return panel;
  674. }
  675. private void OpenErrorWindow(Exception ex)
  676. {
  677. var message = ex.Message;
  678. var stackTrace = ex.StackTrace;
  679. Settings.logger.Error(ex.Message);
  680. Settings.logger.Error(ex.StackTrace);
  681. Exception innerException = ex.InnerException;
  682. while (innerException is not null)
  683. {
  684. message += "\n" + innerException.Message;
  685. stackTrace += "\n" + innerException.StackTrace;
  686. Settings.logger.Error(innerException.Message);
  687. Settings.logger.Error(innerException.StackTrace);
  688. innerException = innerException.InnerException;
  689. }
  690. Dispatcher.UIThread.InvokeAsync(() =>
  691. {
  692. if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  693. {
  694. var ErrorMessageViewModel = new ErrorWindowViewModel(message, stackTrace);
  695. var ErrorMessage = new ErrorWindow()
  696. {
  697. DataContext = ErrorMessageViewModel
  698. };
  699. ErrorMessage.ShowDialog(desktop.MainWindow);
  700. }
  701. });
  702. }
  703. }