Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions Controls/AppTile.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ await progress.CreateAsync(async () =>
{
await WinDurangoPatcher.UnpatchPackage(_package, progress);
});
if (!progress.failed)
if (!progress.Failed)
{
NoticeDialog good = new NoticeDialog($"WinDurango has been uninstalled from package {_Name}", "Uninstalled");
await good.ShowAsync();
Expand All @@ -126,14 +126,15 @@ await progress.CreateAsync(async () =>
await WinDurangoPatcher.PatchPackage(_package, false, progress);
});

if (!progress.failed)
if (!progress.Failed)
{
NoticeDialog good = new NoticeDialog($"WinDurango has been installed in package {_Name}", "Installed");
await good.ShowAsync();
}
App.MainWindow.ReloadAppList();
}

// TODO: This can probably be improved
public AppTile(string familyName)
{
_familyName = familyName;
Expand All @@ -152,7 +153,45 @@ public AppTile(string familyName)
_Version = $"{_package.Id.Version.Major.ToString() ?? "U"}.{_package.Id.Version.Minor.ToString() ?? "U"}.{_package.Id.Version.Build.ToString() ?? "U"}.{_package.Id.Version.Revision.ToString() ?? "U"}";
_Logo = _package.Logo;

string ss = Packages.GetSplashScreenPath(_package);
ManifestInfo mfInfo = _package.GetProperties();

string ss = String.Empty;

// TODO: This seems slow.
if (!string.IsNullOrEmpty(mfInfo.SplashScreen))
{
ss = Path.Combine(_package.InstalledPath, mfInfo.SplashScreen);
// if it doesn't exist it probably has some scale thing
if (!File.Exists(ss))
{
for (int i = 100; i < 400; i += 100)
{
string path = Path.Combine(_package.InstalledPath, Path.GetDirectoryName(mfInfo.SplashScreen), Path.GetFileNameWithoutExtension(mfInfo.SplashScreen) + $".scale-{i}.png");
if (File.Exists(path))
{
ss = path;
break;
}
}
}
} else if (!string.IsNullOrEmpty(mfInfo.WideLogo))
{
ss = Path.Combine(_package.InstalledPath, mfInfo.WideLogo);
// if it doesn't exist it probably has some scale thing
if (!File.Exists(ss))
{
for (int i = 100; i < 400; i += 100)
{
string path = Path.Combine(_package.InstalledPath, Path.GetDirectoryName(mfInfo.WideLogo), Path.GetFileNameWithoutExtension(mfInfo.WideLogo) + $".scale-{i}.png");
if (File.Exists(path))
{
ss = path;
break;
}
}
}
}

IReadOnlyList<AppListEntry> appListEntries = null;
try
{
Expand All @@ -167,7 +206,7 @@ public AppTile(string familyName)
if (firstAppListEntry == null)
Logger.WriteWarning($"Could not get the applist entry of \"{_Name}\"");

if (ss == null || !File.Exists(ss))
if (String.IsNullOrEmpty(ss) || !File.Exists(ss))
{
try
{
Expand Down
1 change: 1 addition & 0 deletions Dialogs/AppListDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ private void AppListView_Loaded(object sender, RoutedEventArgs e)
foreach (Package pkg in Packages)
{
// if we already have the package "installed" we will skip it (not show it in the AppListView)
// maybe this behavior should change?
if (App.InstalledPackages.GetPackages().Find(p => p.FamilyName == pkg.Id.FamilyName) != null) continue;

ListViewItem item = new() { MinWidth = 200 };
Expand Down
4 changes: 2 additions & 2 deletions Dialogs/ProgressController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@
});
}

public async Task Fail(string title, string reason)

Check warning on line 43 in Dialogs/ProgressController.cs

View workflow job for this annotation

GitHub Actions / build (Debug)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 43 in Dialogs/ProgressController.cs

View workflow job for this annotation

GitHub Actions / build (Release)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
hasFailed = true;
Logger.WriteError($"[{this._dialog.PTitle}] {title}: {reason}");
_dialog.DispatcherQueue.TryEnqueue(async () =>

Check warning on line 47 in Dialogs/ProgressController.cs

View workflow job for this annotation

GitHub Actions / build (Debug)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 47 in Dialogs/ProgressController.cs

View workflow job for this annotation

GitHub Actions / build (Release)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
_dialog.Hide();
_dialog.Closed += async (sender, e) =>
Expand All @@ -55,11 +55,11 @@
});
}

public async Task Fail(string title, Exception ex)

Check warning on line 58 in Dialogs/ProgressController.cs

View workflow job for this annotation

GitHub Actions / build (Debug)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 58 in Dialogs/ProgressController.cs

View workflow job for this annotation

GitHub Actions / build (Release)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
hasFailed = true;
Logger.WriteException(ex);
_dialog.DispatcherQueue.TryEnqueue(async () =>

Check warning on line 62 in Dialogs/ProgressController.cs

View workflow job for this annotation

GitHub Actions / build (Debug)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 62 in Dialogs/ProgressController.cs

View workflow job for this annotation

GitHub Actions / build (Release)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
_dialog.Hide();
_dialog.Closed += async (sender, e) =>
Expand All @@ -70,7 +70,7 @@
});
}

public bool failed
public bool Failed
{
get => hasFailed;
}
Expand All @@ -87,7 +87,7 @@
{
_dialog.DispatcherQueue.TryEnqueue(() =>
{
_dialog.ShowAsync();
_ = _dialog.ShowAsync();
});
}

Expand Down
1 change: 1 addition & 0 deletions Pages/AboutPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.IO;
using WinDurango.UI.Controls;
using WinDurango.UI.Utils;


namespace WinDurango.UI.Pages
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ For building you'll need Visual Studio 2022 with the following:
- [X] Scan for already installed EraOS/XUWP stuff
- [X] Allow for any existing installed package to be added to the applist
- [ ] Built in updater
- [ ] Fitting place for extra xbox-specific info
- [ ] Resize content to fit to screen
- [X] Allow for search

Expand All @@ -29,6 +30,7 @@ For building you'll need Visual Studio 2022 with the following:
- [X] Fix icon in the titlebar
- [ ] Repo contributors on the about screen
- [ ] Get Fluent Thin working
- [ ] Use InfoBar for many progress related things (such as registering package, etc)
- [ ] Add versioning to the InstalledPackages json (as in versioning the JSON file itself)
- [ ] Make the Package stuff not rely on UI so much, handle that somewhere else.
- [X] Fix crash when installation errors
Expand Down
147 changes: 85 additions & 62 deletions Utils/Packages.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Microsoft.Windows.ApplicationModel.DynamicDependency;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -7,6 +8,7 @@
using System.Xml.Linq;
using Windows.ApplicationModel;
using Windows.Management.Deployment;
using Windows.Networking;
using WinDurango.UI.Dialogs;
using WinUI3Localizer;
using static WinDurango.UI.Localization.Locale;
Expand All @@ -17,14 +19,35 @@ namespace WinDurango.UI.Utils
// this class sucks so much that we literally had to stop bc we didn't know how to rewrite a function and make it still work with the UI stuff
// update: this was fixed same commit.
#nullable enable

public class ManifestInfo
{
public string? DisplayName { get; set; }
public string? PublisherDisplayName { get; set; }
public string? StoreLogo { get; set; }
public string? Logo { get; set; }
public string? WideLogo { get; set; }
public string? SmallLogo { get; set; }
public string? Description { get; set; }
public string? SplashScreen { get; set; }
public (string? Name, string? Version)? OSPackageDependency { get; set; }
}

public class XbManifestInfo
{
public string[]? Ratings { get; set; }
public string? TitleId { get; set; }
public string? OsName { get; set; }
public string? ApplicationEnvironment { get; set; }
public bool? IsBackCompat { get; set; }

public bool IsEra(ManifestInfo mfInfo)
{
return (this.OsName?.Equals("era", StringComparison.OrdinalIgnoreCase) ?? false) ||
(mfInfo.OSPackageDependency?.Name == "Microsoft.GameOs");
}
}
public abstract class Packages
public static class Packages
{
// TODO: Make these methods not use the GUI, instead just throw an exception and catch it in the area where the method is actually invoked.
/// <summary>
Expand Down Expand Up @@ -56,85 +79,85 @@ public static ManifestInfo GetPropertiesFromManifest(string manifestPath)
}

XDocument doc = XDocument.Parse(manifest);
XElement? package = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "Package");

XElement? properties = package?.Descendants().FirstOrDefault(e => e.Name.LocalName == "Properties");
XElement? defaultTile = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "DefaultTile");
XElement? visualElements = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "VisualElements");

manifestInfo.WideLogo = defaultTile?.Attribute("WideLogo")?.Value;
manifestInfo.StoreLogo = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "Logo")?.Value;
manifestInfo.Logo = visualElements?.Attribute("Logo")?.Value;
manifestInfo.SmallLogo = visualElements?.Attribute("SmallLogo")?.Value;
manifestInfo.DisplayName = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "DisplayName")?.Value;
manifestInfo.PublisherDisplayName = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "PublisherDisplayName")?.Value;
manifestInfo.Description = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "Description")?.Value;
manifestInfo.SplashScreen = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "SplashScreen")?.Attribute("Image")?.Value;
manifestInfo.OSPackageDependency = (doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "OSPackageDependency")?.Attribute("Name")?.Value, doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "OSPackageDependency")?.Attribute("Version")?.Value);

manifestInfo.Logo = properties?.Descendants().FirstOrDefault(e => e.Name.LocalName == "Logo")?.Value;
manifestInfo.DisplayName = properties?.Descendants().FirstOrDefault(e => e.Name.LocalName == "DisplayName")?.Value;
manifestInfo.PublisherDisplayName = properties?.Descendants().FirstOrDefault(e => e.Name.LocalName == "PublisherDisplayName")?.Value;
manifestInfo.Description = properties?.Descendants().FirstOrDefault(e => e.Name.LocalName == "Description")?.Value;
return manifestInfo;
}

/// <summary>
/// Installs an Xbox Package with the folder given to it
/// </summary>
/// <remarks>
/// This is simply meant for being able to pass a folder containing the Mount directory.
/// </remarks>
public static async Task InstallXPackageAsync(string dir, ProgressController controller, bool addInstalledPackage = true)
public static ManifestInfo GetProperties(this Package pkg)
{
string mountDir = Path.Combine(dir, "Mount");
string installPath = pkg.InstalledPath;
string manifestPath = Path.Combine(installPath, "AppxManifest.xml");

if (!Directory.Exists(mountDir))
{
await new NoticeDialog(GetLocalizedText($"/Errors/NotFound", mountDir), "Error").ShowAsync();
return;
}
return GetPropertiesFromManifest(manifestPath);
}

await InstallPackageAsync(new Uri(mountDir + "\\AppxManifest.xml", UriKind.Absolute), controller, addInstalledPackage);
public static XbManifestInfo GetXbProperties(this Package pkg)
{
string installPath = pkg.InstalledPath;
string manifestPath = Path.Combine(installPath, "AppxManifest.xml");

return GetXbProperties(manifestPath);
}

/// <summary>
/// Gets a Package's splash screen, if none is found... it returns null.
/// Gets Xbox specific properties from the provided AppxManifest
/// </summary>
/// <remarks>
/// It checks the package's AppxManifest for Package.Applications.Application.VisualElements.SplashScreen.Image
/// </remarks>
public static string GetSplashScreenPath(Package pkg)
public static XbManifestInfo GetXbProperties(string manifestPath)
{
try
{
string installPath = pkg.InstalledPath;
string manifestPath = Path.Combine(installPath, "AppxManifest.xml");

if (!File.Exists(manifestPath))
return null;
XbManifestInfo info = new();

string manifest;
using (var stream = File.OpenRead(manifestPath))
{
var reader = new StreamReader(stream);
manifest = reader.ReadToEnd();
}
if (!File.Exists(manifestPath))
return info;

XDocument doc = XDocument.Parse(manifest);
XElement package = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "Package");
if (package == null) return null;
string manifest;
using (var stream = File.OpenRead(manifestPath))
{
var reader = new StreamReader(stream);
manifest = reader.ReadToEnd();
}

XElement applications = package.Descendants().FirstOrDefault(e => e.Name.LocalName == "Applications");
if (applications == null) return null;
XDocument doc = XDocument.Parse(manifest);

XElement application = applications.Descendants().FirstOrDefault(e => e.Name.LocalName == "Application");
if (application == null) return null;
info.Ratings = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "Ratings")?.Descendants().Select(e => e.Value).ToArray();
info.TitleId = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "XboxLive")?.Attribute("TitleId")?.Value;
info.OsName = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "OSName")?.Value;
info.ApplicationEnvironment = doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "ApplicationEnvironment")?.Value;
bool.TryParse(doc?.Descendants().FirstOrDefault(e => e.Name.LocalName == "XboxFission")?.Attribute("IsFissionApp")?.Value, out bool isBackCompat);

XElement visualElements = application.Descendants().FirstOrDefault(e => e.Name.LocalName == "VisualElements");
if (visualElements == null) return null;
info.IsBackCompat = isBackCompat;
return info;
}

XElement splashScreen = visualElements.Descendants().FirstOrDefault(e => e.Name.LocalName == "SplashScreen");
if (splashScreen == null) return null;

string imagePath = splashScreen.Attribute("Image")?.Value;
if (imagePath == null) return null;
/// <summary>
/// Installs an Xbox Package with the folder given to it
/// </summary>
/// <remarks>
/// This is simply meant for being able to pass a folder containing the Mount directory.
/// </remarks>
public static async Task InstallXPackageAsync(string dir, ProgressController controller, bool addInstalledPackage = true)
{
string mountDir = Path.Combine(dir, "Mount");

string splashScreenPath = Path.Combine(installPath, imagePath);
return splashScreenPath;
}
catch
if (!Directory.Exists(mountDir))
{
return null;
await new NoticeDialog(GetLocalizedText($"/Errors/NotFound", mountDir), "Error").ShowAsync();
return;
}

await InstallPackageAsync(new Uri(mountDir + "\\AppxManifest.xml", UriKind.Absolute), controller, addInstalledPackage);
}

/// <summary>
Expand Down Expand Up @@ -245,7 +268,7 @@ public static async Task RemovePackage(Package package, ProgressController contr
/// <summary>
/// Gets a Package by it's Family Name
/// </summary>
public static Package GetPackageByFamilyName(string familyName)
public static Package? GetPackageByFamilyName(string familyName)
{
var packageManager = new PackageManager();
var packages = packageManager.FindPackagesForUser(null, familyName);
Expand All @@ -256,7 +279,7 @@ public static Package GetPackageByFamilyName(string familyName)
/// <summary>
/// Gets the most recent installed package
/// </summary>
public static Package GetMostRecentInstalledPackage()
public static Package? GetMostRecentInstalledPackage()
{
var sid = WindowsIdentity.GetCurrent().User?.Value;
var pm = new PackageManager();
Expand All @@ -270,4 +293,4 @@ public static Package GetMostRecentInstalledPackage()
return newestPackage;
}
}
}
}
Loading
Loading