diff --git a/ADB.cs b/ADB.cs index cba9e6e..4387fd4 100644 --- a/ADB.cs +++ b/ADB.cs @@ -1,8 +1,14 @@ -using AndroidSideloader.Utilities; +using AdvancedSharpAdbClient; +using AdvancedSharpAdbClient.DeviceCommands; +using AdvancedSharpAdbClient.Models; +using AdvancedSharpAdbClient.Receivers; +using AndroidSideloader.Utilities; using JR.Utils.GUI.Forms; using System; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Threading.Tasks; using System.Windows.Forms; namespace AndroidSideloader @@ -10,32 +16,82 @@ namespace AndroidSideloader internal class ADB { private static readonly SettingsManager settings = SettingsManager.Instance; - private static readonly Process adb = new Process(); public static string adbFolderPath = Path.Combine(Environment.CurrentDirectory, "platform-tools"); public static string adbFilePath = Path.Combine(adbFolderPath, "adb.exe"); public static string DeviceID = ""; public static string package = ""; + public static bool wirelessadbON; + + // AdbClient for direct protocol communication + private static AdbClient _adbClient; + private static DeviceData _currentDevice; + + // Gets or initializes the AdbClient instance + private static AdbClient GetAdbClient() + { + if (_adbClient == null) + { + // Ensure ADB server is started + if (!AdbServer.Instance.GetStatus().IsRunning) + { + var server = new AdbServer(); + var result = server.StartServer(adbFilePath, false); + Logger.Log($"ADB server start result: {result}"); + } + + _adbClient = new AdbClient(); + } + return _adbClient; + } + + // Gets the current device for AdbClient operations + private static DeviceData GetCurrentDevice() + { + var client = GetAdbClient(); + var devices = client.GetDevices(); + + if (devices == null || !devices.Any()) + { + Logger.Log("No devices found via AdbClient", LogLevel.WARNING); + return default; + } + + // If DeviceID is set, find that specific device + if (!string.IsNullOrEmpty(DeviceID) && DeviceID.Length > 1) + { + var device = devices.FirstOrDefault(d => d.Serial == DeviceID || d.Serial.StartsWith(DeviceID)); + if (device.Serial != null) + { + _currentDevice = device; + return device; + } + } + + // Otherwise return the first available device + _currentDevice = devices.First(); + return _currentDevice; + } + public static ProcessOutput RunAdbCommandToString(string command) { - // Replacing "adb" from command if the user added it command = command.Replace("adb", ""); settings.ADBFolder = adbFolderPath; settings.ADBPath = adbFilePath; settings.Save(); + if (DeviceID.Length > 1) { command = $" -s {DeviceID} {command}"; } + if (!command.Contains("dumpsys") && !command.Contains("shell pm list packages") && !command.Contains("KEYCODE_WAKEUP")) { string logcmd = command; - if (logcmd.Contains(Environment.CurrentDirectory)) { logcmd = logcmd.Replace($"{Environment.CurrentDirectory}", $"CurrentDirectory"); } - _ = Logger.Log($"Running command: {logcmd}"); } @@ -88,6 +144,264 @@ namespace AndroidSideloader } } + // Executes a shell command on the device. + private static void ExecuteShellCommand(AdbClient client, DeviceData device, string command) + { + var receiver = new ConsoleOutputReceiver(); + client.ExecuteRemoteCommand(command, device, receiver); + } + + // Copies and installs an APK with real-time progress reporting using AdvancedSharpAdbClient + public static async Task SideloadWithProgressAsync( + string path, + Action progressCallback = null, + Action statusCallback = null, + string packagename = "", + string gameName = "") + { + statusCallback?.Invoke("Installing APK..."); + progressCallback?.Invoke(0); + + try + { + var device = GetCurrentDevice(); + if (device.Serial == null) + { + return new ProcessOutput("", "No device connected"); + } + + var client = GetAdbClient(); + var packageManager = new PackageManager(client, device); + + statusCallback?.Invoke("Installing APK..."); + + // Create install progress handler + Action installProgress = (args) => + { + // Map PackageInstallProgressState to percentage + int percent = 0; + switch (args.State) + { + case PackageInstallProgressState.Preparing: + percent = 0; + statusCallback?.Invoke("Preparing..."); + break; + case PackageInstallProgressState.Uploading: + percent = (int)Math.Round(args.UploadProgress); + statusCallback?.Invoke($"Installing · {args.UploadProgress:F0}%"); + break; + case PackageInstallProgressState.Installing: + percent = 100; + statusCallback?.Invoke("Completing Installation..."); + break; + case PackageInstallProgressState.Finished: + percent = 100; + statusCallback?.Invoke(""); + break; + default: + percent = 50; + break; + } + progressCallback?.Invoke(percent); + }; + + // Install the package with progress + await Task.Run(() => + { + packageManager.InstallPackage(path, installProgress); + }); + + progressCallback?.Invoke(100); + statusCallback?.Invoke(""); + + return new ProcessOutput($"{gameName}: Success\n"); + } + catch (Exception ex) + { + Logger.Log($"SideloadWithProgressAsync error: {ex.Message}", LogLevel.ERROR); + + // Check for signature mismatch errors + if (ex.Message.Contains("INSTALL_FAILED") || + ex.Message.Contains("signatures do not match")) + { + bool cancelClicked = false; + + if (!settings.AutoReinstall) + { + Program.form.Invoke(() => + { + DialogResult dialogResult1 = FlexibleMessageBox.Show(Program.form, + "In place upgrade has failed. Rookie can attempt to backup your save data and reinstall the game automatically, however some games do not store their saves in an accessible location (less than 5%). Continue with reinstall?", + "In place upgrade failed.", MessageBoxButtons.OKCancel); + if (dialogResult1 == DialogResult.Cancel) + cancelClicked = true; + }); + } + + if (cancelClicked) + return new ProcessOutput("", "Installation cancelled by user"); + + // Perform reinstall + statusCallback?.Invoke("Performing reinstall..."); + + try + { + var device = GetCurrentDevice(); + var client = GetAdbClient(); + var packageManager = new PackageManager(client, device); + + // Backup save data + statusCallback?.Invoke("Backing up save data..."); + _ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\""); + + // Uninstall + statusCallback?.Invoke("Uninstalling old version..."); + packageManager.UninstallPackage(packagename); + + // Reinstall with progress + statusCallback?.Invoke("Reinstalling game..."); + Action reinstallProgress = (args) => + { + if (args.State == PackageInstallProgressState.Uploading) + { + progressCallback?.Invoke((int)Math.Round(args.UploadProgress)); + } + }; + packageManager.InstallPackage(path, reinstallProgress); + + // Restore save data + statusCallback?.Invoke("Restoring save data..."); + _ = RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{MainForm.CurrPCKG}\" /sdcard/Android/data/"); + + string directoryToDelete = Path.Combine(Environment.CurrentDirectory, MainForm.CurrPCKG); + if (Directory.Exists(directoryToDelete) && directoryToDelete != Environment.CurrentDirectory) + { + Directory.Delete(directoryToDelete, true); + } + + progressCallback?.Invoke(100); + return new ProcessOutput($"{gameName}: Reinstall: Success\n", ""); + } + catch (Exception reinstallEx) + { + return new ProcessOutput($"{gameName}: Reinstall: Failed: {reinstallEx.Message}\n"); + } + } + + return new ProcessOutput("", ex.Message); + } + } + + // Copies OBB folder with real-time progress reporting using AdvancedSharpAdbClient + public static async Task CopyOBBWithProgressAsync( + string localPath, + Action progressCallback = null, + Action statusCallback = null, + string gameName = "") + { + string folderName = Path.GetFileName(localPath); + + if (!folderName.Contains(".")) + { + return new ProcessOutput("No OBB Folder found"); + } + + try + { + var device = GetCurrentDevice(); + if (device.Serial == null) + { + return new ProcessOutput("", "No device connected"); + } + + var client = GetAdbClient(); + string remotePath = $"/sdcard/Android/obb/{folderName}"; + + statusCallback?.Invoke($"Preparing: {folderName}"); + progressCallback?.Invoke(0); + + // Delete existing OBB folder and create new one + ExecuteShellCommand(client, device, $"rm -rf \"{remotePath}\""); + ExecuteShellCommand(client, device, $"mkdir -p \"{remotePath}\""); + + // Get all files to push and calculate total size + var files = Directory.GetFiles(localPath, "*", SearchOption.AllDirectories); + long totalBytes = files.Sum(f => new FileInfo(f).Length); + long transferredBytes = 0; + + statusCallback?.Invoke($"Copying: {folderName}"); + + using (var syncService = new SyncService(client, device)) + { + foreach (var file in files) + { + string relativePath = file.Substring(localPath.Length) + .TrimStart('\\', '/') + .Replace('\\', '/'); + string remoteFilePath = $"{remotePath}/{relativePath}"; + string fileName = Path.GetFileName(file); + + // Let UI know which file we're currently on + statusCallback?.Invoke(fileName); + + // Ensure remote directory exists + string remoteDir = remoteFilePath.Substring(0, remoteFilePath.LastIndexOf('/')); + ExecuteShellCommand(client, device, $"mkdir -p \"{remoteDir}\""); + + var fileInfo = new FileInfo(file); + long fileSize = fileInfo.Length; + long capturedTransferredBytes = transferredBytes; + + // Progress handler for this file + Action progressHandler = (args) => + { + long totalProgressBytes = capturedTransferredBytes + args.ReceivedBytesSize; + + double overallPercent = totalBytes > 0 + ? (totalProgressBytes * 100.0) / totalBytes + : 0.0; + + int overallPercentInt = (int)Math.Round(overallPercent); + overallPercentInt = Math.Max(0, Math.Min(100, overallPercentInt)); + + // Single source of truth for UI (bar + label + text) + progressCallback?.Invoke(overallPercentInt); + }; + + // Push the file with progress + using (var stream = File.OpenRead(file)) + { + await Task.Run(() => + { + syncService.Push( + stream, + remoteFilePath, + UnixFileStatus.DefaultFileMode, + DateTime.Now, + progressHandler, + false); + }); + } + + // Mark this file as fully transferred + transferredBytes += fileSize; + } + } + + // Ensure final 100% and clear status + progressCallback?.Invoke(100); + statusCallback?.Invoke(""); + + return new ProcessOutput($"{gameName}: OBB transfer: Success\n", ""); + } + catch (Exception ex) + { + Logger.Log($"CopyOBBWithProgressAsync error: {ex.Message}", LogLevel.ERROR); + + return new ProcessOutput("", $"{gameName}: OBB transfer: Failed: {ex.Message}\n"); + } + } + public static ProcessOutput RunAdbCommandToStringWOADB(string result, string path) { string command = result; @@ -99,54 +413,51 @@ namespace AndroidSideloader _ = Logger.Log($"Running command: {logcmd}"); - adb.StartInfo.FileName = "cmd.exe"; - adb.StartInfo.RedirectStandardError = true; - adb.StartInfo.RedirectStandardInput = true; - adb.StartInfo.RedirectStandardOutput = true; - adb.StartInfo.CreateNoWindow = true; - adb.StartInfo.UseShellExecute = false; - adb.StartInfo.WorkingDirectory = Path.GetDirectoryName(path); - _ = adb.Start(); - adb.StandardInput.WriteLine(command); - adb.StandardInput.Flush(); - adb.StandardInput.Close(); - - - string output = ""; - string error = ""; - - try + using (var adb = new Process()) { - output += adb.StandardOutput.ReadToEnd(); - error += adb.StandardError.ReadToEnd(); - } - catch { } - if (command.Contains("connect")) - { - bool graceful = adb.WaitForExit(3000); - if (!graceful) + adb.StartInfo.FileName = "cmd.exe"; + adb.StartInfo.RedirectStandardError = true; + adb.StartInfo.RedirectStandardInput = true; + adb.StartInfo.RedirectStandardOutput = true; + adb.StartInfo.CreateNoWindow = true; + adb.StartInfo.UseShellExecute = false; + adb.StartInfo.WorkingDirectory = Path.GetDirectoryName(path); + _ = adb.Start(); + adb.StandardInput.WriteLine(command); + adb.StandardInput.Flush(); + adb.StandardInput.Close(); + + string output = ""; + string error = ""; + + try { - adb.Kill(); - adb.WaitForExit(); + output += adb.StandardOutput.ReadToEnd(); + error += adb.StandardError.ReadToEnd(); } - } - else if (command.Contains("connect")) - { - bool graceful = adb.WaitForExit(3000); - if (!graceful) + catch { } + + if (command.Contains("connect")) { - adb.Kill(); - adb.WaitForExit(); + bool graceful = adb.WaitForExit(3000); + if (!graceful) + { + adb.Kill(); + adb.WaitForExit(); + } } + + if (error.Contains("ADB_VENDOR_KEYS") && settings.AdbDebugWarned) + { + ADBDebugWarning(); + } + + _ = Logger.Log(output); + _ = Logger.Log(error, LogLevel.ERROR); + return new ProcessOutput(output, error); } - if (error.Contains("ADB_VENDOR_KEYS") && settings.AdbDebugWarned) - { - ADBDebugWarning(); - } - _ = Logger.Log(output); - _ = Logger.Log(error, LogLevel.ERROR); - return new ProcessOutput(output, error); } + public static ProcessOutput RunCommandToString(string result, string path = "") { string command = result; @@ -160,37 +471,37 @@ namespace AndroidSideloader try { - using (var adb = new Process()) + using (var proc = new Process()) { - adb.StartInfo.FileName = $@"{Path.GetPathRoot(Environment.SystemDirectory)}\Windows\System32\cmd.exe"; - adb.StartInfo.Arguments = command; - adb.StartInfo.RedirectStandardError = true; - adb.StartInfo.RedirectStandardInput = true; - adb.StartInfo.RedirectStandardOutput = true; - adb.StartInfo.CreateNoWindow = true; - adb.StartInfo.UseShellExecute = false; - adb.StartInfo.WorkingDirectory = Path.GetDirectoryName(path); + proc.StartInfo.FileName = $@"{Path.GetPathRoot(Environment.SystemDirectory)}\Windows\System32\cmd.exe"; + proc.StartInfo.Arguments = command; + proc.StartInfo.RedirectStandardError = true; + proc.StartInfo.RedirectStandardInput = true; + proc.StartInfo.RedirectStandardOutput = true; + proc.StartInfo.CreateNoWindow = true; + proc.StartInfo.UseShellExecute = false; + proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(path); - adb.Start(); - adb.StandardInput.WriteLine(command); - adb.StandardInput.Flush(); - adb.StandardInput.Close(); + proc.Start(); + proc.StandardInput.WriteLine(command); + proc.StandardInput.Flush(); + proc.StandardInput.Close(); - string output = adb.StandardOutput.ReadToEnd(); - string error = adb.StandardError.ReadToEnd(); + string output = proc.StandardOutput.ReadToEnd(); + string error = proc.StandardError.ReadToEnd(); if (command.Contains("connect")) { - bool graceful = adb.WaitForExit(3000); + bool graceful = proc.WaitForExit(3000); if (!graceful) { - adb.Kill(); - adb.WaitForExit(); + proc.Kill(); + proc.WaitForExit(); } } else { - adb.WaitForExit(); + proc.WaitForExit(); } if (error.Contains("ADB_VENDOR_KEYS") && settings.AdbDebugWarned) @@ -198,12 +509,6 @@ namespace AndroidSideloader ADBDebugWarning(); } - if (error.Contains("Asset path") && error.Contains("is neither a directory nor file")) - { - Logger.Log("Asset path error detected. The specified path might not exist or be accessible.", LogLevel.WARNING); - // You might want to handle this specific error differently - } - Logger.Log(output); Logger.Log(error, LogLevel.ERROR); @@ -221,10 +526,11 @@ namespace AndroidSideloader { Program.form.Invoke(() => { - DialogResult dialogResult = FlexibleMessageBox.Show(Program.form, "On your headset, click on the Notifications Bell, and then select the USB Detected notification to enable Connections.", "ADB Debugging not enabled.", MessageBoxButtons.OKCancel); + DialogResult dialogResult = FlexibleMessageBox.Show(Program.form, + "On your headset, click on the Notifications Bell, and then select the USB Detected notification to enable Connections.", + "ADB Debugging not enabled.", MessageBoxButtons.OKCancel); if (dialogResult == DialogResult.Cancel) { - // settings.adbdebugwarned = true; settings.Save(); } }); @@ -234,6 +540,20 @@ namespace AndroidSideloader { ProcessOutput output = new ProcessOutput("", ""); output += RunAdbCommandToString($"shell pm uninstall {package}"); + + // Prefix the output with the simple game name + string label = Sideloader.gameNameToSimpleName(Sideloader.PackageNametoGameName(package)); + + if (!string.IsNullOrEmpty(output.Output)) + { + output.Output = $"{label}: {output.Output}"; + } + + if (!string.IsNullOrEmpty(output.Error)) + { + output.Error = $"{label}: {output.Error}"; + } + return output; } @@ -255,7 +575,7 @@ namespace AndroidSideloader totalSize = long.Parse(foo[1]) / 1000; usedSize = long.Parse(foo[2]) / 1000; freeSize = long.Parse(foo[3]) / 1000; - break; // Assuming we only need the first matching line + break; } } } @@ -263,51 +583,52 @@ namespace AndroidSideloader return $"Total space: {string.Format("{0:0.00}", (double)totalSize / 1000)}GB\nUsed space: {string.Format("{0:0.00}", (double)usedSize / 1000)}GB\nFree space: {string.Format("{0:0.00}", (double)freeSize / 1000)}GB"; } - public static bool wirelessadbON; public static ProcessOutput Sideload(string path, string packagename = "") { ProcessOutput ret = new ProcessOutput(); ret += RunAdbCommandToString($"install -g \"{path}\""); string out2 = ret.Output + ret.Error; + if (out2.Contains("failed")) { _ = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), $"Rookie Backups"); _ = Logger.Log(out2); + if (out2.Contains("offline") && !settings.NodeviceMode) { DialogResult dialogResult2 = FlexibleMessageBox.Show(Program.form, "Device is offline. Press Yes to reconnect, or if you don't wish to connect and just want to download the game (requires unchecking \"Delete games after install\" from settings menu) then press No.", "Device offline.", MessageBoxButtons.YesNoCancel); } + if (out2.Contains($"signatures do not match previously") || out2.Contains("INSTALL_FAILED_VERSION_DOWNGRADE") || out2.Contains("signatures do not match") || out2.Contains("failed to install")) { ret.Error = string.Empty; ret.Output = string.Empty; + + bool cancelClicked = false; + if (!settings.AutoReinstall) { - bool cancelClicked = false; - - if (!settings.AutoReinstall) + Program.form.Invoke((MethodInvoker)(() => { - Program.form.Invoke((MethodInvoker)(() => - { - DialogResult dialogResult1 = FlexibleMessageBox.Show(Program.form, "In place upgrade has failed. Rookie can attempt to backup your save data and reinstall the game automatically, however some games do not store their saves in an accessible location (less than 5%). Continue with reinstall?", "In place upgrade failed.", MessageBoxButtons.OKCancel); - if (dialogResult1 == DialogResult.Cancel) - cancelClicked = true; - })); - } - - if (cancelClicked) - return ret; + DialogResult dialogResult1 = FlexibleMessageBox.Show(Program.form, "In place upgrade has failed. Rookie can attempt to backup your save data and reinstall the game automatically, however some games do not store their saves in an accessible location (less than 5%). Continue with reinstall?", "In place upgrade failed.", MessageBoxButtons.OKCancel); + if (dialogResult1 == DialogResult.Cancel) + cancelClicked = true; + })); } + if (cancelClicked) + return ret; + Program.form.changeTitle("Performing reinstall, please wait..."); - _ = ADB.RunAdbCommandToString("kill-server"); - _ = ADB.RunAdbCommandToString("devices"); - _ = ADB.RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\""); + _ = RunAdbCommandToString("kill-server"); + _ = RunAdbCommandToString("devices"); + _ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\""); Program.form.changeTitle("Uninstalling game..."); _ = Sideloader.UninstallGame(MainForm.CurrPCKG); Program.form.changeTitle("Reinstalling game..."); - ret += ADB.RunAdbCommandToString($"install -g \"{path}\""); - _ = ADB.RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{MainForm.CurrPCKG}\" /sdcard/Android/data/"); + ret += RunAdbCommandToString($"install -g \"{path}\""); + _ = RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{MainForm.CurrPCKG}\" /sdcard/Android/data/"); + string directoryToDelete = Path.Combine(Environment.CurrentDirectory, MainForm.CurrPCKG); if (Directory.Exists(directoryToDelete)) { @@ -335,4 +656,4 @@ namespace AndroidSideloader : new ProcessOutput("No OBB Folder found"); } } -} +} \ No newline at end of file diff --git a/AndroidSideloader.csproj b/AndroidSideloader.csproj index f135480..2086f73 100644 --- a/AndroidSideloader.csproj +++ b/AndroidSideloader.csproj @@ -136,6 +136,9 @@ AndroidSideloader_TemporaryKey.pfx + + packages\AdvancedSharpAdbClient.3.5.15\lib\net45\AdvancedSharpAdbClient.dll + packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll @@ -187,6 +190,9 @@ Component + + Component + True True diff --git a/DonorsListView.cs b/DonorsListView.cs index 56843f8..55d2d6f 100644 --- a/DonorsListView.cs +++ b/DonorsListView.cs @@ -305,7 +305,9 @@ namespace AndroidSideloader File.WriteAllText(blacklistPath, Newtonsoft.Json.JsonConvert.SerializeObject(existingBlacklist.ToArray(), Newtonsoft.Json.Formatting.Indented)); Logger.Log($"Added {appsToBlacklist.Count} apps to local blacklist"); - MessageBox.Show($"{appsToBlacklist.Count} app(s) added to blacklist.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show($"{appsToBlacklist.Count} {(appsToBlacklist.Count == 1 ? "app" : "apps")} added to blacklist.", + "Success", + MessageBoxButtons.OK, MessageBoxIcon.Information); Close(); } catch (Exception ex) diff --git a/MainForm.Designer.cs b/MainForm.Designer.cs index 2e7735f..d39582e 100644 --- a/MainForm.Designer.cs +++ b/MainForm.Designer.cs @@ -34,7 +34,7 @@ namespace AndroidSideloader { this.components = new System.ComponentModel.Container(); this.m_combo = new SergeUtils.EasyCompletionComboBox(); - this.progressBar = new System.Windows.Forms.ProgressBar(); + this.progressBar = new AndroidSideloader.ModernProgressBar(); this.speedLabel = new System.Windows.Forms.Label(); this.etaLabel = new System.Windows.Forms.Label(); this.freeDisclaimer = new System.Windows.Forms.Label(); @@ -117,9 +117,9 @@ namespace AndroidSideloader this.leftNavContainer = new System.Windows.Forms.Panel(); this.statusInfoPanel = new System.Windows.Forms.Panel(); this.sideloadingStatusLabel = new System.Windows.Forms.Label(); - this.rookieStatusLabel = new System.Windows.Forms.Label(); this.activeMirrorLabel = new System.Windows.Forms.Label(); this.deviceIdLabel = new System.Windows.Forms.Label(); + this.rookieStatusLabel = new System.Windows.Forms.Label(); this.sidebarMediaPanel = new System.Windows.Forms.Panel(); this.downloadInstallGameButton = new AndroidSideloader.RoundButton(); this.selectedGameLabel = new System.Windows.Forms.Label(); @@ -176,14 +176,26 @@ namespace AndroidSideloader // this.progressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.progressBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.progressBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(35)))), ((int)(((byte)(45))))); + this.progressBar.BackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(32)))), ((int)(((byte)(38))))); + this.progressBar.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); this.progressBar.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173))))); + this.progressBar.IndeterminateColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173))))); + this.progressBar.IsIndeterminate = false; this.progressBar.Location = new System.Drawing.Point(1, 18); + this.progressBar.Maximum = 100; + this.progressBar.Minimum = 0; this.progressBar.MinimumSize = new System.Drawing.Size(200, 13); this.progressBar.Name = "progressBar"; + this.progressBar.OperationType = ""; + this.progressBar.ProgressEndColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(160)))), ((int)(((byte)(130))))); + this.progressBar.ProgressStartColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(220)))), ((int)(((byte)(190))))); + this.progressBar.Radius = 6; this.progressBar.Size = new System.Drawing.Size(983, 13); - this.progressBar.Style = System.Windows.Forms.ProgressBarStyle.Continuous; + this.progressBar.StatusText = ""; this.progressBar.TabIndex = 7; + this.progressBar.TextColor = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(230)))), ((int)(((byte)(230))))); + this.progressBar.Value = 0; // // speedLabel // @@ -1200,7 +1212,6 @@ namespace AndroidSideloader this.statusInfoPanel.Controls.Add(this.activeMirrorLabel); this.statusInfoPanel.Controls.Add(this.deviceIdLabel); this.statusInfoPanel.Controls.Add(this.rookieStatusLabel); - this.statusInfoPanel.AutoSize = false; this.statusInfoPanel.Dock = System.Windows.Forms.DockStyle.Bottom; this.statusInfoPanel.Location = new System.Drawing.Point(0, 1019); this.statusInfoPanel.Name = "statusInfoPanel"; @@ -1208,58 +1219,54 @@ namespace AndroidSideloader this.statusInfoPanel.Size = new System.Drawing.Size(233, 81); this.statusInfoPanel.TabIndex = 102; // - // rookieStatusLabel - // - this.rookieStatusLabel.AutoSize = false; - this.rookieStatusLabel.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Bold); - this.rookieStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173))))); - this.rookieStatusLabel.Location = new System.Drawing.Point(8, 4); - this.rookieStatusLabel.Name = "rookieStatusLabel"; - this.rookieStatusLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); - this.rookieStatusLabel.Size = new System.Drawing.Size(225, 17); - this.rookieStatusLabel.AutoEllipsis = true; - this.rookieStatusLabel.TabIndex = 0; - this.rookieStatusLabel.Text = "Status"; - // - // deviceIdLabel - // - this.deviceIdLabel.AutoSize = false; - this.deviceIdLabel.Font = new System.Drawing.Font("Segoe UI", 8F); - this.deviceIdLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(145)))), ((int)(((byte)(150))))); - this.deviceIdLabel.Location = new System.Drawing.Point(8, 21); - this.deviceIdLabel.Name = "deviceIdLabel"; - this.deviceIdLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); - this.deviceIdLabel.Size = new System.Drawing.Size(225, 17); - this.deviceIdLabel.AutoEllipsis = true; - this.deviceIdLabel.TabIndex = 1; - this.deviceIdLabel.Text = "Device: Not connected"; - // - // activeMirrorLabel - // - this.activeMirrorLabel.AutoSize = false; - this.activeMirrorLabel.Font = new System.Drawing.Font("Segoe UI", 8F); - this.activeMirrorLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(145)))), ((int)(((byte)(150))))); - this.activeMirrorLabel.Location = new System.Drawing.Point(8, 38); - this.activeMirrorLabel.Name = "activeMirrorLabel"; - this.activeMirrorLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); - this.activeMirrorLabel.Size = new System.Drawing.Size(225, 17); - this.activeMirrorLabel.AutoEllipsis = true; - this.activeMirrorLabel.TabIndex = 2; - this.activeMirrorLabel.Text = "Mirror: None"; - // // sideloadingStatusLabel // - this.sideloadingStatusLabel.AutoSize = false; + this.sideloadingStatusLabel.AutoEllipsis = true; this.sideloadingStatusLabel.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Bold); this.sideloadingStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173))))); this.sideloadingStatusLabel.Location = new System.Drawing.Point(8, 55); this.sideloadingStatusLabel.Name = "sideloadingStatusLabel"; this.sideloadingStatusLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); this.sideloadingStatusLabel.Size = new System.Drawing.Size(225, 17); - this.sideloadingStatusLabel.AutoEllipsis = true; this.sideloadingStatusLabel.TabIndex = 3; this.sideloadingStatusLabel.Text = "Sideloading: Enabled"; // + // activeMirrorLabel + // + this.activeMirrorLabel.AutoEllipsis = true; + this.activeMirrorLabel.Font = new System.Drawing.Font("Segoe UI", 8F); + this.activeMirrorLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(145)))), ((int)(((byte)(150))))); + this.activeMirrorLabel.Location = new System.Drawing.Point(8, 38); + this.activeMirrorLabel.Name = "activeMirrorLabel"; + this.activeMirrorLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); + this.activeMirrorLabel.Size = new System.Drawing.Size(225, 17); + this.activeMirrorLabel.TabIndex = 2; + this.activeMirrorLabel.Text = "Mirror: None"; + // + // deviceIdLabel + // + this.deviceIdLabel.AutoEllipsis = true; + this.deviceIdLabel.Font = new System.Drawing.Font("Segoe UI", 8F); + this.deviceIdLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(145)))), ((int)(((byte)(150))))); + this.deviceIdLabel.Location = new System.Drawing.Point(8, 21); + this.deviceIdLabel.Name = "deviceIdLabel"; + this.deviceIdLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); + this.deviceIdLabel.Size = new System.Drawing.Size(225, 17); + this.deviceIdLabel.TabIndex = 1; + this.deviceIdLabel.Text = "Device: Not connected"; + // + // rookieStatusLabel + // + this.rookieStatusLabel.AutoEllipsis = true; + this.rookieStatusLabel.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Bold); + this.rookieStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173))))); + this.rookieStatusLabel.Location = new System.Drawing.Point(8, 4); + this.rookieStatusLabel.Name = "rookieStatusLabel"; + this.rookieStatusLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2); + this.rookieStatusLabel.Size = new System.Drawing.Size(225, 17); + this.rookieStatusLabel.TabIndex = 0; + this.rookieStatusLabel.Text = "Status"; + // // sidebarMediaPanel // this.sidebarMediaPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(20)))), ((int)(((byte)(24)))), ((int)(((byte)(29))))); @@ -1600,7 +1607,6 @@ namespace AndroidSideloader this.HelpButton = true; this.MinimumSize = new System.Drawing.Size(1048, 760); this.Name = "MainForm"; - this.ShowIcon = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Rookie Sideloader"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing); @@ -1623,7 +1629,6 @@ namespace AndroidSideloader this.leftNavContainer.ResumeLayout(false); this.leftNavContainer.PerformLayout(); this.statusInfoPanel.ResumeLayout(false); - this.statusInfoPanel.PerformLayout(); this.sidebarMediaPanel.ResumeLayout(false); this.tableLayoutPanel1.ResumeLayout(false); this.searchPanel.ResumeLayout(false); @@ -1638,7 +1643,7 @@ namespace AndroidSideloader #endregion private SergeUtils.EasyCompletionComboBox m_combo; - private System.Windows.Forms.ProgressBar progressBar; + private ModernProgressBar progressBar; private System.Windows.Forms.Label etaLabel; private System.Windows.Forms.Label speedLabel; private System.Windows.Forms.Label freeDisclaimer; diff --git a/MainForm.cs b/MainForm.cs index f34f296..570a5e5 100755 --- a/MainForm.cs +++ b/MainForm.cs @@ -105,6 +105,9 @@ namespace AndroidSideloader InitializeTimeReferences(); CheckCommandLineArguments(); + // Use same icon as the executable + this.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); + // Load user's preferred view from settings isGalleryView = settings.UseGalleryView; @@ -597,7 +600,8 @@ namespace AndroidSideloader btnNoDevice.Text = "ENABLE SIDELOADING"; } - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Loading"; // Update check if (!debugMode && settings.CheckForUpdates && !isOffline) @@ -729,7 +733,8 @@ namespace AndroidSideloader await Task.WhenAll(tasksToWait); } - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Loading"; changeTitle("Populating Game List..."); _ = await CheckForDevice(); @@ -950,27 +955,34 @@ namespace AndroidSideloader if (dialog.Show(Handle)) { - progressBar.Style = ProgressBarStyle.Marquee; string path = dialog.FileName; - changeTitle($"Copying {path} OBB to device..."); - Thread t1 = new Thread(() => - { - output += output += ADB.CopyOBB(path); - }) - { - IsBackground = true - }; - t1.Start(); + string folderName = Path.GetFileName(path); - while (t1.IsAlive) - { - await Task.Delay(100); - } + changeTitle($"Copying {folderName} OBB to device..."); + progressBar.IsIndeterminate = false; + progressBar.Value = 0; + progressBar.OperationType = "Copying OBB"; + + output = await ADB.CopyOBBWithProgressAsync( + path, + progress => this.Invoke(() => { + progressBar.Value = progress; + speedLabel.Text = $"Progress: {progress}%"; + }), + status => this.Invoke(() => { + progressBar.StatusText = status; + etaLabel.Text = status; + }), + folderName); + + progressBar.Value = 100; changeTitle("Done."); showAvailableSpace(); ShowPrcOutput(output); changeTitle(""); + speedLabel.Text = ""; + etaLabel.Text = ""; } } @@ -1356,7 +1368,8 @@ namespace AndroidSideloader if (!isworking) { isworking = true; - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Loading"; string HWID = SideloaderUtilities.UUID(); string GameName = selectedApp; string packageName = Sideloader.gameNameToPackageName(GameName); @@ -1418,7 +1431,7 @@ namespace AndroidSideloader changeTitle("Zipping extracted application..."); string cmd = $"7z a -mx1 \"{gameZipName}\" .\\{packageName}\\*"; string path = $"{settings.MainDir}\\7z.exe"; - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; Thread t4 = new Thread(() => { _ = ADB.RunCommandToString(cmd, path); @@ -1513,7 +1526,8 @@ namespace AndroidSideloader Sideloader.BackupGame(packagename); } ProcessOutput output = new ProcessOutput("", ""); - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Loading"; Thread t1 = new Thread(() => { output += Sideloader.UninstallGame(packagename); @@ -1527,7 +1541,7 @@ namespace AndroidSideloader ShowPrcOutput(output); showAvailableSpace(); - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; } private async void copyBulkObbButton_Click(object sender, EventArgs e) @@ -1576,7 +1590,8 @@ namespace AndroidSideloader DragDropLbl.Visible = false; ProcessOutput output = new ProcessOutput(String.Empty, String.Empty); ADB.DeviceID = GetDeviceID(); - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Loading"; CurrPCKG = String.Empty; string[] datas = (string[])e.Data.GetData(DataFormats.FileDrop); foreach (string data in datas) @@ -1928,7 +1943,7 @@ namespace AndroidSideloader } } - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; showAvailableSpace(); @@ -2050,7 +2065,8 @@ namespace AndroidSideloader if (SideloaderRCLONE.games.Count > 5) { - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = ""; // Use full dumpsys to get all version codes at once Dictionary installedVersions = new Dictionary(packageList.Length, StringComparer.OrdinalIgnoreCase); @@ -2257,7 +2273,7 @@ namespace AndroidSideloader await ProcessNewApps(newGamesList, blacklistSet.ToList()); } - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; if (either && !updatesNotified && !noAppCheck) { @@ -2572,7 +2588,8 @@ namespace AndroidSideloader public async Task extractAndPrepareGameToUploadAsync(string GameName, string packagename, ulong installedVersionInt, bool isupdate) { - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = ""; Thread t1 = new Thread(() => { @@ -2604,7 +2621,7 @@ namespace AndroidSideloader string HWID = SideloaderUtilities.UUID(); File.WriteAllText($"{settings.MainDir}\\{packagename}\\HWID.txt", HWID); - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; UploadGame game = new UploadGame { isUpdate = isupdate, @@ -2905,7 +2922,8 @@ Additional Thanks & Resources if (isLoading) { return; } isLoading = true; - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Refreshing"; devicesbutton_Click(sender, e); await initMirrors(); @@ -2919,7 +2937,8 @@ Additional Thanks & Resources changeTitle(titleMessage); if (isLoading) { return; } isLoading = true; - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Refreshing"; Thread t1 = new Thread(() => { @@ -3030,7 +3049,14 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g public void SetProgress(int progress) { - progressBar.Value = progress; + if (progressBar.InvokeRequired) + { + progressBar.Invoke(new Action(() => progressBar.Value = progress)); + } + else + { + progressBar.Value = progress; + } } public bool isinstalling = false; @@ -3051,10 +3077,11 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g showAvailableSpace(); listAppsBtn(); } - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Downloading"; if (gamesListView.SelectedItems.Count == 0) { - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; changeTitle("You must select a game from the game list!"); return; } @@ -3077,7 +3104,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g } progressBar.Value = 0; - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; string game = gamesToDownload.Length == 1 ? $"\"{gamesToDownload[0]}\"" : "the selected games"; isinstalling = true; //Add games to the queue @@ -3279,7 +3306,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g // Logger.Log("Files: " + transfersComplete.ToString() + "/" + fileCount.ToString() + " (" + Convert.ToInt32((downloadedSize / totalSize) * 100).ToString() + "% Complete)"); // Logger.Log("Downloaded: " + downloadedSize.ToString() + " of " + totalSize.ToString()); - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; progressBar.Value = Convert.ToInt32((downloadedSize / totalSize) * 100); TimeSpan time = TimeSpan.FromSeconds(globalEta); @@ -3367,18 +3394,18 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g if (UsingPublicConfig && otherError == false && gameDownloadOutput.Output != "Download skipped.") { - Thread extractionThread = new Thread(() => { Invoke(new Action(() => { speedLabel.Text = "Extracting..."; etaLabel.Text = "Please wait..."; - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; progressBar.Value = 0; isInDownloadExtract = true; })); try { + progressBar.OperationType = "Extracting"; changeTitle("Extracting " + gameName); Zip.ExtractFile($"{settings.DownloadDir}\\{gameNameHash}\\{gameNameHash}.7z.001", $"{settings.DownloadDir}", PublicConfigFile.Password); changeTitle(""); @@ -3415,7 +3442,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g ADB.DeviceID = GetDeviceID(); quotaTries = 0; progressBar.Value = 0; - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; changeTitle("Installing game APK " + gameName); etaLabel.Text = "ETA: Wait for install..."; speedLabel.Text = "DLS: Finished"; @@ -3474,41 +3501,90 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g }; t.Tick += new EventHandler(timer_Tick4); t.Start(); - Thread apkThread = new Thread(() => - { - changeTitle($"Sideloading APK..."); - etaLabel.Text = "Sideloading APK..."; - output += ADB.Sideload(apkFile, packagename); - }) - { - IsBackground = true - }; - apkThread.Start(); - while (apkThread.IsAlive) - { - await Task.Delay(100); - } + + changeTitle($"Sideloading APK..."); + etaLabel.Text = "Installing APK..."; + progressBar.IsIndeterminate = false; + progressBar.OperationType = "Installing"; + progressBar.Value = 0; + + // Use async method with progress + output += await ADB.SideloadWithProgressAsync( + apkFile, + progress => this.Invoke(() => { + if (progress == 0) + { + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Installing"; + } + else + { + progressBar.IsIndeterminate = false; + progressBar.Value = progress; + } + }), + status => this.Invoke(() => { + progressBar.StatusText = status; + etaLabel.Text = status; + }), + packagename, + Sideloader.gameNameToSimpleName(gameName)); + t.Stop(); + progressBar.IsIndeterminate = false; Debug.WriteLine(wrDelimiter); if (Directory.Exists($"{settings.DownloadDir}\\{gameName}\\{packagename}")) { deleteOBB(packagename); - Thread obbThread = new Thread(() => - { - changeTitle($"Copying {packagename} OBB to device..."); - ADB.RunAdbCommandToString($"shell mkdir \"/sdcard/Android/obb/{packagename}\""); - output += ADB.RunAdbCommandToString($"push \"{settings.DownloadDir}\\{gameName}\\{packagename}\" \"/sdcard/Android/obb\""); - changeTitle(""); - }) - { - IsBackground = true - }; - obbThread.Start(); - while (obbThread.IsAlive) - { - await Task.Delay(100); - } + + changeTitle($"Copying {packagename} OBB to device..."); + etaLabel.Text = "Copying OBB..."; + progressBar.Value = 0; + progressBar.OperationType = "Copying OBB"; + + // Use async method with progress for OBB + string currentObbStatusBase = string.Empty; // phase or filename + + output += await ADB.CopyOBBWithProgressAsync( + $"{settings.DownloadDir}\\{gameName}\\{packagename}", + progress => this.Invoke(() => + { + progressBar.Value = progress; + speedLabel.Text = $"OBB: {progress}%"; + + if (!string.IsNullOrEmpty(currentObbStatusBase)) + { + if (currentObbStatusBase.StartsWith("Preparing:", StringComparison.OrdinalIgnoreCase) || + currentObbStatusBase.StartsWith("Copying:", StringComparison.OrdinalIgnoreCase)) + { + progressBar.StatusText = currentObbStatusBase; + } + else + { + // "filename · 73%" + progressBar.StatusText = $"{currentObbStatusBase} · {progress}%"; + } + } + else + { + // Fallback: just show the numeric percent in the bar + progressBar.StatusText = $"{progress}%"; + } + }), + status => this.Invoke(() => + { + currentObbStatusBase = status ?? string.Empty; + if (currentObbStatusBase.StartsWith("Preparing:", StringComparison.OrdinalIgnoreCase) || + currentObbStatusBase.StartsWith("Copying:", StringComparison.OrdinalIgnoreCase)) + { + progressBar.StatusText = currentObbStatusBase; + } + }), + Sideloader.gameNameToSimpleName(gameName)); + + changeTitle(""); + if (!nodeviceonstart | DeviceConnected) { if (!output.Output.Contains("offline")) @@ -3564,7 +3640,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g { ShowPrcOutput(output); } - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; etaLabel.Text = "ETA: Finished Queue"; speedLabel.Text = "DLS: Finished Queue"; gamesAreDownloading = false; @@ -3686,7 +3762,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g { ShowPrcOutput(output); } - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; etaLabel.Text = "ETA: Finished Queue"; speedLabel.Text = "DLS: Finished Queue"; gamesAreDownloading = false; @@ -3849,7 +3925,8 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g { ADB.wirelessadbON = false; changeTitle("Disabling wireless ADB..."); - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = ""; await Task.Run(() => { @@ -3866,7 +3943,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g try { File.Delete(storedIpPath); } catch { } } - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; _ = await CheckForDevice(); changeTitlebarToDevice(); changeTitle("Wireless ADB disabled.", true); @@ -3917,12 +3994,13 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g // Connect to the device changeTitle($"Connecting to {ipAddress}..."); - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = ""; string ipCommand = $"connect {ipAddress}:5555"; string connectResult = await Task.Run(() => ADB.RunAdbCommandToString(ipCommand).Output); - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; if (connectResult.Contains("cannot resolve host") || connectResult.Contains("cannot connect to") || @@ -4090,7 +4168,8 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g } changeTitle("Scanning network for ADB devices..."); - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = ""; // Scan common IP range (1-254) on port 5555 var tasks = new List>(); @@ -4104,7 +4183,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g var results = await Task.WhenAll(tasks); foundDevices.AddRange(results.Where(r => !string.IsNullOrEmpty(r))); - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; changeTitle(""); return foundDevices; @@ -5100,7 +5179,8 @@ function onYouTubeIframeAPIReady() { if (!isworking) { isworking = true; - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = "Loading"; string HWID = SideloaderUtilities.UUID(); string GameName = selectedApp; string packageName = Sideloader.gameNameToPackageName(GameName); @@ -5179,7 +5259,7 @@ function onYouTubeIframeAPIReady() { Directory.Delete($"{settings.MainDir}\\{packageName}", true); isworking = false; changeTitle(""); - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; _ = FlexibleMessageBox.Show(Program.form, $"{GameName} pulled to:\n\n{GameName} v{VersionInt} {packageName}.zip\n\nOn your desktop!"); } } @@ -6894,7 +6974,8 @@ function onYouTubeIframeAPIReady() { // Perform uninstall ProcessOutput output = new ProcessOutput("", ""); - progressBar.Style = ProgressBarStyle.Marquee; + progressBar.IsIndeterminate = true; + progressBar.OperationType = ""; await Task.Run(() => { output += Sideloader.UninstallGame(packageName); @@ -6902,7 +6983,7 @@ function onYouTubeIframeAPIReady() { ShowPrcOutput(output); showAvailableSpace(); - progressBar.Style = ProgressBarStyle.Continuous; + progressBar.IsIndeterminate = false; // Remove from combo box for (int i = 0; i < m_combo.Items.Count; i++) diff --git a/ModernProgessBar.cs b/ModernProgessBar.cs new file mode 100644 index 0000000..1eb4b4a --- /dev/null +++ b/ModernProgessBar.cs @@ -0,0 +1,438 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace AndroidSideloader +{ + // A modern progress bar with rounded corners, left-to-right gradient fill, + // animated indeterminate mode, and optional status text overlay + [Description("Modern Themed Progress Bar")] + public class ModernProgressBar : Control + { + #region Fields + + private int _value; + private int _minimum; + private int _maximum = 100; + private int _radius = 8; + private bool _isIndeterminate; + private string _statusText = string.Empty; + private string _operationType = string.Empty; + + // Indeterminate animation + private readonly Timer _animationTimer; + private float _animationOffset; + private const float AnimationSpeed = 4f; + private const int IndeterminateBlockWidth = 80; + + // Colors + private Color _backgroundColor = Color.FromArgb(28, 32, 38); + private Color _progressStartColor = Color.FromArgb(120, 220, 190); // lighter accent + private Color _progressEndColor = Color.FromArgb(50, 160, 130); // darker accent + private Color _indeterminateColor = Color.FromArgb(93, 203, 173); // accent + private Color _textColor = Color.FromArgb(230, 230, 230); + private Color _textShadowColor = Color.FromArgb(90, 0, 0, 0); + + #endregion + + #region Constructor + + public ModernProgressBar() + { + SetStyle( + ControlStyles.AllPaintingInWmPaint | + ControlStyles.OptimizedDoubleBuffer | + ControlStyles.ResizeRedraw | + ControlStyles.UserPaint | + ControlStyles.SupportsTransparentBackColor, + true); + + BackColor = Color.Transparent; + + // Size + Font + Height = 28; + Width = 220; + Font = new Font("Segoe UI", 9f, FontStyle.Bold); + + _animationTimer = new Timer { Interval = 16 }; // ~60fps + _animationTimer.Tick += AnimationTimer_Tick; + } + + #endregion + + #region Properties + + [Category("Progress")] + [Description("The current value of the progress bar.")] + public int Value + { + get => _value; + set + { + _value = Math.Max(_minimum, Math.Min(_maximum, value)); + Invalidate(); + } + } + + [Category("Progress")] + [Description("The minimum value of the progress bar.")] + public int Minimum + { + get => _minimum; + set + { + _minimum = value; + if (_value < _minimum) _value = _minimum; + Invalidate(); + } + } + + [Category("Progress")] + [Description("The maximum value of the progress bar.")] + public int Maximum + { + get => _maximum; + set + { + _maximum = value; + if (_value > _maximum) _value = _maximum; + Invalidate(); + } + } + + [Category("Appearance")] + [Description("The corner radius of the progress bar.")] + public int Radius + { + get => _radius; + set + { + _radius = Math.Max(0, value); + Invalidate(); + } + } + + [Category("Progress")] + [Description("Whether the progress bar shows indeterminate (marquee) progress.")] + public bool IsIndeterminate + { + get => _isIndeterminate; + set + { + // If there is no change, do nothing + if (_isIndeterminate == value) + return; + + _isIndeterminate = value; + if (_isIndeterminate) + { + _animationOffset = -IndeterminateBlockWidth; + _animationTimer.Start(); + } + else + { + _animationTimer.Stop(); + } + Invalidate(); + } + } + + [Category("Appearance")] + [Description("Optional status text to display on the progress bar.")] + public string StatusText + { + get => _statusText; + set + { + _statusText = value ?? string.Empty; + Invalidate(); + } + } + + [Category("Appearance")] + [Description("Operation type label (e.g., 'Downloading', 'Installing').")] + public string OperationType + { + get => _operationType; + set + { + _operationType = value ?? string.Empty; + Invalidate(); + } + } + + [Category("Appearance")] + [Description("Background color of the progress bar track.")] + public Color BackgroundColor + { + get => _backgroundColor; + set { _backgroundColor = value; Invalidate(); } + } + + [Category("Appearance")] + [Description("Start color of the progress gradient (left side).")] + public Color ProgressStartColor + { + get => _progressStartColor; + set { _progressStartColor = value; Invalidate(); } + } + + [Category("Appearance")] + [Description("End color of the progress gradient (right side).")] + public Color ProgressEndColor + { + get => _progressEndColor; + set { _progressEndColor = value; Invalidate(); } + } + + [Category("Appearance")] + [Description("Color used for indeterminate animation.")] + public Color IndeterminateColor + { + get => _indeterminateColor; + set { _indeterminateColor = value; Invalidate(); } + } + + [Category("Appearance")] + [Description("Text color for status overlay.")] + public Color TextColor + { + get => _textColor; + set { _textColor = value; Invalidate(); } + } + + // Gets the progress as a percentage (0-100) + public float ProgressPercent => + _maximum > _minimum ? (float)(_value - _minimum) / (_maximum - _minimum) * 100f : 0f; + + #endregion + + #region Painting + + protected override void OnPaint(PaintEventArgs e) + { + var g = e.Graphics; + g.SmoothingMode = SmoothingMode.AntiAlias; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.Clear(BackColor); + + int w = ClientSize.Width; + int h = ClientSize.Height; + if (w <= 0 || h <= 0) return; + + var outerRect = new Rectangle(0, 0, w - 1, h - 1); + + // Draw background track + using (var path = CreateRoundedPath(outerRect, _radius)) + using (var bgBrush = new SolidBrush(_backgroundColor)) + { + g.FillPath(bgBrush, path); + } + + // Draw progress or indeterminate animation + if (_isIndeterminate) + { + DrawIndeterminate(g, outerRect); + } + else if (_value > _minimum) + { + DrawProgress(g, outerRect); + } + + // Draw text overlay + DrawTextOverlay(g, outerRect); + + base.OnPaint(e); + } + + private void DrawProgress(Graphics g, Rectangle outerRect) + { + float percent = (_maximum > _minimum) + ? (float)(_value - _minimum) / (_maximum - _minimum) + : 0f; + + if (percent <= 0f) return; + if (percent > 1f) percent = 1f; + + int progressWidth = (int)Math.Round(outerRect.Width * percent); + if (progressWidth <= 0) return; + if (progressWidth > outerRect.Width) progressWidth = outerRect.Width; + + using (var outerPath = CreateRoundedPath(outerRect, _radius)) + { + // Clip to progress area inside rounded track + Rectangle progressRect = new Rectangle(outerRect.X, outerRect.Y, progressWidth, outerRect.Height); + using (var progressClip = new Region(progressRect)) + using (var trackRegion = new Region(outerPath)) + { + trackRegion.Intersect(progressClip); + + Region prevClip = g.Clip; + try + { + g.SetClip(trackRegion, CombineMode.Replace); + + // Left-to-right gradient, based on accent color + using (var gradientBrush = new LinearGradientBrush( + progressRect, + _progressStartColor, + _progressEndColor, + LinearGradientMode.Horizontal)) + { + g.FillPath(gradientBrush, outerPath); + } + } + finally + { + g.Clip = prevClip; + } + } + } + } + + private void DrawIndeterminate(Graphics g, Rectangle outerRect) + { + using (var outerPath = CreateRoundedPath(outerRect, _radius)) + { + Region prevClip = g.Clip; + try + { + g.SetClip(outerPath, CombineMode.Replace); + + int blockWidth = Math.Min(IndeterminateBlockWidth, outerRect.Width); + int blockX = (int)_animationOffset; + var blockRect = new Rectangle(blockX, outerRect.Y, blockWidth, outerRect.Height); + + // Solid bar with slight left-to-right gradient + using (var brush = new LinearGradientBrush( + blockRect, + ControlPaint.Light(_indeterminateColor, 0.1f), + ControlPaint.Dark(_indeterminateColor, 0.1f), + LinearGradientMode.Horizontal)) + { + g.FillRectangle(brush, blockRect); + } + } + finally + { + g.Clip = prevClip; + } + } + } + + private void DrawTextOverlay(Graphics g, Rectangle outerRect) + { + string displayText = BuildDisplayText(); + if (string.IsNullOrEmpty(displayText)) return; + + using (var sf = new StringFormat + { + Alignment = StringAlignment.Center, + LineAlignment = StringAlignment.Center, + Trimming = StringTrimming.EllipsisCharacter + }) + { + // Slight shadow for legibility on accent background + var shadowRect = new Rectangle(outerRect.X + 1, outerRect.Y + 1, outerRect.Width, outerRect.Height); + using (var shadowBrush = new SolidBrush(_textShadowColor)) + { + g.DrawString(displayText, Font, shadowBrush, shadowRect, sf); + } + + using (var textBrush = new SolidBrush(_textColor)) + { + g.DrawString(displayText, Font, textBrush, outerRect, sf); + } + } + } + + private string BuildDisplayText() + { + if (!string.IsNullOrEmpty(_statusText)) + { + return _statusText; + } + + if (_isIndeterminate && !string.IsNullOrEmpty(_operationType)) + { + // E.g. "Downloading..." + return _operationType + "..."; + } + + if (!_isIndeterminate && _value > _minimum) + { + string percentText = $"{(int)ProgressPercent}%"; + if (!string.IsNullOrEmpty(_operationType)) + { + // E.g. "Downloading · 73%" + return $"{_operationType} · {percentText}"; + } + return percentText; + } + + return string.Empty; + } + + private GraphicsPath CreateRoundedPath(Rectangle rect, int radius) + { + var path = new GraphicsPath(); + + if (radius <= 0) + { + path.AddRectangle(rect); + return path; + } + + int diameter = radius * 2; + diameter = Math.Min(diameter, Math.Min(rect.Width, rect.Height)); + radius = diameter / 2; + + var arcRect = new Rectangle(rect.Location, new Size(diameter, diameter)); + + path.AddArc(arcRect, 180, 90); + arcRect.X = rect.Right - diameter; + path.AddArc(arcRect, 270, 90); + arcRect.Y = rect.Bottom - diameter; + path.AddArc(arcRect, 0, 90); + arcRect.X = rect.Left; + path.AddArc(arcRect, 90, 90); + + path.CloseFigure(); + return path; + } + + #endregion + + #region Animation + + private void AnimationTimer_Tick(object sender, EventArgs e) + { + _animationOffset += AnimationSpeed; + + if (_animationOffset > ClientSize.Width + IndeterminateBlockWidth) + { + _animationOffset = -IndeterminateBlockWidth; + } + + Invalidate(); + } + + #endregion + + #region Cleanup + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _animationTimer?.Stop(); + _animationTimer?.Dispose(); + } + base.Dispose(disposing); + } + + #endregion + } +} diff --git a/icon.ico b/icon.ico index cf2c173..b2ca138 100644 Binary files a/icon.ico and b/icon.ico differ diff --git a/packages.config b/packages.config index f655c08..f998aa6 100644 --- a/packages.config +++ b/packages.config @@ -1,5 +1,6 @@  +