diff --git a/Nebula.Launcher/App.axaml.cs b/Nebula.Launcher/App.axaml.cs index 16577eb..7450309 100644 --- a/Nebula.Launcher/App.axaml.cs +++ b/Nebula.Launcher/App.axaml.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; using Microsoft.Extensions.DependencyInjection; +using Nebula.Launcher.ViewModels.ContentView; using Nebula.Launcher.Views; using Nebula.Shared; @@ -40,6 +41,7 @@ public class App : Application services.AddAvaloniaServices(); services.AddServices(); services.AddViews(); + services.AddTransient(); var serviceProvider = services.BuildServiceProvider(); diff --git a/Nebula.Launcher/LauncherConVar.cs b/Nebula.Launcher/LauncherConVar.cs index 079d935..7c01241 100644 --- a/Nebula.Launcher/LauncherConVar.cs +++ b/Nebula.Launcher/LauncherConVar.cs @@ -19,4 +19,6 @@ public static class LauncherConVar ]); public static readonly ConVar CurrentLang = ConVarBuilder.Build("launcher.language", "en-US"); + public static readonly ConVar ILSpyUrl = ConVarBuilder.Build("decompiler.url", + "https://github.com/icsharpcode/ILSpy/releases/download/v9.0/ILSpy_binaries_9.0.0.7889-x64.zip"); } \ No newline at end of file diff --git a/Nebula.Launcher/Services/DecompilerService.cs b/Nebula.Launcher/Services/DecompilerService.cs new file mode 100644 index 0000000..f818d0d --- /dev/null +++ b/Nebula.Launcher/Services/DecompilerService.cs @@ -0,0 +1,54 @@ + +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Net.Http; +using System.Threading.Tasks; +using Nebula.Launcher.ViewModels.Popup; +using Nebula.Shared; +using Nebula.Shared.FileApis; +using Nebula.Shared.FileApis.Interfaces; +using Nebula.Shared.Services; + +namespace Nebula.Launcher.Services; + +[ConstructGenerator, ServiceRegister] +public sealed partial class DecompilerService +{ + [GenerateProperty] private ConfigurationService ConfigurationService { get; } + [GenerateProperty] private PopupMessageService PopupMessageService {get;} + [GenerateProperty] private ViewHelperService ViewHelperService {get;} + + private HttpClient _httpClient = new HttpClient(); + + private static string fullPath = Path.Join(FileService.RootPath,"ILSpy"); + private static string executePath = Path.Join(fullPath, "ILSpy.exe"); + + public async void OpenDecompiler(string path){ + await EnsureILSpy(); + var startInfo = new ProcessStartInfo(){ + FileName = executePath, + Arguments = path + }; + Process.Start(startInfo); + } + + private void Initialise(){} + private void InitialiseInDesignMode(){} + + private async Task EnsureILSpy(){ + if(!Directory.Exists(fullPath)) + await Download(); + } + + private async Task Download(){ + using var loading = ViewHelperService.GetViewModel(); + loading.LoadingName = "Download ILSpy"; + loading.SetJobsCount(1); + PopupMessageService.Popup(loading); + using var response = await _httpClient.GetAsync(ConfigurationService.GetConfigValue(LauncherConVar.ILSpyUrl)); + using var zipArchive = new ZipArchive(await response.Content.ReadAsStreamAsync()); + Directory.CreateDirectory(fullPath); + zipArchive.ExtractToDirectory(fullPath); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/ContentView/DecompilerContentView.cs b/Nebula.Launcher/ViewModels/ContentView/DecompilerContentView.cs new file mode 100644 index 0000000..3180af6 --- /dev/null +++ b/Nebula.Launcher/ViewModels/ContentView/DecompilerContentView.cs @@ -0,0 +1,32 @@ +using System.IO; +using Nebula.Launcher.Services; +using Nebula.Launcher.ViewModels.Pages; + +namespace Nebula.Launcher.ViewModels.ContentView; + +[ConstructGenerator] +public sealed partial class DecompilerContentView: ContentViewBase +{ + [GenerateProperty] private DecompilerService decompilerService {get;} + + public override void InitialiseWithData(ContentPath path, Stream stream) + { + base.InitialiseWithData(path, stream); + var myTempFile = Path.Combine(Path.GetTempPath(), "tempie.dll"); + + var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None); + stream.CopyTo(sw); + sw.Dispose(); + stream.Dispose(); + + decompilerService.OpenDecompiler(myTempFile); + } + + protected override void Initialise() + { + } + + protected override void InitialiseInDesignMode() + { + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs index c5eb82e..a66e25a 100644 --- a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs @@ -7,14 +7,12 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading.Tasks; -using Avalonia.Media; -using Avalonia.Media.Imaging; -using Avalonia.Platform; using CommunityToolkit.Mvvm.ComponentModel; using Nebula.Launcher.Services; using Nebula.Launcher.ViewModels.ContentView; using Nebula.Launcher.ViewModels.Popup; using Nebula.Launcher.Views.Pages; +using Nebula.Shared.FileApis; using Nebula.Shared.Models; using Nebula.Shared.Services; using Nebula.Shared.Utils; @@ -44,6 +42,7 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModel [GenerateProperty] private DebugService DebugService { get; } = default!; [GenerateProperty] private PopupMessageService PopupService { get; } = default!; [GenerateProperty] private HubService HubService { get; } = default!; + [GenerateProperty] private IServiceProvider ServiceProvider {get;} [GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } = default!; public ObservableCollection Entries { get; } = new(); @@ -55,45 +54,48 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModel get => _selectedEntry; set { + var oldSearchText = SearchText; SearchText = value?.GetPath().ToString() ?? ""; + ContentView = null; - - if (value is { Item: not null }) - { - if (FileService.ContentFileApi.TryOpen(value.Item.Value.Hash, out var stream)) - { - var ext = Path.GetExtension(value.Item.Value.Path); - if(TryGetContentViewer(ext, out var contentViewBase)){ - contentViewBase.InitialiseWithData(value.GetPath(), stream); - ContentView = contentViewBase; - return; - } - - var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext); - - using (var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None)) - { - stream.CopyTo(sw); - } - - stream.Dispose(); - - var startInfo = new ProcessStartInfo(myTempFile) - { - UseShellExecute = true - }; - - Process.Start(startInfo); - } - - return; - } - Entries.Clear(); _selectedEntry = value; if (value == null) return; + if(value.TryOpen(out var stream, out var item)){ + + var ext = Path.GetExtension(item.Value.Path); + var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext); + + if(TryGetContentViewer(ext, out var contentViewBase)){ + DebugService.Debug($"Opening custom context:{item.Value.Path}"); + contentViewBase.InitialiseWithData(value.GetPath(), stream); + ContentView = contentViewBase; + return; + } + + var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None); + stream.CopyTo(sw); + + sw.Dispose(); + stream.Dispose(); + + var startInfo = new ProcessStartInfo(myTempFile) + { + UseShellExecute = true + }; + + + DebugService.Log("Opening " + myTempFile); + Process.Start(startInfo); + + return; + } + + if(SearchText.Length > oldSearchText.Length) + AppendHistory(oldSearchText); + foreach (var (_, entryCh) in value.Childs) Entries.Add(entryCh); } } @@ -103,17 +105,16 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModel if(!_contentContainers.TryGetValue(type, out var contentViewType) || !contentViewType.IsAssignableTo(typeof(ContentViewBase))) return false; - - contentViewBase = (ContentViewBase)Activator.CreateInstance(contentViewType)!; + contentViewBase = (ContentViewBase)ServiceProvider.GetService(contentViewType)!; return true; } protected override void InitialiseInDesignMode() { - var a = new ContentEntry(this, "A:", "A", ""); - var b = new ContentEntry(this, "B", "B", ""); + var a = new ContentEntry(this, "A:", "A", "", default!); + var b = new ContentEntry(this, "B", "B", "", default!); a.TryAddChild(b); Entries.Add(a); } @@ -126,6 +127,8 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModel HubService.HubServerLoaded += GoHome; if (!HubService.IsUpdating) GoHome(); + + _contentContainers.Add(".dll",typeof(DecompilerContentView)); } private void GoHome() @@ -142,10 +145,10 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModel private void FillRoot(IEnumerable infos) { - foreach (var info in infos) _root.Add(new ContentEntry(this, info.StatusData.Name, info.Address, info.Address)); + foreach (var info in infos) _root.Add(new ContentEntry(this, info.StatusData.Name, info.Address, info.Address, default!)); } - public async void Go(ContentPath path, bool appendHistory = true) + public async void Go(ContentPath path) { if (path.Pathes.Count > 0 && (path.Pathes[0].StartsWith("ss14://") || path.Pathes[0].StartsWith("ss14s://"))) { @@ -181,7 +184,7 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModel public void OnBackEnter() { - Go(new ContentPath(GetHistory()), false); + Go(new ContentPath(GetHistory())); } public void OnGoEnter() @@ -200,12 +203,12 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModel var hashApi = await ContentService.EnsureItems(info.RobustManifestInfo, loading, CancellationService.Token); - var rootEntry = new ContentEntry(this, "", "", serverUrl); + var rootEntry = new ContentEntry(this, "", "", serverUrl, default!); foreach (var item in hashApi.Manifest.Values) { var path = new ContentPath(item.Path); - rootEntry.CreateItem(path, item); + rootEntry.CreateItem(path, item, hashApi); } loading.Dispose(); @@ -236,15 +239,17 @@ public class ContentEntry { private readonly Dictionary _childs = new(); private readonly ContentBrowserViewModel _viewModel; + private HashApi _fileApi; - public RobustManifestItem? Item; + public RobustManifestItem? Item { get; private set; } - internal ContentEntry(ContentBrowserViewModel viewModel, string name, string pathName, string serverName) + internal ContentEntry(ContentBrowserViewModel viewModel, string name, string pathName, string serverName, HashApi fileApi) { Name = name; ServerName = serverName; PathName = pathName; _viewModel = viewModel; + _fileApi = fileApi; } public bool IsDirectory => Item == null; @@ -259,6 +264,22 @@ public class ContentEntry public IReadOnlyDictionary Childs => _childs.ToFrozenDictionary(); + public Stream Open() + { + _fileApi.TryOpen(Item!.Value, out var stream); + return stream!; + } + + public bool TryOpen([NotNullWhen(true)] out Stream? stream,[NotNullWhen(true)] out RobustManifestItem? item){ + stream = null; + item = null; + if(Item is null || !_fileApi.TryOpen(Item.Value, out stream)) + return false; + + item = Item; + return true; + } + public bool TryGetChild(string name, [NotNullWhen(true)] out ContentEntry? child) { return _childs.TryGetValue(name, out child); @@ -295,7 +316,7 @@ public class ContentEntry if (!TryGetChild(fName, out var child)) { - child = new ContentEntry(_viewModel, fName, fName, ServerName); + child = new ContentEntry(_viewModel, fName, fName, ServerName, _fileApi); TryAddChild(child); } @@ -308,13 +329,13 @@ public class ContentEntry return Parent.GetRoot(); } - public ContentEntry CreateItem(ContentPath path, RobustManifestItem item) + public ContentEntry CreateItem(ContentPath path, RobustManifestItem item, HashApi fileApi) { var dir = path.GetDirectory(); var dirEntry = GetOrCreateDirectory(dir); var name = path.GetName(); - var entry = new ContentEntry(_viewModel, name, name, ServerName) + var entry = new ContentEntry(_viewModel, name, name, ServerName, fileApi) { Item = item }; diff --git a/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.Favorite.cs b/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.Favorite.cs index 64b9152..b075a00 100644 --- a/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.Favorite.cs +++ b/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.Favorite.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Threading; -using CommunityToolkit.Mvvm.ComponentModel; -using Nebula.Shared; using Nebula.Shared.Models; using Nebula.Shared.Services; using Nebula.Shared.Utils; @@ -16,11 +11,13 @@ public partial class ServerListViewModel [GenerateProperty] private ConfigurationService ConfigurationService { get; } [GenerateProperty] private RestService RestService { get; } - public ObservableCollection FavoriteServers { get; } = new(); + public ObservableCollection FavoriteServers { get; } = []; private void UpdateFavoriteEntries() { - FavoriteServers.Clear(); + foreach(var fav in FavoriteServers.ToList()){ + FavoriteServers.Remove(fav); + } var servers = ConfigurationService.GetConfigValue(LauncherConVar.Favorites); if (servers is null || servers.Length == 0) diff --git a/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.cs index 97b9c8f..3e26fce 100644 --- a/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.cs @@ -67,7 +67,10 @@ public partial class ServerListViewModel : ViewModelBase, IViewModelPage private void UpdateServerEntries() { - Servers.Clear(); + foreach(var fav in Servers.ToList()){ + Servers.Remove(fav); + } + Task.Run(() => { UnsortedServers.Sort(new ServerComparer()); @@ -105,7 +108,6 @@ public partial class ServerListViewModel : ViewModelBase, IViewModelPage { UnsortedServers.Clear(); ServerViewContainer.Clear(); - Servers.Clear(); UpdateFavoriteEntries(); } } diff --git a/Nebula.Shared/Services/FileService.cs b/Nebula.Shared/Services/FileService.cs index 0c9a843..b3a8822 100644 --- a/Nebula.Shared/Services/FileService.cs +++ b/Nebula.Shared/Services/FileService.cs @@ -52,9 +52,7 @@ public class FileService var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read); - var prefix = ""; - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) prefix = "Space Station 14.app/Contents/Resources/"; - return new ZipFileApi(zipArchive, prefix); + return new ZipFileApi(zipArchive, ""); } catch (Exception) {