Added modern progress bar, real-time install/OBB progress updates using AdvancedSharpAdbClient, updated sideload confirmation messages, and a new icon based on the VRP server icon.

Introduce ModernProgressBar, a custom control with gradient fill, rounded corners, indeterminate animation, and optional status text. Integrated AdvancedSharpAdbClient to provide real-time progress updates for APK installs and OBB copies, updating the UI to reflect the current operation and its progress. Refactored ADB methods to support async progress reporting, updated MainForm to use the new progress bar features, improved user feedback with clearer confirmation messages during and after sideloading, added a new icon based on the VRP server icon and enabled it in the window title bar.
This commit is contained in:
jp64k
2025-12-11 23:06:49 +01:00
parent f714d2cb92
commit f8dea1e135
8 changed files with 1075 additions and 221 deletions

425
ADB.cs
View File

@@ -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<ProcessOutput> SideloadWithProgressAsync(
string path,
Action<int> progressCallback = null,
Action<string> 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<InstallProgressEventArgs> 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<InstallProgressEventArgs> 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<ProcessOutput> CopyOBBWithProgressAsync(
string localPath,
Action<int> progressCallback = null,
Action<string> 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<SyncProgressChangedEventArgs> 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,6 +413,8 @@ namespace AndroidSideloader
_ = Logger.Log($"Running command: {logcmd}");
using (var adb = new Process())
{
adb.StartInfo.FileName = "cmd.exe";
adb.StartInfo.RedirectStandardError = true;
adb.StartInfo.RedirectStandardInput = true;
@@ -111,7 +427,6 @@ namespace AndroidSideloader
adb.StandardInput.Flush();
adb.StandardInput.Close();
string output = "";
string error = "";
@@ -121,6 +436,7 @@ namespace AndroidSideloader
error += adb.StandardError.ReadToEnd();
}
catch { }
if (command.Contains("connect"))
{
bool graceful = adb.WaitForExit(3000);
@@ -130,23 +446,18 @@ namespace AndroidSideloader
adb.WaitForExit();
}
}
else if (command.Contains("connect"))
{
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);
}
}
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,26 +583,27 @@ 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;
if (!settings.AutoReinstall)
{
bool cancelClicked = false;
if (!settings.AutoReinstall)
@@ -297,17 +618,17 @@ namespace AndroidSideloader
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))
{

View File

@@ -136,6 +136,9 @@
<ManifestKeyFile>AndroidSideloader_TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="AdvancedSharpAdbClient, Version=3.5.15.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\AdvancedSharpAdbClient.3.5.15\lib\net45\AdvancedSharpAdbClient.dll</HintPath>
</Reference>
<Reference Include="Costura, Version=5.7.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll</HintPath>
</Reference>
@@ -187,6 +190,9 @@
<Compile Include="GalleryView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ModernProgessBar.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>

View File

@@ -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)

103
MainForm.Designer.cs generated
View File

@@ -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;

View File

@@ -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<string, ulong> installedVersions = new Dictionary<string, ulong>(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(() =>
{
@@ -3029,9 +3048,16 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
}
public void SetProgress(int progress)
{
if (progressBar.InvokeRequired)
{
progressBar.Invoke(new Action(() => progressBar.Value = progress));
}
else
{
progressBar.Value = progress;
}
}
public bool isinstalling = false;
public static bool isInDownloadExtract = 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);
})
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)
{
IsBackground = true
};
apkThread.Start();
while (apkThread.IsAlive)
{
await Task.Delay(100);
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("");
})
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(() =>
{
IsBackground = true
};
obbThread.Start();
while (obbThread.IsAlive)
progressBar.Value = progress;
speedLabel.Text = $"OBB: {progress}%";
if (!string.IsNullOrEmpty(currentObbStatusBase))
{
await Task.Delay(100);
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<Task<string>>();
@@ -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++)

438
ModernProgessBar.cs Normal file
View File

@@ -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
}
}

BIN
icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AdvancedSharpAdbClient" version="3.5.15" targetFramework="net452" />
<package id="Costura.Fody" version="5.7.0" targetFramework="net452" developmentDependency="true" />
<package id="Fody" version="6.8.1" targetFramework="net452" developmentDependency="true" />
<package id="Microsoft.NETCore.Platforms" version="7.0.4" targetFramework="net452" />