diff --git a/App.xaml.cs b/App.xaml.cs index a812a28..ab15d7c 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -13,14 +13,15 @@ namespace BetterHI3Launcher { public partial class App : Application { - public static readonly LauncherVersion LocalLauncherVersion = new LauncherVersion("1.3.20221030.0"); + public static readonly LauncherVersion LocalLauncherVersion = new LauncherVersion("1.4.20230111.0"); public static readonly string LauncherRootPath = AppDomain.CurrentDomain.BaseDirectory; public static readonly string LocalLowPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}Low"; public static readonly string LauncherDataPath = Path.Combine(LocalLowPath, @"Bp\Better HI3 Launcher"); public static readonly string LauncherBackgroundsPath = Path.Combine(LauncherDataPath, "Backgrounds"); public static readonly string LauncherLogFile = Path.Combine(LauncherDataPath, "BetterHI3Launcher-latest.log"); public static readonly string LauncherTranslationsFile = Path.Combine(LauncherDataPath, "BetterHI3Launcher-translations.json"); - public static string UserAgent = $"BetterHI3Launcher v{LocalLauncherVersion}"; + public static string UserAgent = $"BetterHI3Launcher/v{LocalLauncherVersion}"; + public static List UserAgentComment = new List(); public static string LauncherExeName, LauncherPath, LauncherArchivePath, LauncherLanguage; public static readonly string OSVersion = BpUtility.GetWindowsVersion(); public static readonly string OSLanguage = CultureInfo.CurrentUICulture.ToString(); @@ -28,7 +29,7 @@ public partial class App : Application public static List SeenAnnouncements = new List(); public static JArray Announcements = new JArray(); public static RegistryKey LauncherRegKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Bp\Better HI3 Launcher"); - public static bool DisableAutoUpdate, DisableLogging, DisableTranslations, DisableSounds, AdvancedFeatures, NeedsUpdate, UseLegacyDownload; + public static bool DisableAutoUpdate, DisableLogging, DisableTranslations, DisableSounds, AdvancedFeatures, NeedsUpdate; public static bool FirstLaunch = LauncherRegKey.GetValue("LauncherVersion") == null ? true : false; public static bool Starting = true; public static readonly int ParallelDownloadSessions = 4; @@ -61,7 +62,7 @@ protected override void OnStartup(StartupEventArgs e) CultureInfo.DefaultThreadCurrentUICulture = culture; #if DEBUG WinConsole.Initialize(); - UserAgent += " [DEBUG]"; + UserAgentComment.Add("DEBUG"); #endif TextStrings_English(); switch(OSLanguage) diff --git a/BetterHI3Launcher.csproj b/BetterHI3Launcher.csproj index 59a5f08..9a86bb9 100644 --- a/BetterHI3Launcher.csproj +++ b/BetterHI3Launcher.csproj @@ -130,16 +130,24 @@ - - + + - - - + + + + + + + + + + - - + + + @@ -255,7 +263,7 @@ 2.5.2 - 6.0.6 + 6.0.7 2.0.3 diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index a406a6d..adfe471 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -31,7 +31,7 @@ public partial class MainWindow : Window public static string GameInstallPath, GameCachePath, GameRegistryPath, GameArchivePath, GameArchiveTempPath, GameExePath; public static string RegistryVersionInfo; public static string GameWebProfileURL, GameFullName, GameArchiveName, GameExeName, GameInstallRegistryName; - public static bool DownloadPaused, PatchDownload, PreloadDownload, BackgroundImageDownloading, LegacyBoxActive; + public static bool DownloadPaused, PatchDownload, PreloadDownload, BackgroundImageDownloading, LegacyBoxActive, ActionAbort; public static int PatchDownloadInt; public static RoutedCommand DownloadCacheCommand = new RoutedCommand(); public static RoutedCommand RepairGameCommand = new RoutedCommand(); @@ -52,7 +52,6 @@ public partial class MainWindow : Window Http httpclient; HttpProp httpprop; CancellationTokenSource token; - DownloadPauseable download; DownloadProgressTracker tracker = new DownloadProgressTracker(50, TimeSpan.FromMilliseconds(500)); internal LauncherStatus Status @@ -254,6 +253,7 @@ internal HI3Mirror Mirror public MainWindow() { InitializeComponent(); + var args = new List(); for(int i = 1; i < App.CommandLineArgs.Length; i++) { @@ -293,7 +293,8 @@ public MainWindow() } } DeleteFile(App.LauncherLogFile, true); - Log(App.UserAgent, false); + + Log($"BetterHI3Launcher v{App.LocalLauncherVersion}", false); Log($"Working directory: {App.LauncherRootPath}"); Log($"OS version: {App.OSVersion}"); Log($"OS language: {App.OSLanguage}"); @@ -301,38 +302,33 @@ public MainWindow() if(args.Contains("NOUPDATE")) { App.DisableAutoUpdate = true; - App.UserAgent += " [NOUPDATE]"; + App.UserAgentComment.Add("NOUPDATE"); Log("Auto-update disabled"); } #endif if(args.Contains("NOLOG")) { - App.UserAgent += " [NOLOG]"; + App.UserAgentComment.Add("NOLOG"); Log("Logging to file disabled"); } if(args.Contains("NOTRANSLATIONS")) { App.DisableTranslations = true; App.LauncherLanguage = "en"; - App.UserAgent += " [NOTRANSLATIONS]"; + App.UserAgentComment.Add("NOTRANSLATIONS"); Log("Translations disabled, only English will be available"); } - if(args.Contains("LEGACYDOWNLOAD")) - { - App.UseLegacyDownload = true; - App.UserAgent += " [LEGACYDOWNLOAD]"; - Log("Using legacy download method"); - } if(args.Contains("ADVANCED")) { App.AdvancedFeatures = true; - App.UserAgent += " [ADVANCED]"; + App.UserAgentComment.Add("ADVANCED"); Log("Advanced features enabled"); } else { RepairBoxGenerateButton.Visibility = Visibility.Collapsed; } + string language_log_msg = "Launcher language: {0}"; var language_reg = App.LauncherRegKey.GetValue("Language"); if(!App.DisableTranslations) @@ -352,7 +348,10 @@ public MainWindow() } language_log_msg = string.Format(language_log_msg, App.LauncherLanguage); Log(language_log_msg); - App.UserAgent += $" [{App.LauncherLanguage}] [{App.OSVersion}]"; + App.UserAgentComment.Add(App.LauncherLanguage); + App.UserAgentComment.Add(App.OSLanguage); + App.UserAgentComment.Add(App.OSVersion); + App.UserAgent += $" ({string.Join("; ", App.UserAgentComment)})"; LaunchButton.Content = App.TextStrings["button_download"]; OptionsButton.Content = App.TextStrings["button_options"]; @@ -1048,17 +1047,10 @@ private async void LaunchButton_Click(object sender, RoutedEventArgs e) try { - if(!App.UseLegacyDownload) - { - path = GameInstallPath; - if(!Directory.Exists(GameInstallPath)) - { - Directory.CreateDirectory(GameInstallPath); - } - } - else + path = GameInstallPath; + if(!Directory.Exists(GameInstallPath)) { - path = Directory.CreateDirectory(GameInstallPath).FullName; + Directory.CreateDirectory(GameInstallPath); } } catch(Exception ex) @@ -1121,35 +1113,16 @@ private async void LaunchButton_Click(object sender, RoutedEventArgs e) } else if(Status == LauncherStatus.Downloading || Status == LauncherStatus.DownloadPaused) { - if(!App.UseLegacyDownload) + if(httpclient.DownloadState == DownloadState.Merging) { - if(httpclient.SessionState == MultisessionState.Merging) - { - LaunchButton.IsEnabled = false; - return; - } + LaunchButton.IsEnabled = false; + return; } if(new DialogWindow(App.TextStrings["msgbox_abort_title"], $"{App.TextStrings["msgbox_abort_2_msg"]}\n{App.TextStrings["msgbox_abort_3_msg"]}", DialogWindow.DialogType.Question).ShowDialog() == true) { - if(!App.UseLegacyDownload) - { - token.Cancel(); - await httpclient.DeleteMultisessionChunks(httpprop.Out); - } - else - { - download.Pause(); - download = null; - if(!string.IsNullOrEmpty(GameArchiveTempPath)) - { - while(BpUtility.IsFileLocked(new FileInfo(GameArchiveTempPath))) - { - Thread.Sleep(10); - } - DeleteFile(GameArchiveTempPath, true); - } - } - + token.Cancel(); + await httpclient.WaitUntilInstanceDisposed(); + httpclient.DeleteMultisessionFiles(httpprop.Out, httpprop.Thread); try{Directory.Delete(Path.GetDirectoryName(GameArchiveTempPath));}catch{} DownloadPaused = false; Log("Download cancelled"); @@ -1157,6 +1130,11 @@ private async void LaunchButton_Click(object sender, RoutedEventArgs e) GameUpdateCheck(); } } + else if(Status == LauncherStatus.Working) + { + LaunchButton.IsEnabled = false; + ActionAbort = true; + } } private void OptionsButton_Click(object sender, RoutedEventArgs e) @@ -1183,20 +1161,14 @@ private async void DownloadPauseButton_Click(object sender, RoutedEventArgs e) if(!DownloadPaused) { + token.Cancel(); + await httpclient.WaitUntilInstanceDisposed(); Status = LauncherStatus.DownloadPaused; DownloadProgressBarStackPanel.Visibility = Visibility.Visible; DownloadETAText.Visibility = Visibility.Hidden; DownloadSpeedText.Visibility = Visibility.Hidden; DownloadPauseButton.Visibility = Visibility.Collapsed; TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Paused; - if(!App.UseLegacyDownload) - { - token.Cancel(); - } - else - { - download.Pause(); - } DownloadResumeButton.Visibility = Visibility.Visible; Log("Download paused"); } @@ -1214,23 +1186,18 @@ private async void DownloadPauseButton_Click(object sender, RoutedEventArgs e) DownloadProgressBarStackPanel.Visibility = Visibility.Visible; LaunchButton.IsEnabled = true; LaunchButton.Content = App.TextStrings["button_cancel"]; + TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Normal; Log("Download resumed"); - if(!App.UseLegacyDownload) + using(httpclient = new Http(true, 5, 1000, App.UserAgent)) { - TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Normal; - httpclient = new Http(); token = new CancellationTokenSource(); httpclient.DownloadProgress += DownloadStatusChanged; - await httpclient.DownloadMultisession(httpprop.URL, httpprop.Out, false, httpprop.Thread, token.Token); - await httpclient.MergeMultisession(httpprop.Out, httpprop.Thread, token.Token); + await httpclient.Download(httpprop.URL, httpprop.Out, httpprop.Thread, false, token.Token); + await httpclient.Merge(); httpclient.DownloadProgress -= DownloadStatusChanged; await DownloadGameFile(); } - else - { - await download.Start(); - } } catch(TaskCanceledException){} catch(OperationCanceledException){} @@ -1264,14 +1231,13 @@ private async void PreloadButton_Click(object sender, RoutedEventArgs e) string md5 = miHoYoVersionInfo.pre_download_game.latest.md5.ToString(); string path = Path.Combine(GameInstallPath, title); string tmp_path = $"{path}_tmp"; - bool abort = false; var web_request = BpUtility.CreateWebRequest(url, "HEAD"); using(var web_response = (HttpWebResponse) web_request.GetResponse()) { size = web_response.ContentLength; } - if(App.UseLegacyDownload && !File.Exists(tmp_path) || !App.UseLegacyDownload && !File.Exists($"{tmp_path}.001")) + if(Directory.GetFiles(GameInstallPath, $"{title}_tmp.*").Length == 0) { if(new DialogWindow(App.TextStrings["label_pre_install"], $"{App.TextStrings["msgbox_pre_install_msg"]}\n{string.Format(App.TextStrings["msgbox_install_2_msg"], BpUtility.ToBytesCount(size))}", DialogWindow.DialogType.Question).ShowDialog() == false) { @@ -1297,69 +1263,25 @@ private async void PreloadButton_Click(object sender, RoutedEventArgs e) { File.Move(path, tmp_path); } - if(!App.UseLegacyDownload) + if(!File.Exists(tmp_path)) { - if(!File.Exists(tmp_path)) + try { - try + using(httpclient = new Http(true, 5, 1000, App.UserAgent)) { - httpclient = new Http(); token = new CancellationTokenSource(); httpprop = new HttpProp(url, tmp_path); httpclient.DownloadProgress += PreloadDownloadStatusChanged; PreloadPauseButton.IsEnabled = true; - await httpclient.DownloadMultisession(httpprop.URL, httpprop.Out, false, httpprop.Thread, token.Token); - await httpclient.MergeMultisession(httpprop.Out, httpprop.Thread, token.Token); + await httpclient.Download(httpprop.URL, httpprop.Out, httpprop.Thread, false, token.Token); + await httpclient.Merge(); httpclient.DownloadProgress -= PreloadDownloadStatusChanged; Log("Downloaded pre-download archive"); } - catch(OperationCanceledException) - { - httpclient.DownloadProgress -= PreloadDownloadStatusChanged; - return; - } } - } - else - { - await Task.Run(() => - { - tracker.NewFile(); - var eta_calc = new ETACalculator(); - download = new DownloadPauseable(url, tmp_path); - download.Start(); - while(download != null && !download.Done) - { - tracker.SetProgress(download.BytesWritten, download.ContentLength); - eta_calc.Update((float)download.BytesWritten / (float)download.ContentLength); - Dispatcher.Invoke(() => - { - var progress = tracker.GetProgress(); - PreloadCircleProgressBar.Value = progress; - TaskbarItemInfo.ProgressValue = progress; - PreloadBottomText.Text = string.Format(App.TextStrings["label_downloaded_1"], Math.Round(progress * 100)); - PreloadStatusTopLeftText.Text = App.TextStrings["label_downloaded_2"]; - PreloadStatusTopRightText.Text = $"{BpUtility.ToBytesCount(download.BytesWritten)}/{BpUtility.ToBytesCount(download.ContentLength)}"; - PreloadStatusMiddleRightText.Text = eta_calc.ETR.ToString("hh\\:mm\\:ss"); - PreloadStatusBottomLeftText.Text = App.TextStrings["label_download_speed"]; - PreloadStatusBottomRightText.Text = tracker.GetBytesPerSecondString(); - }); - Thread.Sleep(500); - } - if(download == null) - { - abort = true; - Status = LauncherStatus.Ready; - return; - } - Log("Downloaded pre-download archive"); - while(BpUtility.IsFileLocked(new FileInfo(tmp_path))) - { - Thread.Sleep(10); - } - }); - if(abort) + catch(OperationCanceledException) { + httpclient.DownloadProgress -= PreloadDownloadStatusChanged; return; } } @@ -1425,25 +1347,18 @@ await Task.Run(() => WindowState = WindowState.Normal; } - private void PreloadPauseButton_Click(object sender, RoutedEventArgs e) + private async void PreloadPauseButton_Click(object sender, RoutedEventArgs e) { if(LegacyBoxActive) { return; } - if(download != null || httpclient.SessionState == MultisessionState.Downloading) + if(PreloadDownload) { PreloadPauseButton.IsEnabled = false; - if(download != null) - { - download.Pause(); - download = null; - } - else - { - token.Cancel(); - } + token.Cancel(); + await httpclient.WaitUntilInstanceDisposed(); Log("Pre-download paused"); PreloadDownload = false; PreloadPauseButton.IsEnabled = true; @@ -1493,7 +1408,6 @@ private void ServerDropdown_Changed(object sender, SelectionChangedEventArgs e) ServerDropdown.SelectedIndex = (int)Server; return; } - download = null; DownloadPaused = false; DeleteFile(GameArchiveTempPath); if(LocalVersionInfo.game_info.installed == false) @@ -1575,7 +1489,6 @@ private void MirrorDropdown_Changed(object sender, SelectionChangedEventArgs e) MirrorDropdown.SelectedIndex = (int)Mirror; return; } - download = null; DownloadPaused = false; DeleteFile(GameArchiveTempPath); if(LocalVersionInfo.game_info.installed == false) @@ -1623,7 +1536,7 @@ private void MainWindow_Closing(object sender, CancelEventArgs e) { if(Status == LauncherStatus.Downloading || Status == LauncherStatus.DownloadPaused || Status == LauncherStatus.Preloading) { - if(download == null && httpclient == null || httpclient.SessionState == MultisessionState.Idle) + if(httpclient == null || httpclient.DownloadState == DownloadState.Idle) { if(new DialogWindow(App.TextStrings["msgbox_abort_title"], $"{App.TextStrings["msgbox_abort_1_msg"]}\n{App.TextStrings["msgbox_abort_3_msg"]}", DialogWindow.DialogType.Question).ShowDialog() == false) { @@ -1632,18 +1545,14 @@ private void MainWindow_Closing(object sender, CancelEventArgs e) } else { - if(httpclient != null && httpclient.SessionState == MultisessionState.Merging) + if(httpclient != null && httpclient.DownloadState == DownloadState.Merging) { e.Cancel = true; return; } if(new DialogWindow(App.TextStrings["msgbox_abort_title"], $"{App.TextStrings["msgbox_abort_1_msg"]}\n{App.TextStrings["msgbox_abort_4_msg"]}", DialogWindow.DialogType.Question).ShowDialog() == true) { - if(download != null) - { - download.Pause(); - } - else if(httpclient != null && httpclient.SessionState == MultisessionState.Downloading || httpclient.SessionState == MultisessionState.CancelledDownloading) + if(httpclient != null && httpclient.DownloadState == DownloadState.Downloading || httpclient.DownloadState == DownloadState.CancelledDownloading) { try { diff --git a/Modules/ContextMenu.cs b/Modules/ContextMenu.cs index ea57048..98ccdd4 100644 --- a/Modules/ContextMenu.cs +++ b/Modules/ContextMenu.cs @@ -29,7 +29,7 @@ private void CM_DownloadCache_Click(object sender, RoutedEventArgs e) { return; } - if(Server != HI3Server.GLB && Server != HI3Server.SEA && Server != HI3Server.CN) + if(Mirror == HI3Mirror.Hi3Mirror && Server != HI3Server.GLB && Server != HI3Server.SEA && Server != HI3Server.CN) { new DialogWindow(App.TextStrings["contextmenu_download_cache"], App.TextStrings["msgbox_feature_not_available_msg"]).ShowDialog(); return; @@ -93,17 +93,23 @@ private void CM_DownloadCache_Click(object sender, RoutedEventArgs e) goto case 0; } - if(Mirror == HI3Mirror.miHoYo || Mirror == HI3Mirror.Hi3Mirror) + string dialog_message; + if(Mirror == HI3Mirror.miHoYo) { - if(new DialogWindow(App.TextStrings["contextmenu_download_cache"], string.Format(App.TextStrings["msgbox_download_cache_hi3mirror_msg"], OnlineVersionInfo.game_info.mirror.hi3mirror.maintainer.ToString()), DialogWindow.DialogType.Question).ShowDialog() == false) - { - return; - } - Status = LauncherStatus.CheckingUpdates; - Dispatcher.Invoke(() => { ProgressText.Text = App.TextStrings["progresstext_mirror_connect"]; }); - Log("Connecting to Hi3Mirror..."); - DownloadGameCache(game_language); + dialog_message = App.TextStrings["msgbox_download_cache_msg"]; + } + else + { + dialog_message = string.Format(App.TextStrings["msgbox_download_cache_hi3mirror_msg"], OnlineVersionInfo.game_info.mirror.hi3mirror.maintainer.ToString()); } + if(new DialogWindow(App.TextStrings["contextmenu_download_cache"], dialog_message, DialogWindow.DialogType.Question).ShowDialog() == false) + { + return; + } + Status = LauncherStatus.CheckingUpdates; + Dispatcher.Invoke(() => {ProgressText.Text = App.TextStrings["progresstext_fetching_hashes"];}); + Log("Fetching cache data..."); + DownloadGameCache(game_language); } private async Task CM_Repair_Click(object sender, RoutedEventArgs e) @@ -123,7 +129,7 @@ private async Task CM_Repair_Click(object sender, RoutedEventArgs e) } Status = LauncherStatus.CheckingUpdates; - Dispatcher.Invoke(() => { ProgressText.Text = App.TextStrings["progresstext_fetching_hashes"]; }); + Dispatcher.Invoke(() => {ProgressText.Text = App.TextStrings["progresstext_fetching_hashes"];}); Log("Fetching repair data..."); try { @@ -164,7 +170,7 @@ await Task.Run(() => { Status = LauncherStatus.Error; Log($"Failed to fetch repair data:\n{ex}", true, 1); - Dispatcher.Invoke(() => { new DialogWindow(App.TextStrings["msgbox_net_error_title"], string.Format(App.TextStrings["msgbox_net_error_msg"], ex.Message)).ShowDialog(); }); + Dispatcher.Invoke(() => {new DialogWindow(App.TextStrings["msgbox_net_error_title"], string.Format(App.TextStrings["msgbox_net_error_msg"], ex.Message)).ShowDialog();}); } Status = LauncherStatus.Ready; } @@ -295,7 +301,7 @@ await Task.Run(() => { Status = LauncherStatus.Error; Log($"Failed to move the game:\n{ex}", true, 1); - Dispatcher.Invoke(() => { new DialogWindow(App.TextStrings["msgbox_move_error_title"], App.TextStrings["msgbox_generic_error_msg"]).ShowDialog(); }); + Dispatcher.Invoke(() => {new DialogWindow(App.TextStrings["msgbox_move_error_title"], App.TextStrings["msgbox_generic_error_msg"]).ShowDialog();}); Status = LauncherStatus.Ready; } }); @@ -432,8 +438,7 @@ private void CM_CustomFPS_Click(object sender, RoutedEventArgs e) { key.DeleteValue(value); } - } - catch { } + }catch{} new DialogWindow(App.TextStrings["msgbox_registry_error_title"], $"{App.TextStrings["msgbox_registry_empty_1_msg"]}\n{App.TextStrings["msgbox_registry_empty_3_msg"]}").ShowDialog(); return; } @@ -494,8 +499,7 @@ private void CM_CustomResolution_Click(object sender, RoutedEventArgs e) { key.DeleteValue(value); } - } - catch { } + }catch{} new DialogWindow(App.TextStrings["msgbox_registry_error_title"], $"{App.TextStrings["msgbox_registry_empty_1_msg"]}\n{App.TextStrings["msgbox_registry_empty_3_msg"]}").ShowDialog(); return; } @@ -561,8 +565,7 @@ private void CM_CustomLaunchOptions_Click(object sender, RoutedEventArgs e) try { dialog.CustomLaunchOptionsTextBox.Text = LocalVersionInfo.launch_options.ToString().Trim(); - } - catch { } + }catch{} if(dialog.ShowDialog() == false) { return; @@ -604,7 +607,7 @@ private void CM_ResetDownloadType_Click(object sender, RoutedEventArgs e) try { var key = Registry.CurrentUser.OpenSubKey(GameRegistryPath, true); - string[] values = { "GENERAL_DATA_V2_ResourceDownloadType_h2238376574", "GENERAL_DATA_V2_ResourceDownloadVersion_h1528433916" }; + string[] values = {"GENERAL_DATA_V2_ResourceDownloadType_h2238376574", "GENERAL_DATA_V2_ResourceDownloadVersion_h1528433916"}; foreach(string value in values) { if(key == null || key.GetValue(value) == null || key.GetValueKind(value) != RegistryValueKind.DWord) @@ -615,8 +618,7 @@ private void CM_ResetDownloadType_Click(object sender, RoutedEventArgs e) { key.DeleteValue(value); } - } - catch { } + }catch{} } } var value_before = key.GetValue(values[0]); @@ -823,7 +825,7 @@ private void CM_Language_Click(object sender, RoutedEventArgs e) { if(lang == App.TextStrings["contextmenu_language_system"]) { - try { BpUtility.DeleteFromRegistry("Language"); } catch { } + try{BpUtility.DeleteFromRegistry("Language");} catch{} } else { @@ -1047,8 +1049,7 @@ private Tuple GetCustomBackgroundFileInfo(string path) Thread.Sleep(100); } BackgroundMedia.Source = old_source; - } - catch { } + }catch{} } } else diff --git a/Modules/GameCache.cs b/Modules/GameCache.cs index e247a50..5e95d8d 100644 --- a/Modules/GameCache.cs +++ b/Modules/GameCache.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json; +using AssetsTools.NET.Extra; +using Hi3Helper.EncTool; +using Newtonsoft.Json; using SharpCompress.Common; using System; using System.Collections.Generic; @@ -30,17 +32,26 @@ private string ReturnCacheTypeEnum(CacheType enumName) } } + private class CacheDataProperties + { + public string N {get; set;} + public long CS {get; set;} + public string CRC {get; set;} + public int DLM {get; set;} + public CacheType Type {get; set;} + } + /* * N -> Name of the necessary file * CRC -> Expected MD5 hash of the file * CS -> Size of the file * IsNecessary -> The file is necessary on "Updating settings" screen */ - private class CacheDataProperties + private class CacheDataPropertiesHi3Mirror { public string N {get; set;} - public string CRC {get; set;} public long CS {get; set;} + public string CRC {get; set;} public bool IsNecessary {get; set;} public CacheType Type {get; set;} } @@ -72,37 +83,86 @@ private byte FilterRegion(string input, string regionName) // Normalize Unix path (/) to Windows path (\) private string NormalizePath(string i) => i.Replace('/', '\\'); + private string GetPackageVersion(Stream stream) + { + var manager = new AssetsManager(); + var asset_bundle = manager.LoadBundleFile(stream, "."); + var assets = manager.LoadAssetsFileFromBundle(asset_bundle, 0); + var asset = assets.table.GetAssetInfo("PackageVersion"); + return manager.GetTypeInstance(assets, asset).GetBaseField().Get("m_Script").GetValue().AsString(); + } + + private string CalculateCRC(string path, string hash_salt) + { + byte[] salt = new mhyEncTool(hash_salt, OnlineVersionInfo.game_info.mirror.mihoyo.master_key.ToString()).GetSalt(); + using(FileStream stream = new FileStream(path, FileMode.Open)) + { + byte[] hash = new System.Security.Cryptography.HMACSHA1(salt).ComputeHash(stream); + return BitConverter.ToString(hash).Replace("-", string.Empty); + } + } + private async void DownloadGameCache(string game_language) { + string hash_salt = string.Empty; + string data_url; string data; - string path; - string data_url = OnlineVersionInfo.game_info.mirror.hi3mirror.game_cache.ToString(); string hi3mirror_api_url = OnlineVersionInfo.game_info.mirror.hi3mirror.api.ToString(); + int hi3mirror_server = 0; - List cache_files, bad_files; + List cache_files, bad_files; CacheType cache_type; var web_client = new BpWebClient(); try { - int server; - switch((int)Server) + if(Mirror == HI3Mirror.miHoYo) + { + switch((int)Server) + { + case 0: + data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.global.ToString(); + break; + case 1: + data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.os.ToString(); + break; + case 2: + data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.cn.ToString(); + break; + case 3: + data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.tw.ToString(); + break; + case 4: + data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.kr.ToString(); + break; + case 5: + data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.jp.ToString(); + break; + default: + throw new NotSupportedException("This server is not supported."); + } + } + else { - case 0: - server = 1; - break; - case 1: - server = 0; - break; - case 2: - server = 2; - break; - default: - throw new NotSupportedException("This server is not supported."); + data_url = OnlineVersionInfo.game_info.mirror.hi3mirror.game_cache.ToString(); + switch((int)Server) + { + case 0: + hi3mirror_server = 1; + break; + case 1: + hi3mirror_server = 0; + break; + case 2: + hi3mirror_server = 2; + break; + default: + throw new NotSupportedException("This server is not supported."); + } } - cache_files = new List(); - bad_files = new List(); + cache_files = new List(); + bad_files = new List(); await Task.Run(() => { @@ -128,27 +188,91 @@ await Task.Run(() => break; } - // Get URL and API data - var url = string.Format(hi3mirror_api_url, i, server); - data = web_client.DownloadString(url); + if(Mirror == HI3Mirror.miHoYo) + { + dynamic data_info; + switch((int)Server) + { + case 0: + data_info = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache_info.global[i].ToString(); + break; + case 1: + data_info = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache_info.os[i].ToString(); + break; + case 2: + data_info = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache_info.cn[i].ToString(); + break; + case 3: + data_info = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache_info.tw[i].ToString(); + break; + case 4: + data_info = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache_info.kr[i].ToString(); + break; + case 5: + data_info = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache_info.jp[i].ToString(); + break; + default: + throw new NotSupportedException("This server is not supported."); + } - // Do Elimination Process - // Deserialize string and make it to Object as List - foreach(CacheDataProperties file in JsonConvert.DeserializeObject>(data)) + using(var stream = new MemoryStream(web_client.DownloadData(new Uri(data_info)))) + { + using(var xor_stream = new XORStream(stream)) + { + var data_lines = GetPackageVersion(xor_stream).Split(new string[]{Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries); + var data_entries = new List(); + foreach(string line in data_lines) + { + try + { + var json = JsonConvert.DeserializeObject(line); + data_entries.Add(json); + }catch{} + } + if(cache_type == CacheType.Data) hash_salt = data_lines.FirstOrDefault(); + data = JsonConvert.SerializeObject(data_entries); + foreach(CacheDataProperties file in JsonConvert.DeserializeObject>(data)) + { + if(FilterRegion(file.N, game_language) > 0) + { + cache_files.Add(new CacheDataPropertiesHi3Mirror + { + N = file.N, + CRC = file.CRC, + CS = file.CS, + IsNecessary = file.DLM == 1, + Type = cache_type + }); + } + } + } + } + } + else { - // Do check whenever the file is included regional language as game_language defined - // Then add it to cache_files list - if(FilterRegion(file.N, game_language) > 0) + // Get URL and API data + var url = string.Format(hi3mirror_api_url, i, hi3mirror_server); + var api_data = JsonConvert.DeserializeObject(web_client.DownloadString(url)); + if(cache_type == CacheType.Data) hash_salt = api_data.HashSalt; + data = JsonConvert.SerializeObject(api_data.Content); + // Do Elimination Process + // Deserialize string and make it to Object as List + foreach(CacheDataPropertiesHi3Mirror file in JsonConvert.DeserializeObject>(data)) { - // Do add if the Filter passed. - cache_files.Add(new CacheDataProperties + // Do check whenever the file is included regional language as game_language defined + // Then add it to cache_files list + if(FilterRegion(file.N, game_language) > 0) { - N = file.N, - CRC = file.CRC, - CS = file.CS, - IsNecessary = file.IsNecessary, - Type = cache_type - }); + // Do add if the Filter passed. + cache_files.Add(new CacheDataPropertiesHi3Mirror + { + N = file.N, + CRC = file.CRC, + CS = file.CS, + IsNecessary = file.IsNecessary, + Type = cache_type + }); + } } } } @@ -166,6 +290,9 @@ await Task.Run(() => try { + string path; + string url; + Directory.CreateDirectory(GameCachePath); var existing_files = new DirectoryInfo(GameCachePath).GetFiles("*", SearchOption.AllDirectories).Where(x => x.DirectoryName.Contains(@"Data\data") || x.DirectoryName.Contains("Resources")).ToList(); var useless_files = existing_files; @@ -181,7 +308,7 @@ await Task.Run(() => { for(int i = 0; i < cache_files.Count; i++) { - var name = $"{NormalizePath(cache_files[i].N)}.unity3d"; + var name = $"{NormalizePath(cache_files[i].N)}_{cache_files[i].CRC}.unity3d"; // Combine Path and assign their own path // If none of them assigned as Unknown type, throw an exception. @@ -209,7 +336,7 @@ await Task.Run(() => if(file.Exists) { - if(BpUtility.CalculateMD5(file.FullName) == cache_files[i].CRC) + if(CalculateCRC(file.FullName, hash_salt) == cache_files[i].CRC) { if(App.AdvancedFeatures) Log($"File OK: {path}"); } @@ -280,33 +407,25 @@ await Task.Run(() => Log($"Finished verifying files, found corrupted/missing files: {bad_files.Count}"); if(new DialogWindow(App.TextStrings["contextmenu_download_cache"], string.Format(App.TextStrings["msgbox_repair_3_msg"], bad_files.Count, BpUtility.ToBytesCount(bad_files_size)), DialogWindow.DialogType.Question).ShowDialog() == true) { - string server; - switch((int)Server) - { - case 0: - server = "global"; - if(Mirror == HI3Mirror.miHoYo) data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.global.ToString(); - break; - case 1: - server = "sea"; - if(Mirror == HI3Mirror.miHoYo) data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.os.ToString(); - break; - case 2: - server = "cn"; - if(Mirror == HI3Mirror.miHoYo) data_url = OnlineVersionInfo.game_info.mirror.mihoyo.game_cache.cn.ToString(); - break; - default: - throw new NotSupportedException("This server is not supported."); - } - int downloaded_files = 0; - Status = LauncherStatus.Downloading; + + Status = LauncherStatus.Working; + ProgressBar.IsIndeterminate = false; + LaunchButton.IsEnabled = true; + LaunchButton.Content = App.TextStrings["button_cancel"]; await Task.Run(async () => { for(int i = 0; i < bad_files.Count; i++) { - path = $"{NormalizePath(bad_files[i].N)}.unity3d"; + if(ActionAbort) + { + Log("Task cancelled"); + ActionAbort = false; + break; + } + + path = $"{NormalizePath(bad_files[i].N)}_{bad_files[i].CRC}.unity3d"; switch(bad_files[i].Type) { case CacheType.Data: @@ -318,8 +437,29 @@ await Task.Run(async () => break; } - var url = string.Format(data_url, server, ReturnCacheTypeEnum(bad_files[i].Type), bad_files[i].N); - if(Mirror == HI3Mirror.miHoYo) url = string.Format(data_url, ReturnCacheTypeEnum(bad_files[i].Type), bad_files[i].N); + if(Mirror == HI3Mirror.miHoYo) + { + url = string.Format(data_url, ReturnCacheTypeEnum(bad_files[i].Type), $"{bad_files[i].N}_{bad_files[i].CRC}"); + } + else + { + string server; + switch((int)Server) + { + case 0: + server = "global"; + break; + case 1: + server = "sea"; + break; + case 2: + server = "cn"; + break; + default: + throw new NotSupportedException("This server is not supported."); + } + url = string.Format(data_url, server, ReturnCacheTypeEnum(bad_files[i].Type), $"{bad_files[i].N}_{bad_files[i].CRC}"); + } Log($"Downloading from {url}..."); Dispatcher.Invoke(() => { @@ -333,7 +473,7 @@ await Task.Run(async () => { Directory.CreateDirectory(Path.GetDirectoryName(path)); await web_client.DownloadFileTaskAsync(new Uri(url), path); - var md5 = BpUtility.CalculateMD5(path); + var md5 = CalculateCRC(path, hash_salt); if(File.Exists(path) && md5 != bad_files[i].CRC) { throw new CryptographicException("Verification failed"); @@ -346,7 +486,7 @@ await Task.Run(async () => } catch(Exception ex) { - Log($"Failed to download file [{bad_files[i].N}] ({url}): {ex.Message}", true, 1); + Log($"Failed to download file [{bad_files[i].N}_{bad_files[i].CRC}] ({url}): {ex.Message}", true, 1); } } }); @@ -394,7 +534,6 @@ await Task.Run(async () => new DialogWindow(App.TextStrings["contextmenu_download_cache"], App.TextStrings["msgbox_repair_2_msg"]).ShowDialog(); } Status = LauncherStatus.Ready; - } catch(Exception ex) { diff --git a/Modules/GameOnlineInfo.cs b/Modules/GameOnlineInfo.cs index ba50d89..43f6ee7 100644 --- a/Modules/GameOnlineInfo.cs +++ b/Modules/GameOnlineInfo.cs @@ -97,41 +97,7 @@ void Get(int timeout) GameVersionText.Text = $"{App.TextStrings["version"]}: {miHoYoVersionInfo.game.latest.version.ToString()}"; }); } - private DateTime FetchmiHoYoResourceVersionDateModified() - { - var url = new string[3]; - var time = new DateTime[3]; - switch(Server) - { - case HI3Server.GLB: - url[0] = OnlineVersionInfo.game_info.mirror.mihoyo.resource_version.global[0].ToString(); - url[1] = OnlineVersionInfo.game_info.mirror.mihoyo.resource_version.global[1].ToString(); - url[2] = OnlineVersionInfo.game_info.mirror.mihoyo.resource_version.global[2].ToString(); - break; - case HI3Server.SEA: - url[0] = OnlineVersionInfo.game_info.mirror.mihoyo.resource_version.os[0].ToString(); - url[1] = OnlineVersionInfo.game_info.mirror.mihoyo.resource_version.os[1].ToString(); - url[2] = OnlineVersionInfo.game_info.mirror.mihoyo.resource_version.os[2].ToString(); - break; - } - try - { - for(int i = 0; i < url.Length; i++) - { - var web_request = BpUtility.CreateWebRequest(url[i], "HEAD"); - using(var web_response = (HttpWebResponse)web_request.GetResponse()) - { - time[i] = web_response.LastModified.ToUniversalTime(); - } - } - Array.Sort(time); - return time[time.Length - 1]; - } - catch - { - return new DateTime(); - } - } + private dynamic FetchMediaFireFileMetadata(string id) { if(string.IsNullOrEmpty(id)) diff --git a/Modules/GameUpdate.cs b/Modules/GameUpdate.cs index eeacd8d..dc84091 100644 --- a/Modules/GameUpdate.cs +++ b/Modules/GameUpdate.cs @@ -256,10 +256,44 @@ private void DownloadBackgroundImage() switch(Server) { case HI3Server.GLB: - url = OnlineVersionInfo.game_info.mirror.mihoyo.launcher_content.global.ToString(); + string lang; + switch(App.LauncherLanguage) + { + case "de": + lang = "de-de"; + break; + case "fr": + lang = "fr-fr"; + break; + case "zh-CN": + lang = "zh-cn"; + break; + default: + lang = "en-us"; + break; + } + url = string.Format(OnlineVersionInfo.game_info.mirror.mihoyo.launcher_content.global.ToString(), lang); break; case HI3Server.SEA: - url = OnlineVersionInfo.game_info.mirror.mihoyo.launcher_content.os.ToString(); + switch(App.LauncherLanguage) + { + case "id": + lang = "id-id"; + break; + case "th": + lang = "th-th"; + break; + case "vn": + lang = "vi-vn"; + break; + case "zh-CN": + lang = "zh-cn"; + break; + default: + lang = "en-us"; + break; + } + url = string.Format(OnlineVersionInfo.game_info.mirror.mihoyo.launcher_content.os.ToString(), lang); break; case HI3Server.CN: url = OnlineVersionInfo.game_info.mirror.mihoyo.launcher_content.cn.ToString(); @@ -292,6 +326,7 @@ private void DownloadBackgroundImage() } else { + Log("Background image info is missing!", true, 2); BackgroundImageDownloading = false; return; } @@ -520,11 +555,10 @@ private async Task DownloadGameFile() if(!File.Exists(GameArchiveTempPath)) { Log($"Starting to download game archive: {title} ({url})"); - if(!App.UseLegacyDownload) + try { - try + using(httpclient = new Http(true, 5, 1000, App.UserAgent)) { - httpclient = new Http(); httpprop = new HttpProp(url, GameArchiveTempPath); token = new CancellationTokenSource(); httpclient.DownloadProgress += DownloadStatusChanged; @@ -536,80 +570,22 @@ private async Task DownloadGameFile() LaunchButton.IsEnabled = true; LaunchButton.Content = App.TextStrings["button_cancel"]; }); - await httpclient.DownloadMultisession(httpprop.URL, httpprop.Out, false, httpprop.Thread, token.Token); - await httpclient.MergeMultisession(httpprop.Out, httpprop.Thread, token.Token); + await httpclient.Download(httpprop.URL, httpprop.Out, httpprop.Thread, false, token.Token); + await httpclient.Merge(); httpclient.DownloadProgress -= DownloadStatusChanged; Log("Successfully downloaded game archive"); - Dispatcher.Invoke(() => - { - ProgressText.Text = string.Empty; - DownloadProgressBarStackPanel.Visibility = Visibility.Collapsed; - LaunchButton.Content = App.TextStrings["button_launch"]; - }); } - catch(OperationCanceledException) + Dispatcher.Invoke(() => { - httpclient.DownloadProgress -= DownloadStatusChanged; - return; - } + ProgressText.Text = string.Empty; + DownloadProgressBarStackPanel.Visibility = Visibility.Collapsed; + LaunchButton.Content = App.TextStrings["button_launch"]; + }); } - else + catch(OperationCanceledException) { - await Task.Run(() => - { - tracker.NewFile(); - var eta_calc = new ETACalculator(); - download = new DownloadPauseable(url, GameArchiveTempPath); - download.Start(); - Dispatcher.Invoke(() => - { - ProgressText.Text = string.Empty; - ProgressBar.Visibility = Visibility.Collapsed; - DownloadProgressBarStackPanel.Visibility = Visibility.Visible; - LaunchButton.IsEnabled = true; - LaunchButton.Content = App.TextStrings["button_cancel"]; - }); - while(download != null && !download.Done) - { - if(DownloadPaused) - { - continue; - } - size = download.ContentLength; - tracker.SetProgress(download.BytesWritten, download.ContentLength); - eta_calc.Update((float)download.BytesWritten / (float)download.ContentLength); - Dispatcher.Invoke(() => - { - var progress = tracker.GetProgress(); - DownloadProgressBar.Value = progress; - TaskbarItemInfo.ProgressValue = progress; - DownloadProgressText.Text = $"{string.Format(App.TextStrings["label_downloaded_1"], Math.Round(progress * 100))} ({BpUtility.ToBytesCount(download.BytesWritten)}/{BpUtility.ToBytesCount(download.ContentLength)})"; - DownloadETAText.Text = string.Format(App.TextStrings["progresstext_eta"], eta_calc.ETR.ToString("hh\\:mm\\:ss")); - DownloadSpeedText.Text = $"{App.TextStrings["label_download_speed"]} {tracker.GetBytesPerSecondString()}"; - }); - Thread.Sleep(500); - } - if(download == null) - { - abort = true; - } - if(abort) - { - return; - } - download = null; - Log("Successfully downloaded game archive"); - while(BpUtility.IsFileLocked(new FileInfo(GameArchiveTempPath))) - { - Thread.Sleep(10); - } - Dispatcher.Invoke(() => - { - ProgressText.Text = string.Empty; - DownloadProgressBarStackPanel.Visibility = Visibility.Collapsed; - LaunchButton.Content = App.TextStrings["button_launch"]; - }); - }); + httpclient.DownloadProgress -= DownloadStatusChanged; + return; } } diff --git a/Modules/LegacyBox.cs b/Modules/LegacyBox.cs index af6833c..f95f996 100644 --- a/Modules/LegacyBox.cs +++ b/Modules/LegacyBox.cs @@ -27,7 +27,7 @@ private void FPSInputBoxTextBox_Pasting(object sender, DataObjectPastingEventArg { bool IsTextAllowed(string text) { - return Array.TrueForAll(text.ToCharArray(), delegate (char c) { return char.IsDigit(c) || char.IsControl(c); }); + return Array.TrueForAll(text.ToCharArray(), delegate (char c) {return char.IsDigit(c) || char.IsControl(c);}); } if(e.DataObject.GetDataPresent(typeof(string))) @@ -114,7 +114,11 @@ await Task.Run(() => int repaired_files = 0; bool abort = false; - Status = LauncherStatus.Downloading; + Status = LauncherStatus.Working; + ProgressBar.IsIndeterminate = false; + LaunchButton.IsEnabled = true; + LaunchButton.Content = App.TextStrings["button_cancel"]; + await Task.Run(async () => { if(urls.Length == 0) @@ -125,6 +129,12 @@ await Task.Run(async () => { string path = Path.Combine(GameInstallPath, corrupted_files[i]); + if(ActionAbort) + { + Log("Task cancelled"); + ActionAbort = false; + break; + } Dispatcher.Invoke(() => { ProgressText.Text = string.Format(App.TextStrings["progresstext_downloading_file"], i + 1, corrupted_files.Count); @@ -154,7 +164,7 @@ await Task.Run(async () => Directory.CreateDirectory(Path.GetDirectoryName(path)); await PartialZipDownloader.DownloadFile(url, corrupted_files[i], path); - Dispatcher.Invoke(() => { ProgressText.Text = string.Format(App.TextStrings["progresstext_verifying_file"], i + 1, corrupted_files.Count); }); + Dispatcher.Invoke(() => {ProgressText.Text = string.Format(App.TextStrings["progresstext_verifying_file"], i + 1, corrupted_files.Count);}); if(!File.Exists(path) || BpUtility.CalculateMD5(path) != corrupted_file_hashes[i]) { Log($"Failed to repair file {corrupted_files[i]}", true, 1); @@ -308,7 +318,7 @@ async Task Generate() !x.Name.Contains("AUDIO_BGM") && !x.Name.Contains("AUDIO_Dialog") && !x.Name.Contains("AUDIO_DLC") && - !x.Name.Contains("AUDIO_EVENT") && + !x.Name.Contains("AUDIO_Event") && !x.Name.Contains("AUDIO_Ex") && !x.Name.Contains("AUDIO_HOT_FIX") && !x.Name.Contains("AUDIO_Main") && diff --git a/TextStrings_en.cs b/TextStrings_en.cs index 47109f4..f68b444 100644 --- a/TextStrings_en.cs +++ b/TextStrings_en.cs @@ -99,7 +99,6 @@ public static void TextStrings_English() TextStrings.Add("progresstext_fetching_hashes", "Fetching file hashes..."); TextStrings.Add("progresstext_generating_hash", "Generating hash for file {0}/{1}..."); TextStrings.Add("progresstext_initiating_download", "Initiating download..."); - TextStrings.Add("progresstext_mirror_connect", "Connecting to mirror..."); TextStrings.Add("progresstext_moving_files", "Moving game files..."); TextStrings.Add("progresstext_uninstalling", "Uninstalling the game..."); TextStrings.Add("progresstext_unpacking_1", "Unpacking game files..."); @@ -132,6 +131,7 @@ public static void TextStrings_English() TextStrings.Add("msgbox_custom_resolution_1_msg", "Height being greater than width is not recommended.\nContinue?"); TextStrings.Add("msgbox_custom_resolution_2_msg", "Resolution successfully set to {0}x{1} with fullscreen {2}."); TextStrings.Add("msgbox_custom_launch_options_msg", "Advanced users may enter custom launch options here.\nNote: they are applied independently per server."); + TextStrings.Add("msgbox_download_cache_msg", "The entire game cache will be checked and can be downloaded if needed. It can take a while.\nContinue?"); TextStrings.Add("msgbox_download_cache_hi3mirror_msg", "The entire game cache will be checked and can be downloaded if needed. It can take a while.\nHi3Mirror is a project courtesy of {0}.\nContinue?"); TextStrings.Add("msgbox_download_type_1_msg", "This will reset game's resource download type and allow you to reselect which assets to download.\nContinue?"); TextStrings.Add("msgbox_download_type_2_msg", "Download type has been reset."); diff --git a/Utility/BpUtility.cs b/Utility/BpUtility.cs index 32fc67d..12d6e18 100644 --- a/Utility/BpUtility.cs +++ b/Utility/BpUtility.cs @@ -201,6 +201,7 @@ public static bool IsFileLocked(FileInfo file) } return false; } + public static HttpWebRequest CreateWebRequest(string url, string method = "GET", int timeout = 10000) { var webRequest = (HttpWebRequest)WebRequest.Create(url); @@ -288,11 +289,9 @@ public string GetBytesPerSecondString() { double speed = GetBytesPerSecond(); string[] prefix; - switch(App.OSLanguage) + switch(App.LauncherLanguage) { - case "ru-RU": - case "uk-UA": - case "be-BY": + case "ru": prefix = new[]{"", "К", "М", "Г"}; break; default: diff --git a/Utility/DownloadProgressEvents.cs b/Utility/DownloadProgressEvents.cs index 097d123..a3223f2 100644 --- a/Utility/DownloadProgressEvents.cs +++ b/Utility/DownloadProgressEvents.cs @@ -20,7 +20,7 @@ private void DownloadStatusChanged(object sender, DownloadEvent e) DownloadProgressBar.Value = DownloadPercentage / 100; TaskbarItemInfo.ProgressValue = DownloadPercentage / 100; DownloadETAText.Text = string.Format(App.TextStrings["progresstext_eta"], string.Format("{0:hh\\:mm\\:ss}", e.TimeLeft)); - if (e.State == MultisessionState.Merging) + if (e.State == DownloadState.Merging) { DownloadProgressText.Text = $"{string.Format(App.TextStrings["label_merged"], DownloadPercentage)} ({BpUtility.ToBytesCount(e.SizeDownloaded)}/{BpUtility.ToBytesCount(e.SizeToBeDownloaded)})"; DownloadSpeedText.Text = $"{App.TextStrings["label_merge_speed"]} {BpUtility.ToBytesCount(e.Speed)}{App.TextStrings["bytes_per_second"].Substring(1)}"; @@ -47,8 +47,8 @@ private void PreloadDownloadStatusChanged(object sender, DownloadEvent e) PreloadStatusTopRightText.Text = $"{BpUtility.ToBytesCount(e.SizeDownloaded)}/{BpUtility.ToBytesCount(e.SizeToBeDownloaded)}"; PreloadStatusMiddleRightText.Text = string.Format("{0:hh\\:mm\\:ss}", e.TimeLeft); PreloadStatusBottomRightText.Text = $"{BpUtility.ToBytesCount(e.Speed)}{App.TextStrings["bytes_per_second"].Substring(1)}"; - if (e.State == MultisessionState.Merging) - { + if (e.State == DownloadState.Merging) + { PreloadPauseButton.IsEnabled = false; PreloadBottomText.Text = string.Format(App.TextStrings["label_merged"], DownloadPercentage); PreloadStatusTopLeftText.Text = $"{App.TextStrings["label_merged"].Split(' ')[0]}:"; diff --git a/Utility/Hi3Helper.EncTool/XORFileStream.cs b/Utility/Hi3Helper.EncTool/XORFileStream.cs new file mode 100644 index 0000000..424a852 --- /dev/null +++ b/Utility/Hi3Helper.EncTool/XORFileStream.cs @@ -0,0 +1,110 @@ +using System; +using System.IO; + +namespace Hi3Helper.EncTool +{ + public class XORStream : Stream + { + private protected readonly Stream _stream; + private protected readonly byte _xorKey; + + public XORStream(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare = FileShare.Read, FileOptions fileOptions = FileOptions.None, byte XORKey = 0xA5) + : base() + { + _xorKey = XORKey; + _stream = new FileStream(path, fileMode, fileAccess, fileShare, 4 << 10, fileOptions); + } + + public XORStream(Stream stream, byte XORKey = 0xA5) + : base() + { + _xorKey = XORKey; + _stream = stream; + } + + public XORStream(byte XORKey = 0xA5) + : base() + { + _xorKey = XORKey; + _stream = new MemoryStream(); + } + + ~XORStream() => Dispose(true); + + private void WriteBytes(byte[] buffer, int offset, int count) + { + for(int i = 0; i < buffer.Length; i++) + { + buffer[i] ^= _xorKey; + } + + _stream.Write(buffer, offset, count); + } + + private int ReadBytes(byte[] buffer, int offset, int count) + { + int i = _stream.Read(buffer, offset, count); + ReadXOR(buffer, i); + return i; + } + + public override int Read(byte[] buffer, int offset, int count) => ReadBytes(buffer, offset, count); + + public override void Write(byte[] buffer, int offset, int count) => WriteBytes(buffer, offset, count); + + public override bool CanRead + { + get {return true;} + } + + public override bool CanSeek + { + get {return true;} + } + + public override bool CanWrite + { + get {return false;} + } + + public override void Flush() + { + _stream.Flush(); + } + + public override long Length + { + get {return _stream.Length;} + } + + public override long Position + { + get {return _stream.Position;} + set {_stream.Position = value;} + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _stream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _stream.SetLength(value); + } + + private void ReadXOR(Span buffer, int i) + { + for(int j = 0; j < i; j++) + { + buffer[j] ^= _xorKey; + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _stream.Dispose(); + } + } +} \ No newline at end of file diff --git a/Utility/Hi3Helper.EncTool/mhyEncTool.cs b/Utility/Hi3Helper.EncTool/mhyEncTool.cs new file mode 100644 index 0000000..8d09c14 --- /dev/null +++ b/Utility/Hi3Helper.EncTool/mhyEncTool.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Hi3Helper.EncTool +{ + public class mhyEncTool + { + private RSA _ooh; + private string _778; + private string _MasterKey; + + public mhyEncTool(string _i, string MasterKey) + { + _778 = _i; + _ooh = RSA.Create(); + _MasterKey = MasterKey; + } + + private readonly Dictionary __951 = new Dictionary() + { + {'a', 0xA},{'b', 0xB},{'c', 0xC},{'d', 0xD}, + {'e', 0xE},{'f', 0xF},{'A', 0xA},{'B', 0xB}, + {'C', 0xC},{'D', 0xD},{'E', 0xE},{'F', 0xF}, + {'0', 0x0},{'1', 0x1},{'2', 0x2},{'3', 0x3}, + {'4', 0x4},{'5', 0x5},{'6', 0x6},{'7', 0x7}, + {'8', 0x8},{'9', 0x9} + }; + + private readonly byte[] sKey = new byte[12] + { + 232, 170, 135, 231, + 189, 170, 227, 130, + 134, 227, 129, 132 + }; + + public byte[] GetSalt() + { + byte[] cy_a = default(byte[]); + byte[] hR = default(byte[]); + byte[] _0041 = default(byte[]); + byte[] at = default(byte[]); + while(true) + { + int num = -379967069; + while(true) + { + uint num2; + switch((num2 = (uint)num ^ 0xD91CA180u) % 8u) + { + case 0u: + break; + case 3u: + cy_a = new byte[8]; + num = (int)((num2 * 265579718) ^ 0x2AB6A14D); + continue; + case 2u: + num = (int)((num2 * 1194515468) ^ 0x6BE031F1); + continue; + case 5u: + hR = cy_a; + num = ((int)num2 * -1066675674) ^ 0xD7A97C; + continue; + case 6u: + FromXmlStringA(in _ooh, Encoding.UTF8.GetString(_0041)); + num = (int)(num2 * 1342003605) ^ -355963254; + continue; + case 4u: + Array.Copy(_ooh.Decrypt(at, RSAEncryptionPadding.Pkcs1), 48, cy_a, 0, 8); + num = ((int)num2 * -1711149688) ^ -181350819; + continue; + case 7u: + _0041 = _f8j51(_MasterKey); + at = HTb(_778); + num = ((int)num2 * -1995578406) ^ 0x57CB1D8; + continue; + default: + return hR; + } + break; + } + } + } + + private void FromXmlStringA(in RSA rsa, string xmlString = null) + { + if(string.IsNullOrEmpty(xmlString)) xmlString = _778; + rsa.FromXmlString(xmlString); + } + + private byte[] HTb(string _a) + { + byte[] _p49 = new byte[_a.Length / 2]; + bool f = default(bool); + int n_94 = default(int); + int _001 = default(int); + while(true) + { + int kk1 = -1675277297; + while(true) + { + uint lo_051; + switch((lo_051 = (uint)kk1 ^ 0x8D7A7B5Fu) % 9u) + { + case 2u: + break; + case 5u: + { + int _0051; + if(!f) + { + _0051 = -1380326733; + } + else + { + _0051 = -189009401; + } + kk1 = _0051 ^ (int)(lo_051 * 2073484105); + continue; + } + case 3u: + n_94 = 0; + kk1 = (int)((lo_051 * 2059650746) ^ 0x70BDC4E2); + continue; + case 8u: + { + char c = _a[n_94]; + char c2 = _a[n_94 + 1]; + _p49[_001] = (byte)((__951[c] << 4) | __951[c2]); + kk1 = (int)((lo_051 * 1839013216) ^ 0x5C090E6D); + continue; + } + case 1u: + _001 = 0; + kk1 = (int)((lo_051 * 316152874) ^ 0x4220ABE6); + continue; + case 6u: + f = n_94 < _a.Length; + kk1 = -7386813; + continue; + case 0u: + n_94 += 2; + _001++; + kk1 = (int)((lo_051 * 196220108) ^ 0x5B6DC890); + continue; + case 7u: + kk1 = -742839246; + continue; + default: + return _p49; + } + break; + } + } + } + + private byte[] _f8j51(string c) + { + byte[] ar_84 = new byte[c.Length / 2]; + int _445 = default(int); + int nud_e = default(int); + byte[] r = default(byte[]); + while(true) + { + int _Kj9a = -415293042; + while(true) + { + uint _99Jm1; + switch((_99Jm1 = (uint)_Kj9a ^ 0xD88AD053u) % 8u) + { + case 3u: + break; + case 5u: + _445 = 0; + nud_e = 0; + _Kj9a = ((int)_99Jm1 * -1420181188) ^ 0x2336EBD; + continue; + case 2u: + _Kj9a = ((int)_99Jm1 * -336838899) ^ -1483897312; + continue; + case 0u: + nud_e++; + _Kj9a = (int)(_99Jm1 * 1698348363) ^ -1786813222; + continue; + case 4u: + r = ar_84; + _Kj9a = (int)(_99Jm1 * 1245963323) ^ -23762559; + continue; + case 1u: + { + if(_445 >= c.Length) + { + _Kj9a = -481382921; + } + else + { + _Kj9a = -1714879556; + } + continue; + } + case 7u: + { + byte b = (byte)((__951[c[_445]] << 4) | __951[c[_445 + 1]]); + ar_84[nud_e] = (byte)(b ^ sKey[nud_e % sKey.Length]); + _445 += 2; + _Kj9a = -1908881005; + continue; + } + default: + return r; + } + break; + } + } + } + } +} \ No newline at end of file diff --git a/Utility/Hi3Helper.Http b/Utility/Hi3Helper.Http index ea777b8..184e7ee 160000 --- a/Utility/Hi3Helper.Http +++ b/Utility/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit ea777b8e30887606032bea03b8f6f5dea69b5eac +Subproject commit 184e7eef91ba9587bf71c5055b136fcb198788c6