Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
- Reduced overhead while fetching launcher's sprites by using local ``FallbackCDNUtil``'s HTTP Client method.
- Using direct JSON deserialization from ``FallbackCDNUtil``'s ``DownloadAsJSONType<T>()`` for fetching launcher's API
- Improving Color Palette generation by using the latest ``ColorThief`` changes.
- Moving unrelated methods from ``BackgroundManagement`` to ``RegionManagement``
- Back to previous method while downloading game packages where merging the chunk is required before verification and extraction
- Removed overused ``GC.Collect()``
- Minor UI Changes, including:
    - Adding an ability to go back to the previous page
    - Making title icon clickable for "Back to Homepage" button (Issue: #131)
    - Adding an ability to disable acrylic effect (Issue: #190)
- Adding failsafe if the user choose invalid folders to be used as "App Folder" for the launcher on the first set-up.
- Fixed the rare state where the foreground image is shown outside of the home page.
  • Loading branch information
neon-nyan committed Jul 21, 2023
1 parent 7d40207 commit 6cd747b
Show file tree
Hide file tree
Showing 44 changed files with 1,156 additions and 494 deletions.
321 changes: 305 additions & 16 deletions CollapseLauncher/App.xaml

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions CollapseLauncher/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
using Hi3Helper;
using Hi3Helper.Shared.Region;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Resources;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Storage;
using static CollapseLauncher.InnerLauncherConfig;
using static Hi3Helper.Logger;

Expand All @@ -15,6 +23,10 @@ public App()
{
try
{
bool IsAcrylicEnabled = LauncherConfig.GetAppConfigValue("EnableAcrylicEffect").ToBool();

if (!IsAcrylicEnabled) ToggleBlurBackdrop(false);

this.InitializeComponent();
RequestedTheme = CurrentRequestedAppTheme = GetAppTheme();

Expand All @@ -39,5 +51,45 @@ public App()
Console.ReadLine();
}
}

public static async void ToggleBlurBackdrop(bool useBackdrop = true)
{
// Always wait for the resources to load up
while (true)
{
try
{
await Task.Delay(250);
if (Current.Resources.Count != 0) break;
}
catch { }
}

// Enumerate the dictionary (MergedDictionaries)
foreach (ResourceDictionary resource in Current
.Resources
.MergedDictionaries)
{
// Parse the dictionary (ThemeDictionaries) and read the type of KeyValuePair<object, object>,
// then select the value, get the type of ResourceDictionary, then enumerate it
foreach (ResourceDictionary list in resource
.ThemeDictionaries
.OfType<KeyValuePair<object, object>>()
.Select(x => x.Value)
.OfType<ResourceDictionary>())
{
// Parse the dictionary as type of KeyValuePair<object, object>,
// and get the value which has type of AcrylicBrush only, then enumerate it
foreach (AcrylicBrush theme in list
.OfType<KeyValuePair<object, object>>()
.Select(x => x.Value)
.OfType<AcrylicBrush>())
{
// Set the theme AlwaysUseFallback as per toggle from useBackdrop.
theme.AlwaysUseFallback = !useBackdrop;
}
}
}
}
}
}
242 changes: 20 additions & 222 deletions CollapseLauncher/Classes/BackgroundManagement/BackgroundManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
Expand All @@ -34,20 +33,6 @@ public sealed partial class MainPage : Page
private bool BGLastState = true;
private bool IsFirstStartup = true;

private async ValueTask FetchLauncherLocalizedResources(CancellationToken Token, PresetConfigV2 Preset)
{
regionBackgroundProp = Preset.LauncherSpriteURLMultiLang ?
await TryGetMultiLangResourceProp(Token, Preset) :
await TryGetSingleLangResourceProp(Token, Preset);

await DownloadBackgroundImage(Token);

await GetLauncherAdvInfo(Token, Preset);
await GetLauncherCarouselInfo(Token);
await GetLauncherEventInfo(Token);
GetLauncherPostInfo();
}

private async Task ChangeBackgroundImageAsRegion()
{
IsCustomBG = GetAppConfigValue("UseCustomBG").ToBool();
Expand All @@ -70,166 +55,6 @@ private async Task ChangeBackgroundImageAsRegion()
ReloadPageTheme(this, ConvertAppThemeToElementTheme(CurrentAppTheme));
}

private async ValueTask FetchLauncherDownloadInformation(CancellationToken Token, PresetConfigV2 Preset)
{
using (Stream netStream = (await _httpClient.DownloadFromSessionStreamAsync(
Preset.LauncherResourceURL,
0,
null,
Token
)).Item1)
{
_gameAPIProp = (RegionResourceProp)JsonSerializer.Deserialize(netStream, typeof(RegionResourceProp), RegionResourcePropContext.Default) ?? new RegionResourceProp();

#if DEBUG
if (_gameAPIProp.data.game.latest.decompressed_path != null) LogWriteLine($"Decompressed Path: {_gameAPIProp.data.game.latest.decompressed_path}", LogType.Default, true);
if (_gameAPIProp.data.game.latest.path != null) LogWriteLine($"ZIP Path: {_gameAPIProp.data.game.latest.path}", LogType.Default, true);
if (_gameAPIProp.data.pre_download_game?.latest?.decompressed_path != null) LogWriteLine($"Decompressed Path Pre-load: {_gameAPIProp.data.pre_download_game?.latest?.decompressed_path}", LogType.Default, true);
if (_gameAPIProp.data.pre_download_game?.latest?.path != null) LogWriteLine($"ZIP Path Pre-load: {_gameAPIProp.data.pre_download_game?.latest?.path}", LogType.Default, true);
#endif
}
}

private async ValueTask<RegionResourceProp> TryGetMultiLangResourceProp(CancellationToken Token, PresetConfigV2 Preset)
{
RegionResourceProp ret = await GetMultiLangResourceProp(Lang.LanguageID.ToLower(), Token, Preset);

return ret.data.adv == null
|| ((ret.data.adv.version ?? 5) <= 4
&& Preset.GameType == GameType.Honkai) ?
await GetMultiLangResourceProp(Preset.LauncherSpriteURLMultiLangFallback ?? "en-us", Token, Preset) :
ret;
}

private async ValueTask<RegionResourceProp> GetMultiLangResourceProp(string langID, CancellationToken token, PresetConfigV2 Preset)
{
using (Stream netStream = (await _httpClient.DownloadFromSessionStreamAsync(
string.Format(Preset.LauncherSpriteURL, langID),
0,
null,
token
)).Item1)
{
return (RegionResourceProp)JsonSerializer.Deserialize(netStream, typeof(RegionResourceProp), RegionResourcePropContext.Default) ?? new RegionResourceProp();
}
}

private async ValueTask<RegionResourceProp> TryGetSingleLangResourceProp(CancellationToken Token, PresetConfigV2 Preset)
{
using (Stream netStream = (await _httpClient.DownloadFromSessionStreamAsync(
Preset.LauncherSpriteURL,
0,
null,
Token
)).Item1)
{
return (RegionResourceProp)JsonSerializer.Deserialize(netStream, typeof(RegionResourceProp), RegionResourcePropContext.Default) ?? new RegionResourceProp();
}
}

private void ResetRegionProp()
{
LastRegionNewsProp = regionNewsProp.Copy();
regionNewsProp = new HomeMenuPanel()
{
sideMenuPanel = null,
imageCarouselPanel = null,
articlePanel = null,
eventPanel = null
};
}

private async ValueTask GetLauncherAdvInfo(CancellationToken Token, PresetConfigV2 Preset)
{
if (regionBackgroundProp.data.icon.Count == 0) return;

regionNewsProp.sideMenuPanel = new List<MenuPanelProp>();
foreach (RegionSocMedProp item in regionBackgroundProp.data.icon)
{
regionNewsProp.sideMenuPanel.Add(new MenuPanelProp
{
URL = item.url,
Icon = await GetCachedSprites(item.img, Token),
IconHover = await GetCachedSprites(item.img_hover, Token),
QR = string.IsNullOrEmpty(item.qr_img) ? null : await GetCachedSprites(item.qr_img, Token),
QR_Description = string.IsNullOrEmpty(item.qr_desc) ? null : item.qr_desc,
Description = string.IsNullOrEmpty(item.title) || Preset.IsHideSocMedDesc ? item.url : item.title
});
}
}

private async ValueTask GetLauncherCarouselInfo(CancellationToken Token)
{
if (regionBackgroundProp.data.banner.Count == 0) return;

regionNewsProp.imageCarouselPanel = new List<MenuPanelProp>();
foreach (RegionSocMedProp item in regionBackgroundProp.data.banner)
{
regionNewsProp.imageCarouselPanel.Add(new MenuPanelProp
{
URL = item.url,
Icon = await GetCachedSprites(item.img, Token),
Description = string.IsNullOrEmpty(item.name) ? item.url : item.name
});
}
}

private async ValueTask GetLauncherEventInfo(CancellationToken Token)
{
if (string.IsNullOrEmpty(regionBackgroundProp.data.adv.icon)) return;

regionNewsProp.eventPanel = new RegionBackgroundProp
{
url = regionBackgroundProp.data.adv.url,
icon = await GetCachedSprites(regionBackgroundProp.data.adv.icon, Token)
};
}

private void GetLauncherPostInfo()
{
if (regionBackgroundProp.data.post.Count == 0) return;

regionNewsProp.articlePanel = new PostCarouselTypes();
foreach (RegionSocMedProp item in regionBackgroundProp.data.post)
{
switch (item.type)
{
case PostCarouselType.POST_TYPE_ACTIVITY:
regionNewsProp.articlePanel.Events.Add(item);
break;
case PostCarouselType.POST_TYPE_ANNOUNCE:
regionNewsProp.articlePanel.Notices.Add(item);
break;
case PostCarouselType.POST_TYPE_INFO:
regionNewsProp.articlePanel.Info.Add(item);
break;
}
}
}

public async ValueTask<string> GetCachedSprites(string URL, CancellationToken token)
{
string cacheFolder = Path.Combine(AppGameImgFolder, "cache");
string cachePath = Path.Combine(cacheFolder, Path.GetFileNameWithoutExtension(URL));
if (!Directory.Exists(cacheFolder))
Directory.CreateDirectory(cacheFolder);

FileInfo fInfo = new FileInfo(cachePath);

if (!fInfo.Exists || fInfo.Length < (1 << 10))
{
using (FileStream fs = fInfo.Create())
{
using (Stream netStream = (await _httpClient.DownloadFromSessionStreamAsync(URL, 0, null, token)).Item1)
{
netStream.CopyTo(fs);
}
}
}

return cachePath;
}

public static async Task<Windows.UI.Color[]> ApplyAccentColor(Page page, Bitmap bitmapinput, int quality)
{
Windows.UI.Color[] _colors;
Expand Down Expand Up @@ -268,11 +93,11 @@ public async ValueTask<string> GetCachedSprites(string URL, CancellationToken to
private static async Task<Windows.UI.Color[]> SetDarkColors(Bitmap bitmapinput, int quality)
{
Windows.UI.Color[] _colors = await GetPaletteList(bitmapinput, 255, false, quality);
Application.Current.Resources["SystemAccentColor"] = _colors[0];
Application.Current.Resources["SystemAccentColor"] = _colors[1];
Application.Current.Resources["SystemAccentColorLight1"] = _colors[1];
Application.Current.Resources["SystemAccentColorLight2"] = _colors[2];
Application.Current.Resources["SystemAccentColorLight3"] = _colors[3];
Application.Current.Resources["AccentColor"] = new SolidColorBrush(_colors[0]);
Application.Current.Resources["SystemAccentColorLight2"] = _colors[0];
Application.Current.Resources["SystemAccentColorLight3"] = _colors[1];
Application.Current.Resources["AccentColor"] = new SolidColorBrush(_colors[1]);

return _colors;
}
Expand All @@ -284,7 +109,10 @@ public async ValueTask<string> GetCachedSprites(string URL, CancellationToken to

try
{
List<QuantizedColor> ThemedColors = await Task.Run(() => ColorThief.GetPalette(bitmapinput, ColorCount, IsLight ? 1 : quality).Where(x => IsLight ? x.IsDark : !x.IsDark).ToList());
LumaUtils.DarkThreshold = IsLight ? 300f : 300f;
LumaUtils.IgnoreWhiteThreshold = IsLight ? 900f : 750f;
LumaUtils.ChangeCoeToBT709();
List<QuantizedColor> ThemedColors = await Task.Run(() => ColorThief.GetPalette(bitmapinput, ColorCount, IsLight ? 1 : 1).Where(x => IsLight ? x.IsDark : !x.IsDark).ToList());

if (ThemedColors.Count == 0 || ThemedColors.Count < output.Length) throw new Exception($"The image doesn't have {output.Length} matched colors to assign. Fallback to default!");
for (int i = 0, j = output.Length - 1; i < output.Length; i++, j--)
Expand Down Expand Up @@ -316,25 +144,21 @@ public async ValueTask<string> GetCachedSprites(string URL, CancellationToken to

FileInfo cachedFileInfo = new FileInfo(cachedFilePath);

bool isCachedFileExist = cachedFileInfo.Exists && cachedFileInfo.Length > 4 << 15;
FileStream cachedFileStream = isCachedFileExist ? cachedFileInfo.OpenRead() : cachedFileInfo.Create();

try
bool isCachedFileExist = cachedFileInfo.Exists && cachedFileInfo.Length > 1 << 15;
using (stream)
using (FileStream cachedFileStream = isCachedFileExist ? cachedFileInfo.OpenRead() : cachedFileInfo.Create())
{
if (!isCachedFileExist)
try
{
await GetResizedImageStream(stream, cachedFileStream, ToWidth, ToHeight);
}
if (!isCachedFileExist)
{
await GetResizedImageStream(stream, cachedFileStream, ToWidth, ToHeight);
}

bitmapRet = await Task.Run(() => Stream2Bitmap(cachedFileStream.AsRandomAccessStream()));
bitmapImageRet = await Stream2BitmapImage(cachedFileStream.AsRandomAccessStream());
}
catch { throw; }
finally
{
stream?.Dispose();
cachedFileStream?.Dispose();
GC.Collect();
bitmapRet = await Task.Run(() => Stream2Bitmap(cachedFileStream.AsRandomAccessStream()));
bitmapImageRet = await Stream2BitmapImage(cachedFileStream.AsRandomAccessStream());
}
catch { throw; }
}

return (bitmapRet, bitmapImageRet);
Expand Down Expand Up @@ -421,27 +245,6 @@ private static (uint, uint) GetPreservedImageRatio(uint canvasWidth, uint canvas
return ((uint)(imgWidth * ratio), (uint)(imgHeight * ratio));
}

private async ValueTask DownloadBackgroundImage(CancellationToken Token)
{
regionBackgroundProp.imgLocalPath = Path.Combine(AppGameImgFolder, "bg", Path.GetFileName(regionBackgroundProp.data.adv.background));
SetAndSaveConfigValue("CurrentBackground", regionBackgroundProp.imgLocalPath);

if (!Directory.Exists(Path.Combine(AppGameImgFolder, "bg")))
Directory.CreateDirectory(Path.Combine(AppGameImgFolder, "bg"));

FileInfo fI = new FileInfo(regionBackgroundProp.imgLocalPath);

if (fI.Exists) return;

using (Stream netStream = (await _httpClient.DownloadFromSessionStreamAsync(regionBackgroundProp.data.adv.background, 0, null, Token)).Item1)
{
using (Stream outStream = fI.Create())
{
netStream.CopyTo(outStream);
}
}
}

private async void ApplyBackgroundAsync() => await ApplyBackground();

private async Task ApplyBackground()
Expand All @@ -459,11 +262,6 @@ private async Task ApplyBackground()

FadeOutFrontBg();
FadeOutBackBg();

GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
GC.Collect();
}

private async void FadeOutFrontBg()
Expand Down
19 changes: 0 additions & 19 deletions CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,25 +140,6 @@ private async Task BuildGameRepoURL(CancellationToken token)
// Build the asset index and return the count and size of each type
(int, long) returnValue = BuildAssetIndex(type, baseURL, xorStream, assetIndex);

/*
// Fetch additional patch for Data type
if (type == CacheAssetType.Data)
{
// Get the patch config file
assetIndexURL = CombineURLFromString(baseURL, "patch", "patchconfig.xmf");
// Reinitialize the memory stream;
stream.Dispose();
stream = new MemoryStream();
// Start downloading the patch config
await _httpClient.Download(assetIndexURL, stream, null, null, token);
// Build patch config for Data type
BuildDataPatchConfig(stream, assetIndex);
}
*/

return returnValue;
}
catch { throw; }
Expand Down
Loading

0 comments on commit 6cd747b

Please sign in to comment.