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
13 changes: 13 additions & 0 deletions src/UniGetUI.Core.IconStore/IconCacheEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public readonly struct CacheableIcon
public readonly string Version = "";
public readonly long Size = -1;
private readonly int _hashCode = -1;
public readonly bool IsLocalPath = false;
public readonly string LocalPath = "";
public readonly IconValidationMethod ValidationMethod;

/// <summary>
Expand Down Expand Up @@ -76,6 +78,13 @@ public CacheableIcon(Uri uri)
_hashCode = uri.ToString().GetHashCode();
}

public CacheableIcon(string path)
{
IsLocalPath = true;
LocalPath = path;
Url = new Uri(path);
}

public override int GetHashCode()
{
return _hashCode;
Expand All @@ -101,6 +110,10 @@ public static class IconCacheEngine
return null;

var icon = _icon.Value;

if(icon.IsLocalPath)
return icon.LocalPath;

string iconLocation = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName, PackageId);
if (!Directory.Exists(iconLocation)) Directory.CreateDirectory(iconLocation);
string iconVersionFile = Path.Join(iconLocation, $"icon.version");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using System.Globalization;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Management.Deployment;
using Microsoft.Win32;
using UniGetUI.Core.IconEngine;
using UniGetUI.Core.Logging;
using UniGetUI.PackageEngine.Interfaces;
using UniGetUI.PackageEngine.Managers.WingetManager;

namespace UniGetUI.PackageEngine.Managers.WinGet.ClientHelpers;
internal static class WinGetIconsHelper
{
private static readonly Dictionary<string, string> __msstore_package_manifests = [];

public static string? GetMicrosoftStoreManifest(IPackage package)
{
if (__msstore_package_manifests.TryGetValue(package.Id, out var manifest))
return manifest;

string CountryCode = CultureInfo.CurrentCulture.Name.Split("-")[^1];
string Locale = CultureInfo.CurrentCulture.Name;
string url = $"https://storeedgefd.dsx.mp.microsoft.com/v8.0/sdk/products?market={CountryCode}&locale={Locale}&deviceFamily=Windows.Desktop";

#pragma warning disable SYSLIB0014
var httpRequest = (HttpWebRequest)WebRequest.Create(url);
#pragma warning restore SYSLIB0014

httpRequest.Method = "POST";
httpRequest.ContentType = "application/json";

string data = "{\"productIds\": \"" + package.Id.ToLower() + "\"}";

using (StreamWriter streamWriter = new(httpRequest.GetRequestStream()))
streamWriter.Write(data);

var httpResponse = httpRequest.GetResponse() as HttpWebResponse;
if (httpResponse is null)
{
Logger.Warn($"Null MS Store response for uri={url} and data={data}");
return null;
}

string result;
using (StreamReader streamReader = new(httpResponse.GetResponseStream()))
result = streamReader.ReadToEnd();

Logger.Debug("Microsoft Store API call status code: " + httpResponse.StatusCode);

if (result != "" && httpResponse.StatusCode == HttpStatusCode.OK)
__msstore_package_manifests[package.Id] = result;

return result;
}

public static CacheableIcon? GetMicrosoftStoreIcon(IPackage package)
{
string? ResponseContent = GetMicrosoftStoreManifest(package);
if (ResponseContent is null)
return null;

Match IconArray = Regex.Match(ResponseContent, "(?:\"|')Images(?:\"|'): ?\\[([^\\]]+)\\]");
if (!IconArray.Success)
{
Logger.Warn("Could not parse Images array from Microsoft Store response");
return null;
}

Dictionary<int, string> FoundIcons = [];

foreach (Match ImageEntry in Regex.Matches(IconArray.Groups[1].Value, "{([^}]+)}"))
{
string CurrentImage = ImageEntry.Groups[1].Value;

if (!ImageEntry.Success)
continue;

Match ImagePurpose = Regex.Match(CurrentImage, "(?:\"|')ImagePurpose(?:\"|'): ?(?:\"|')([^'\"]+)(?:\"|')");
if (!ImagePurpose.Success || ImagePurpose.Groups[1].Value != "Tile")
continue;

Match ImageUrl = Regex.Match(CurrentImage, "(?:\"|')Uri(?:\"|'): ?(?:\"|')([^'\"]+)(?:\"|')");
Match ImageSize = Regex.Match(CurrentImage, "(?:\"|')Height(?:\"|'): ?([^,]+)");

if (!ImageUrl.Success || !ImageSize.Success)
continue;

FoundIcons[int.Parse(ImageSize.Groups[1].Value)] = ImageUrl.Groups[1].Value;
}

if (FoundIcons.Count == 0)
{
Logger.Warn($"No Logo image found for package {package.Id} in Microsoft Store response");
return null;
}

Logger.Debug("Choosing icon with size " + FoundIcons.Keys.Max() + " for package " + package.Id + " from Microsoft Store");

string uri = "https:" + FoundIcons[FoundIcons.Keys.Max()];

return new CacheableIcon(new Uri(uri));
}

public static CacheableIcon? GetWinGetPackageIcon(IPackage package)
{
CatalogPackageMetadata? NativeDetails = NativePackageHandler.GetDetails(package);
if (NativeDetails is null) return null;

// Get the actual icon and return it
foreach (Icon? icon in NativeDetails.Icons.ToArray())
if (icon is not null && icon.Url is not null)
// Logger.Debug($"Found WinGet native icon for {package.Id} with URL={icon.Url}");
return new CacheableIcon(new Uri(icon.Url), icon.Sha256);

// Logger.Debug($"Native WinGet icon for Package={package.Id} on catalog={package.Source.Name} was not found :(");
return null;
}

public static CacheableIcon? GetAPPXPackageIcon(IPackage package)
{
string appxId = package.Id.Replace("MSIX\\", "");

string globalPath;
var progsPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps", appxId);
if (Directory.Exists(progsPath))
{
globalPath = Path.Join(progsPath, "Assets");
if (!Directory.Exists(globalPath)) globalPath = Path.Join(progsPath, "Images");
if (!Directory.Exists(globalPath)) globalPath = progsPath;
}
else
{
progsPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "SystemApps", appxId);
globalPath = Path.Join(progsPath, "Assets");
if (!Directory.Exists(globalPath)) globalPath = Path.Join(progsPath, "Images");
if (!Directory.Exists(globalPath)) globalPath = progsPath;
}

if (!Directory.Exists(globalPath))
return null;

string[] logoFiles = Directory.GetFiles(globalPath, "*StoreLogo*.png", SearchOption.TopDirectoryOnly);
if (logoFiles.Length > 0)
return new CacheableIcon(logoFiles[^1]);

logoFiles = Directory.GetFiles(globalPath, "*Splash*.png", SearchOption.TopDirectoryOnly);
if (logoFiles.Length > 0)
return new CacheableIcon(logoFiles[^1]);

logoFiles = Directory.GetFiles(globalPath, "*.png", SearchOption.TopDirectoryOnly);
if (logoFiles.Length > 0)
return new CacheableIcon(logoFiles[^1]);

return null;
}

public static CacheableIcon? GetARPPackageIcon(IPackage package)
{
var bits = package.Id.Split("\\");
if (bits.Length < 4) return null;

string regKey = "";
regKey += bits[1] == "Machine" ? "HKEY_LOCAL_MACHINE" : "HKEY_CURRENT_USER";
regKey += "\\SOFTWARE";
if (bits[2] == "X86")
regKey += "\\WOW6432Node";

regKey += "\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
regKey += bits[3];

string? displayIcon = (string?)Registry.GetValue(regKey, "DisplayIcon", null);
if (!string.IsNullOrEmpty(displayIcon) && File.Exists(displayIcon) && !displayIcon.EndsWith(".exe"))
return new CacheableIcon(displayIcon);

return null;
}
}
Loading
Loading