Skip to content

Commit 833a0bf

Browse files
authored
Merge pull request #3564 from marticliment/icons-on-local-packages
2 parents 43a48ed + dc1078e commit 833a0bf

8 files changed

Lines changed: 235 additions & 153 deletions

File tree

src/UniGetUI.Core.IconStore/IconCacheEngine.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public readonly struct CacheableIcon
2424
public readonly string Version = "";
2525
public readonly long Size = -1;
2626
private readonly int _hashCode = -1;
27+
public readonly bool IsLocalPath = false;
28+
public readonly string LocalPath = "";
2729
public readonly IconValidationMethod ValidationMethod;
2830

2931
/// <summary>
@@ -76,6 +78,13 @@ public CacheableIcon(Uri uri)
7678
_hashCode = uri.ToString().GetHashCode();
7779
}
7880

81+
public CacheableIcon(string path)
82+
{
83+
IsLocalPath = true;
84+
LocalPath = path;
85+
Url = new Uri(path);
86+
}
87+
7988
public override int GetHashCode()
8089
{
8190
return _hashCode;
@@ -101,6 +110,10 @@ public static class IconCacheEngine
101110
return null;
102111

103112
var icon = _icon.Value;
113+
114+
if(icon.IsLocalPath)
115+
return icon.LocalPath;
116+
104117
string iconLocation = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName, PackageId);
105118
if (!Directory.Exists(iconLocation)) Directory.CreateDirectory(iconLocation);
106119
string iconVersionFile = Path.Join(iconLocation, $"icon.version");
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using System.Globalization;
2+
using System.Net;
3+
using System.Text;
4+
using System.Text.RegularExpressions;
5+
using Microsoft.Management.Deployment;
6+
using Microsoft.Win32;
7+
using UniGetUI.Core.IconEngine;
8+
using UniGetUI.Core.Logging;
9+
using UniGetUI.PackageEngine.Interfaces;
10+
using UniGetUI.PackageEngine.Managers.WingetManager;
11+
12+
namespace UniGetUI.PackageEngine.Managers.WinGet.ClientHelpers;
13+
internal static class WinGetIconsHelper
14+
{
15+
private static readonly Dictionary<string, string> __msstore_package_manifests = [];
16+
17+
public static string? GetMicrosoftStoreManifest(IPackage package)
18+
{
19+
if (__msstore_package_manifests.TryGetValue(package.Id, out var manifest))
20+
return manifest;
21+
22+
string CountryCode = CultureInfo.CurrentCulture.Name.Split("-")[^1];
23+
string Locale = CultureInfo.CurrentCulture.Name;
24+
string url = $"https://storeedgefd.dsx.mp.microsoft.com/v8.0/sdk/products?market={CountryCode}&locale={Locale}&deviceFamily=Windows.Desktop";
25+
26+
#pragma warning disable SYSLIB0014
27+
var httpRequest = (HttpWebRequest)WebRequest.Create(url);
28+
#pragma warning restore SYSLIB0014
29+
30+
httpRequest.Method = "POST";
31+
httpRequest.ContentType = "application/json";
32+
33+
string data = "{\"productIds\": \"" + package.Id.ToLower() + "\"}";
34+
35+
using (StreamWriter streamWriter = new(httpRequest.GetRequestStream()))
36+
streamWriter.Write(data);
37+
38+
var httpResponse = httpRequest.GetResponse() as HttpWebResponse;
39+
if (httpResponse is null)
40+
{
41+
Logger.Warn($"Null MS Store response for uri={url} and data={data}");
42+
return null;
43+
}
44+
45+
string result;
46+
using (StreamReader streamReader = new(httpResponse.GetResponseStream()))
47+
result = streamReader.ReadToEnd();
48+
49+
Logger.Debug("Microsoft Store API call status code: " + httpResponse.StatusCode);
50+
51+
if (result != "" && httpResponse.StatusCode == HttpStatusCode.OK)
52+
__msstore_package_manifests[package.Id] = result;
53+
54+
return result;
55+
}
56+
57+
public static CacheableIcon? GetMicrosoftStoreIcon(IPackage package)
58+
{
59+
string? ResponseContent = GetMicrosoftStoreManifest(package);
60+
if (ResponseContent is null)
61+
return null;
62+
63+
Match IconArray = Regex.Match(ResponseContent, "(?:\"|')Images(?:\"|'): ?\\[([^\\]]+)\\]");
64+
if (!IconArray.Success)
65+
{
66+
Logger.Warn("Could not parse Images array from Microsoft Store response");
67+
return null;
68+
}
69+
70+
Dictionary<int, string> FoundIcons = [];
71+
72+
foreach (Match ImageEntry in Regex.Matches(IconArray.Groups[1].Value, "{([^}]+)}"))
73+
{
74+
string CurrentImage = ImageEntry.Groups[1].Value;
75+
76+
if (!ImageEntry.Success)
77+
continue;
78+
79+
Match ImagePurpose = Regex.Match(CurrentImage, "(?:\"|')ImagePurpose(?:\"|'): ?(?:\"|')([^'\"]+)(?:\"|')");
80+
if (!ImagePurpose.Success || ImagePurpose.Groups[1].Value != "Tile")
81+
continue;
82+
83+
Match ImageUrl = Regex.Match(CurrentImage, "(?:\"|')Uri(?:\"|'): ?(?:\"|')([^'\"]+)(?:\"|')");
84+
Match ImageSize = Regex.Match(CurrentImage, "(?:\"|')Height(?:\"|'): ?([^,]+)");
85+
86+
if (!ImageUrl.Success || !ImageSize.Success)
87+
continue;
88+
89+
FoundIcons[int.Parse(ImageSize.Groups[1].Value)] = ImageUrl.Groups[1].Value;
90+
}
91+
92+
if (FoundIcons.Count == 0)
93+
{
94+
Logger.Warn($"No Logo image found for package {package.Id} in Microsoft Store response");
95+
return null;
96+
}
97+
98+
Logger.Debug("Choosing icon with size " + FoundIcons.Keys.Max() + " for package " + package.Id + " from Microsoft Store");
99+
100+
string uri = "https:" + FoundIcons[FoundIcons.Keys.Max()];
101+
102+
return new CacheableIcon(new Uri(uri));
103+
}
104+
105+
public static CacheableIcon? GetWinGetPackageIcon(IPackage package)
106+
{
107+
CatalogPackageMetadata? NativeDetails = NativePackageHandler.GetDetails(package);
108+
if (NativeDetails is null) return null;
109+
110+
// Get the actual icon and return it
111+
foreach (Icon? icon in NativeDetails.Icons.ToArray())
112+
if (icon is not null && icon.Url is not null)
113+
// Logger.Debug($"Found WinGet native icon for {package.Id} with URL={icon.Url}");
114+
return new CacheableIcon(new Uri(icon.Url), icon.Sha256);
115+
116+
// Logger.Debug($"Native WinGet icon for Package={package.Id} on catalog={package.Source.Name} was not found :(");
117+
return null;
118+
}
119+
120+
public static CacheableIcon? GetAPPXPackageIcon(IPackage package)
121+
{
122+
string appxId = package.Id.Replace("MSIX\\", "");
123+
124+
string globalPath;
125+
var progsPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps", appxId);
126+
if (Directory.Exists(progsPath))
127+
{
128+
globalPath = Path.Join(progsPath, "Assets");
129+
if (!Directory.Exists(globalPath)) globalPath = Path.Join(progsPath, "Images");
130+
if (!Directory.Exists(globalPath)) globalPath = progsPath;
131+
}
132+
else
133+
{
134+
progsPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "SystemApps", appxId);
135+
globalPath = Path.Join(progsPath, "Assets");
136+
if (!Directory.Exists(globalPath)) globalPath = Path.Join(progsPath, "Images");
137+
if (!Directory.Exists(globalPath)) globalPath = progsPath;
138+
}
139+
140+
if (!Directory.Exists(globalPath))
141+
return null;
142+
143+
string[] logoFiles = Directory.GetFiles(globalPath, "*StoreLogo*.png", SearchOption.TopDirectoryOnly);
144+
if (logoFiles.Length > 0)
145+
return new CacheableIcon(logoFiles[^1]);
146+
147+
logoFiles = Directory.GetFiles(globalPath, "*Splash*.png", SearchOption.TopDirectoryOnly);
148+
if (logoFiles.Length > 0)
149+
return new CacheableIcon(logoFiles[^1]);
150+
151+
logoFiles = Directory.GetFiles(globalPath, "*.png", SearchOption.TopDirectoryOnly);
152+
if (logoFiles.Length > 0)
153+
return new CacheableIcon(logoFiles[^1]);
154+
155+
return null;
156+
}
157+
158+
public static CacheableIcon? GetARPPackageIcon(IPackage package)
159+
{
160+
var bits = package.Id.Split("\\");
161+
if (bits.Length < 4) return null;
162+
163+
string regKey = "";
164+
regKey += bits[1] == "Machine" ? "HKEY_LOCAL_MACHINE" : "HKEY_CURRENT_USER";
165+
regKey += "\\SOFTWARE";
166+
if (bits[2] == "X86")
167+
regKey += "\\WOW6432Node";
168+
169+
regKey += "\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
170+
regKey += bits[3];
171+
172+
string? displayIcon = (string?)Registry.GetValue(regKey, "DisplayIcon", null);
173+
if (!string.IsNullOrEmpty(displayIcon) && File.Exists(displayIcon) && !displayIcon.EndsWith(".exe"))
174+
return new CacheableIcon(displayIcon);
175+
176+
return null;
177+
}
178+
}

0 commit comments

Comments
 (0)