MainWindowViewModel.cs 33 KB

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