MainWindowViewModel.cs 32 KB

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