Merge pull request #292 from jp64k/RSL-3.0.5

Bunch of changes, look inside
This commit is contained in:
Fenopy
2026-02-06 06:22:15 -06:00
committed by GitHub
10 changed files with 1421 additions and 857 deletions

72
ADB.cs
View File

@@ -680,78 +680,6 @@ 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 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"))
{
_ = 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)
{
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;
Program.form.changeTitle("Performing reinstall, please wait...");
_ = 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 += 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))
{
if (directoryToDelete != Environment.CurrentDirectory)
{
FileSystemUtilities.TryDeleteDirectory(directoryToDelete);
}
}
Program.form.changeTitle("");
return ret;
}
}
Program.form.changeTitle("");
return ret;
}
public static ProcessOutput CopyOBB(string path)
{
string folder = Path.GetFileName(path);
string lastFolder = Path.GetFileName(path);
return folder.Contains(".")
? RunAdbCommandToString($"shell rm -rf \"/sdcard/Android/obb/{lastFolder}\" && mkdir \"/sdcard/Android/obb/{lastFolder}\"") + RunAdbCommandToString($"push \"{path}\" \"/sdcard/Android/obb\"")
: new ProcessOutput("No OBB Folder found");
}
}
internal class EtaEstimator

View File

@@ -1,4 +1,21 @@
RSL 2.34
RSL 3.0
Major Rookie overhaul with modernized UI, significant performance improvements and upgraded UX.
- Added high-performance Gallery View with search, filters, sorting, favorites, hover animations, smooth scrolling and uninstall buttons
- Toggle seamlessly between List and Gallery views with your preference remembered across launches
- Complete UI redesign with new dark theme, modernized components and subtle animations throughout
- Refined navigation, layouts, sizing and color consistency across the entire application
- Added uninstall buttons directly in List and Gallery views for quicker app management
- Improved startup performance through overhaul of initialization logic, removal of splash screen, parallelized async loading, batched version retrieval, optimized metadata extraction and game list initialization
- Instant list filtering through caching and streamlined filter logic (INSTALLED / UPDATE AVAILABLE / NEWER THAN LIST)
- Improved search speed and responsiveness
- Fixed and improved trailer handling with faster trailer loading
- Fixed multiple startup issues including connection errors and zombie ADB instances
- Added local blacklist support allowing users to permanently suppress donation prompts for specific apps
- Reduced application size by removal of now unused assets
RSL 2.34
- Feature: Allow users to favorite games (right click on game)
- Fix: Release Notes not showing with trailers enabled

File diff suppressed because it is too large Load Diff

246
MainForm.Designer.cs generated
View File

@@ -90,7 +90,6 @@ namespace AndroidSideloader
this.speedLabel_Tooltip = new System.Windows.Forms.ToolTip(this.components);
this.etaLabel_Tooltip = new System.Windows.Forms.ToolTip(this.components);
this.progressDLbtnContainer = new System.Windows.Forms.Panel();
this.progressBar = new AndroidSideloader.ModernProgressBar();
this.diskLabel = new System.Windows.Forms.Label();
this.questStorageProgressBar = new System.Windows.Forms.Panel();
this.batteryLevImg = new System.Windows.Forms.PictureBox();
@@ -119,9 +118,14 @@ namespace AndroidSideloader
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();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.webView21 = new Microsoft.Web.WebView2.WinForms.WebView2();
this.favoriteGame = new System.Windows.Forms.ContextMenuStrip(this.components);
this.favoriteButton = new System.Windows.Forms.ToolStripMenuItem();
this.gamesGalleryView = new System.Windows.Forms.FlowLayoutPanel();
this.btnViewToggle_Tooltip = new System.Windows.Forms.ToolTip(this.components);
this.webViewPlaceholderPanel = new System.Windows.Forms.Panel();
this.searchPanel = new AndroidSideloader.RoundButton();
this.searchIconPictureBox = new System.Windows.Forms.PictureBox();
this.searchTextBox = new System.Windows.Forms.TextBox();
@@ -130,12 +134,8 @@ namespace AndroidSideloader
this.btnInstalled = new AndroidSideloader.RoundButton();
this.btnUpdateAvailable = new AndroidSideloader.RoundButton();
this.btnNewerThanList = new AndroidSideloader.RoundButton();
this.webView21 = new Microsoft.Web.WebView2.WinForms.WebView2();
this.favoriteGame = new System.Windows.Forms.ContextMenuStrip(this.components);
this.favoriteButton = new System.Windows.Forms.ToolStripMenuItem();
this.gamesGalleryView = new System.Windows.Forms.FlowLayoutPanel();
this.btnViewToggle_Tooltip = new System.Windows.Forms.ToolTip(this.components);
this.webViewPlaceholderPanel = new System.Windows.Forms.Panel();
this.progressBar = new AndroidSideloader.ModernProgressBar();
this.downloadInstallGameButton = new AndroidSideloader.RoundButton();
((System.ComponentModel.ISupportInitialize)(this.gamesPictureBox)).BeginInit();
this.gamesPictureBox.SuspendLayout();
this.progressDLbtnContainer.SuspendLayout();
@@ -151,10 +151,10 @@ namespace AndroidSideloader
this.statusInfoPanel.SuspendLayout();
this.sidebarMediaPanel.SuspendLayout();
this.tableLayoutPanel1.SuspendLayout();
this.searchPanel.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.searchIconPictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.webView21)).BeginInit();
this.favoriteGame.SuspendLayout();
this.searchPanel.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.searchIconPictureBox)).BeginInit();
this.SuspendLayout();
//
// m_combo
@@ -165,7 +165,7 @@ namespace AndroidSideloader
this.m_combo.ForeColor = global::AndroidSideloader.Properties.Settings.Default.FontColor;
this.m_combo.Location = new System.Drawing.Point(253, 9);
this.m_combo.Name = "m_combo";
this.m_combo.Size = new System.Drawing.Size(374, 25);
this.m_combo.Size = new System.Drawing.Size(374, 24);
this.m_combo.TabIndex = 0;
this.m_combo.Text = "Select an Installed App...";
this.m_combo.Visible = false;
@@ -201,7 +201,6 @@ namespace AndroidSideloader
//
// gamesQueListBox
//
this.gamesQueListBox.AllowDrop = false;
this.gamesQueListBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.gamesQueListBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(26)))), ((int)(((byte)(30)))));
this.gamesQueListBox.BorderStyle = System.Windows.Forms.BorderStyle.None;
@@ -226,7 +225,7 @@ namespace AndroidSideloader
this.devicesComboBox.Location = new System.Drawing.Point(253, 39);
this.devicesComboBox.Margin = new System.Windows.Forms.Padding(2);
this.devicesComboBox.Name = "devicesComboBox";
this.devicesComboBox.Size = new System.Drawing.Size(164, 25);
this.devicesComboBox.Size = new System.Drawing.Size(164, 24);
this.devicesComboBox.TabIndex = 1;
this.devicesComboBox.Text = "Select your device";
this.devicesComboBox.Visible = false;
@@ -242,7 +241,7 @@ namespace AndroidSideloader
this.remotesList.Location = new System.Drawing.Point(567, 40);
this.remotesList.Margin = new System.Windows.Forms.Padding(2);
this.remotesList.Name = "remotesList";
this.remotesList.Size = new System.Drawing.Size(67, 25);
this.remotesList.Size = new System.Drawing.Size(67, 24);
this.remotesList.TabIndex = 3;
this.remotesList.Visible = false;
this.remotesList.SelectedIndexChanged += new System.EventHandler(this.remotesList_SelectedIndexChanged);
@@ -336,7 +335,7 @@ namespace AndroidSideloader
//
// notesRichTextBox
//
this.notesRichTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
this.notesRichTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.notesRichTextBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(26)))), ((int)(((byte)(30)))));
this.notesRichTextBox.BorderStyle = System.Windows.Forms.BorderStyle.None;
@@ -347,11 +346,12 @@ namespace AndroidSideloader
this.notesRichTextBox.Name = "notesRichTextBox";
this.notesRichTextBox.ReadOnly = true;
this.notesRichTextBox.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.None;
this.notesRichTextBox.SelectionAlignment = System.Windows.Forms.HorizontalAlignment.Center;
this.notesRichTextBox.ShowSelectionMargin = true;
this.notesRichTextBox.Size = new System.Drawing.Size(265, 192);
this.notesRichTextBox.TabIndex = 10;
this.notesRichTextBox.Text = "\n\n\n\n\nTip: Press F1 to see all shortcuts\n\nDrag and drop APKs or folders to install";
this.notesRichTextBox.SelectionAlignment = System.Windows.Forms.HorizontalAlignment.Center;
this.notesRichTextBox.Text = "\n\n\n\n\nTip: Press F1 to see all shortcuts\n\nDrag and drop APKs or folders to install" +
"";
this.notesRichTextBox.LinkClicked += new System.Windows.Forms.LinkClickedEventHandler(this.notesRichTextBox_LinkClicked);
//
// lblNotes
@@ -458,7 +458,7 @@ namespace AndroidSideloader
this.backupadbbutton.Padding = new System.Windows.Forms.Padding(30, 0, 0, 0);
this.backupadbbutton.Size = new System.Drawing.Size(233, 28);
this.backupadbbutton.TabIndex = 1;
this.backupadbbutton.Text = "BACKUP WITH ADB";
this.backupadbbutton.Text = "BACKUP GAMESAVE WITH ADB";
this.backupadbbutton.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.backupadbbutton_Tooltip.SetToolTip(this.backupadbbutton, "Save game data via ADB-Backup");
this.backupadbbutton.UseVisualStyleBackColor = false;
@@ -478,7 +478,7 @@ namespace AndroidSideloader
this.backupbutton.Padding = new System.Windows.Forms.Padding(30, 0, 0, 0);
this.backupbutton.Size = new System.Drawing.Size(233, 28);
this.backupbutton.TabIndex = 1;
this.backupbutton.Text = "BACKUP GAMESAVES";
this.backupbutton.Text = "BACKUP ALL GAMESAVES";
this.backupbutton.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.backupbutton_Tooltip.SetToolTip(this.backupbutton, "Save game and apps data to the backup folder (Does not save APKs or OBBs)");
this.backupbutton.UseVisualStyleBackColor = false;
@@ -500,7 +500,7 @@ namespace AndroidSideloader
this.restorebutton.TabIndex = 0;
this.restorebutton.Text = "RESTORE GAMESAVES";
this.restorebutton.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.restorebutton_Tooltip.SetToolTip(this.restorebutton, "Restore game and apps data to the device (Use BACKUP GAMESAVES first)");
this.restorebutton_Tooltip.SetToolTip(this.restorebutton, "Restore game and apps data to the device");
this.restorebutton.UseVisualStyleBackColor = false;
this.restorebutton.Click += new System.EventHandler(this.restorebutton_Click);
//
@@ -759,31 +759,6 @@ namespace AndroidSideloader
this.progressDLbtnContainer.Size = new System.Drawing.Size(984, 40);
this.progressDLbtnContainer.TabIndex = 96;
//
// progressBar
//
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)(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, 23);
this.progressBar.Maximum = 100F;
this.progressBar.Minimum = 0F;
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.StatusText = "";
this.progressBar.TabIndex = 7;
this.progressBar.TextColor = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(230)))), ((int)(((byte)(230)))));
this.progressBar.Value = 0F;
//
// diskLabel
//
this.diskLabel.BackColor = System.Drawing.Color.Transparent;
@@ -1249,35 +1224,6 @@ namespace AndroidSideloader
this.sidebarMediaPanel.Size = new System.Drawing.Size(233, 214);
this.sidebarMediaPanel.TabIndex = 101;
//
// downloadInstallGameButton
//
this.downloadInstallGameButton.Active1 = System.Drawing.Color.FromArgb(((int)(((byte)(110)))), ((int)(((byte)(215)))), ((int)(((byte)(190)))));
this.downloadInstallGameButton.Active2 = System.Drawing.Color.FromArgb(((int)(((byte)(110)))), ((int)(((byte)(215)))), ((int)(((byte)(190)))));
this.downloadInstallGameButton.BackColor = System.Drawing.Color.Transparent;
this.downloadInstallGameButton.Cursor = System.Windows.Forms.Cursors.Hand;
this.downloadInstallGameButton.DialogResult = System.Windows.Forms.DialogResult.OK;
this.downloadInstallGameButton.Disabled1 = System.Drawing.Color.FromArgb(((int)(((byte)(16)))), ((int)(((byte)(18)))), ((int)(((byte)(22)))));
this.downloadInstallGameButton.Disabled2 = System.Drawing.Color.FromArgb(((int)(((byte)(16)))), ((int)(((byte)(18)))), ((int)(((byte)(22)))));
this.downloadInstallGameButton.DisabledStrokeColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(55)))), ((int)(((byte)(65)))));
this.downloadInstallGameButton.Enabled = false;
this.downloadInstallGameButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
this.downloadInstallGameButton.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(59)))), ((int)(((byte)(67)))), ((int)(((byte)(82)))));
this.downloadInstallGameButton.Inactive1 = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
this.downloadInstallGameButton.Inactive2 = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
this.downloadInstallGameButton.Location = new System.Drawing.Point(6, 177);
this.downloadInstallGameButton.Margin = new System.Windows.Forms.Padding(0);
this.downloadInstallGameButton.Name = "downloadInstallGameButton";
this.downloadInstallGameButton.Radius = 4;
this.downloadInstallGameButton.Size = new System.Drawing.Size(238, 30);
this.downloadInstallGameButton.Stroke = true;
this.downloadInstallGameButton.StrokeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
this.downloadInstallGameButton.TabIndex = 94;
this.downloadInstallGameButton.Text = "DOWNLOAD AND INSTALL";
this.downloadInstallGameButton.Transparency = false;
this.downloadInstallGameButton.Click += new System.EventHandler(this.downloadInstallGameButton_Click);
this.downloadInstallGameButton.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop);
this.downloadInstallGameButton.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter);
//
// selectedGameLabel
//
this.selectedGameLabel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(20)))), ((int)(((byte)(24)))), ((int)(((byte)(29)))));
@@ -1316,6 +1262,58 @@ namespace AndroidSideloader
this.tableLayoutPanel1.Size = new System.Drawing.Size(984, 34);
this.tableLayoutPanel1.TabIndex = 97;
//
// webView21
//
this.webView21.AllowExternalDrop = true;
this.webView21.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.webView21.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(26)))), ((int)(((byte)(30)))));
this.webView21.CreationProperties = null;
this.webView21.DefaultBackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(26)))), ((int)(((byte)(30)))));
this.webView21.Location = new System.Drawing.Point(259, 496);
this.webView21.Name = "webView21";
this.webView21.Size = new System.Drawing.Size(384, 216);
this.webView21.TabIndex = 98;
this.webView21.ZoomFactor = 1D;
//
// favoriteGame
//
this.favoriteGame.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(42)))), ((int)(((byte)(48)))));
this.favoriteGame.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.favoriteButton});
this.favoriteGame.Name = "favoriteGame";
this.favoriteGame.ShowImageMargin = false;
this.favoriteGame.Size = new System.Drawing.Size(149, 26);
//
// favoriteButton
//
this.favoriteButton.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(42)))), ((int)(((byte)(48)))));
this.favoriteButton.ForeColor = System.Drawing.Color.White;
this.favoriteButton.Name = "favoriteButton";
this.favoriteButton.Size = new System.Drawing.Size(148, 22);
this.favoriteButton.Text = "★ Add to Favorites";
this.favoriteButton.Click += new System.EventHandler(this.favoriteButton_Click);
//
// gamesGalleryView
//
this.gamesGalleryView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.gamesGalleryView.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(15)))), ((int)(((byte)(15)))), ((int)(((byte)(15)))));
this.gamesGalleryView.Location = new System.Drawing.Point(258, 44);
this.gamesGalleryView.Name = "gamesGalleryView";
this.gamesGalleryView.Size = new System.Drawing.Size(984, 409);
this.gamesGalleryView.TabIndex = 102;
//
// webViewPlaceholderPanel
//
this.webViewPlaceholderPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.webViewPlaceholderPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(26)))), ((int)(((byte)(30)))));
this.webViewPlaceholderPanel.Location = new System.Drawing.Point(259, 496);
this.webViewPlaceholderPanel.Name = "webViewPlaceholderPanel";
this.webViewPlaceholderPanel.Size = new System.Drawing.Size(384, 217);
this.webViewPlaceholderPanel.TabIndex = 103;
this.webViewPlaceholderPanel.Paint += new System.Windows.Forms.PaintEventHandler(this.webViewPlaceholderPanel_Paint);
//
// searchPanel
//
this.searchPanel.Active1 = System.Drawing.Color.FromArgb(((int)(((byte)(51)))), ((int)(((byte)(56)))), ((int)(((byte)(70)))));
@@ -1499,57 +1497,59 @@ namespace AndroidSideloader
this.btnNewerThanList.Transparency = false;
this.btnNewerThanList.Click += new System.EventHandler(this.btnNewerThanList_Click);
//
// webView21
// progressBar
//
this.webView21.AllowExternalDrop = true;
this.webView21.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.webView21.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(26)))), ((int)(((byte)(30)))));
this.webView21.CreationProperties = null;
this.webView21.DefaultBackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(26)))), ((int)(((byte)(30)))));
this.webView21.Location = new System.Drawing.Point(259, 496);
this.webView21.Name = "webView21";
this.webView21.Size = new System.Drawing.Size(384, 216);
this.webView21.TabIndex = 98;
this.webView21.ZoomFactor = 1D;
//
// favoriteGame
//
this.favoriteGame.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(42)))), ((int)(((byte)(48)))));
this.favoriteGame.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.favoriteButton});
this.favoriteGame.Name = "favoriteGame";
this.favoriteGame.ShowImageMargin = false;
this.favoriteGame.Size = new System.Drawing.Size(149, 26);
//
// favoriteButton
//
this.favoriteButton.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(42)))), ((int)(((byte)(48)))));
this.favoriteButton.ForeColor = System.Drawing.Color.White;
this.favoriteButton.Name = "favoriteButton";
this.favoriteButton.Size = new System.Drawing.Size(148, 22);
this.favoriteButton.Text = "★ Add to Favorites";
this.favoriteButton.Click += new System.EventHandler(this.favoriteButton_Click);
//
// gamesGalleryView
//
this.gamesGalleryView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
this.progressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.gamesGalleryView.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(15)))), ((int)(((byte)(15)))), ((int)(((byte)(15)))));
this.gamesGalleryView.Location = new System.Drawing.Point(258, 44);
this.gamesGalleryView.Name = "gamesGalleryView";
this.gamesGalleryView.Size = new System.Drawing.Size(984, 409);
this.gamesGalleryView.TabIndex = 102;
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, 23);
this.progressBar.Maximum = 100F;
this.progressBar.Minimum = 0F;
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.StatusText = "";
this.progressBar.TabIndex = 7;
this.progressBar.TextColor = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(230)))), ((int)(((byte)(230)))));
this.progressBar.Value = 0F;
//
// webViewPlaceholderPanel
// downloadInstallGameButton
//
this.webViewPlaceholderPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.webViewPlaceholderPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(26)))), ((int)(((byte)(30)))));
this.webViewPlaceholderPanel.Location = new System.Drawing.Point(259, 496);
this.webViewPlaceholderPanel.Name = "webViewPlaceholderPanel";
this.webViewPlaceholderPanel.Size = new System.Drawing.Size(384, 217);
this.webViewPlaceholderPanel.TabIndex = 103;
this.webViewPlaceholderPanel.Paint += new System.Windows.Forms.PaintEventHandler(this.webViewPlaceholderPanel_Paint);
this.downloadInstallGameButton.Active1 = System.Drawing.Color.FromArgb(((int)(((byte)(110)))), ((int)(((byte)(215)))), ((int)(((byte)(190)))));
this.downloadInstallGameButton.Active2 = System.Drawing.Color.FromArgb(((int)(((byte)(110)))), ((int)(((byte)(215)))), ((int)(((byte)(190)))));
this.downloadInstallGameButton.BackColor = System.Drawing.Color.Transparent;
this.downloadInstallGameButton.Cursor = System.Windows.Forms.Cursors.Hand;
this.downloadInstallGameButton.DialogResult = System.Windows.Forms.DialogResult.OK;
this.downloadInstallGameButton.Disabled1 = System.Drawing.Color.FromArgb(((int)(((byte)(16)))), ((int)(((byte)(18)))), ((int)(((byte)(22)))));
this.downloadInstallGameButton.Disabled2 = System.Drawing.Color.FromArgb(((int)(((byte)(16)))), ((int)(((byte)(18)))), ((int)(((byte)(22)))));
this.downloadInstallGameButton.DisabledStrokeColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(55)))), ((int)(((byte)(65)))));
this.downloadInstallGameButton.Enabled = false;
this.downloadInstallGameButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
this.downloadInstallGameButton.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(59)))), ((int)(((byte)(67)))), ((int)(((byte)(82)))));
this.downloadInstallGameButton.Inactive1 = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
this.downloadInstallGameButton.Inactive2 = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
this.downloadInstallGameButton.Location = new System.Drawing.Point(6, 177);
this.downloadInstallGameButton.Margin = new System.Windows.Forms.Padding(0);
this.downloadInstallGameButton.Name = "downloadInstallGameButton";
this.downloadInstallGameButton.Radius = 4;
this.downloadInstallGameButton.Size = new System.Drawing.Size(238, 30);
this.downloadInstallGameButton.Stroke = true;
this.downloadInstallGameButton.StrokeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
this.downloadInstallGameButton.TabIndex = 94;
this.downloadInstallGameButton.Text = "DOWNLOAD";
this.downloadInstallGameButton.Transparency = false;
this.downloadInstallGameButton.Click += new System.EventHandler(this.downloadInstallGameButton_Click);
this.downloadInstallGameButton.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop);
this.downloadInstallGameButton.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter);
//
// MainForm
//
@@ -1600,11 +1600,11 @@ namespace AndroidSideloader
this.statusInfoPanel.ResumeLayout(false);
this.sidebarMediaPanel.ResumeLayout(false);
this.tableLayoutPanel1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.webView21)).EndInit();
this.favoriteGame.ResumeLayout(false);
this.searchPanel.ResumeLayout(false);
this.searchPanel.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.searchIconPictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.webView21)).EndInit();
this.favoriteGame.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();

View File

@@ -74,6 +74,7 @@ namespace AndroidSideloader
private static readonly Color ColorError = ColorTranslator.FromHtml("#f52f57");
private Panel _listViewUninstallButton;
private bool _listViewUninstallButtonHovered = false;
private ListViewItem _hoveredItemForDeleteBtn;
private bool isGalleryView; // Will be set from settings in constructor
private List<ListViewItem> _galleryDataSource;
private FastGalleryPanel _fastGallery;
@@ -171,12 +172,10 @@ namespace AndroidSideloader
if (_listViewUninstallButton == null)
return;
// Check if we have a tagged item to track
if (!(_listViewUninstallButton.Tag is ListViewItem item))
return;
var item = _hoveredItemForDeleteBtn;
// Verify item is still valid and selected
if (!gamesListView.Items.Contains(item) || !item.Selected)
// Hide if no item is hovered
if (item == null || !gamesListView.Items.Contains(item))
{
_listViewUninstallButton.Visible = false;
return;
@@ -213,6 +212,7 @@ namespace AndroidSideloader
if (isVisible)
{
_listViewUninstallButton.Location = new Point(buttonX, buttonY);
_listViewUninstallButton.Tag = item; // Store reference for click handler
if (!_listViewUninstallButton.Visible)
{
_listViewUninstallButton.Visible = true;
@@ -225,12 +225,19 @@ namespace AndroidSideloader
};
uninstallButtonTimer.Start();
// Hide button when selection changes
gamesListView.ItemSelectionChanged += (s, ev) =>
gamesListView.MouseMove += (s, ev) =>
{
if (!ev.IsSelected && _listViewUninstallButton != null)
var hitTest = gamesListView.HitTest(ev.Location);
_hoveredItemForDeleteBtn = hitTest.Item;
};
gamesListView.MouseLeave += (s, ev) =>
{
// Clear hover if mouse left the ListView bounds
Point clientPoint = gamesListView.PointToClient(Control.MousePosition);
if (!gamesListView.ClientRectangle.Contains(clientPoint))
{
_listViewUninstallButton.Visible = false;
_hoveredItemForDeleteBtn = null;
}
};
@@ -577,28 +584,17 @@ namespace AndroidSideloader
"Config Update Failed", MessageBoxButtons.OK);
}
}
else if (settings.AutoUpdateConfig && settings.CreatePubMirrorFile)
else if (settings.AutoUpdateConfig)
{
DialogResult dialogResult = FlexibleMessageBox.Show(Program.form,
"Rookie has detected that you are missing the public config file, would you like to create it?",
"Public Config Missing", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
// Auto-create the public config file if it doesn't exist
Logger.Log("Public config file missing, creating automatically...");
File.Create(configFilePath).Close();
await GetPublicConfigAsync();
if (!hasPublicConfig)
{
File.Create(configFilePath).Close();
await GetPublicConfigAsync();
if (!hasPublicConfig)
{
_ = FlexibleMessageBox.Show(Program.form,
"Failed to fetch public mirror config, and the current one is unreadable.\r\nPlease ensure you can access https://vrpirates.wiki/ in your browser.",
"Config Update Failed", MessageBoxButtons.OK);
}
}
else
{
settings.CreatePubMirrorFile = false;
settings.AutoUpdateConfig = false;
settings.Save();
_ = FlexibleMessageBox.Show(Program.form,
"Failed to fetch public mirror config, and the current one is unreadable.\r\nPlease ensure you can access https://vrpirates.wiki/ in your browser.",
"Config Update Failed", MessageBoxButtons.OK);
}
}
@@ -876,22 +872,52 @@ namespace AndroidSideloader
}
ADB.DeviceID = GetDeviceID();
Thread t1 = new Thread(() =>
{
output += ADB.Sideload(path);
})
{
IsBackground = true
};
t1.Start();
string filename = Path.GetFileName(path);
changeTitle($"Installing {filename}...");
progressBar.IsIndeterminate = false;
progressBar.OperationType = "Installing";
progressBar.Value = 0;
progressBar.StatusText = "Preparing...";
while (t1.IsAlive)
{
await Task.Delay(100);
}
output = await ADB.SideloadWithProgressAsync(
path,
(percent, eta) => this.Invoke(() =>
{
if (percent == 0)
{
progressBar.IsIndeterminate = true;
progressBar.OperationType = "Installing";
}
else
{
progressBar.IsIndeterminate = false;
progressBar.Value = percent;
}
UpdateProgressStatus("Installing", percent: (int)Math.Round(percent), eta: eta);
progressBar.StatusText = $"Installing · {percent:0.0}%";
}),
status => this.Invoke(() =>
{
if (!string.IsNullOrEmpty(status))
{
if (status.Contains("Completing Installation"))
{
speedLabel.Text = status;
}
progressBar.StatusText = status;
}
}),
"",
filename);
// Reset UI on completion
progressBar.Value = 0;
progressBar.StatusText = "";
progressBar.IsIndeterminate = false;
speedLabel.Text = "";
changeTitle("");
showAvailableSpace();
ShowPrcOutput(output);
}
@@ -984,31 +1010,45 @@ namespace AndroidSideloader
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Copying OBB";
progressBar.StatusText = "Preparing...";
string currentStatusBase = string.Empty;
output = await ADB.CopyOBBWithProgressAsync(
path,
(progress, eta) => this.Invoke(() =>
(percent, eta) => this.Invoke(() =>
{
progressBar.Value = progress;
string etaStr = eta.HasValue && eta.Value.TotalSeconds > 0
? $" · ETA: {eta.Value:mm\\:ss}"
: "";
speedLabel.Text = $"Progress: {progress}%{etaStr}";
progressBar.Value = percent;
UpdateProgressStatus("Copying OBB", percent: (int)Math.Round(percent), eta: eta);
if (!string.IsNullOrEmpty(currentStatusBase))
{
progressBar.StatusText = $"{currentStatusBase} · {percent:0.0}%";
}
else
{
progressBar.StatusText = $"Copying · {percent:0.0}%";
}
}),
status => this.Invoke(() =>
{
progressBar.StatusText = status;
currentStatusBase = status ?? string.Empty;
if (!string.IsNullOrEmpty(status))
{
progressBar.StatusText = status;
}
}),
folderName);
progressBar.Value = 100;
// Reset UI on completion
progressBar.Value = 0;
progressBar.StatusText = "";
changeTitle("Done.");
showAvailableSpace();
ShowPrcOutput(output);
changeTitle("");
progressBar.IsIndeterminate = false;
speedLabel.Text = "";
changeTitle("");
showAvailableSpace();
ShowPrcOutput(output);
}
}
@@ -1041,6 +1081,9 @@ namespace AndroidSideloader
Text = "No Device Connected";
if (!settings.NodeviceMode)
{
// Explicitly update sideloading UI when the dialog is shown
UpdateSideloadingUI(true);
DialogResult dialogResult = FlexibleMessageBox.Show(Program.form, "No device found. Please ensure the following:\n\n - Developer mode is enabled\n - ADB drivers are installed\n - ADB connection is enabled on your device (this can reset)\n - Your device is plugged in\n\nThen press \"Retry\"", "No device found.", MessageBoxButtons.RetryCancel);
if (dialogResult == DialogResult.Retry)
{
@@ -1184,8 +1227,8 @@ namespace AndroidSideloader
string CurrBackups = Path.Combine(backupFolder, date_str);
DialogResult dialogResult1 = FlexibleMessageBox.Show(Program.form,
$"Do you want to backup all gamesaves to:\n{CurrBackups}\\",
"Backup Gamesaves",
$"Do you want to attempt to backup all gamesaves to:\n{CurrBackups}\\",
"Backup All Gamesaves",
MessageBoxButtons.YesNo);
if (dialogResult1 == DialogResult.No || dialogResult1 == DialogResult.Cancel) return;
@@ -1275,7 +1318,10 @@ namespace AndroidSideloader
summary.AppendLine($" • {failed}");
}
FlexibleMessageBox.Show(Program.form, summary.ToString(), "Backup Complete");
summary.AppendLine("\nNote: Some games may not support bulk backup as they require special permissions.");
summary.AppendLine("In this case, use 'BACKUP GAMESAVE WITH ADB' to backup the game individually.");
FlexibleMessageBox.Show(Program.form, summary.ToString(), "Bulk Backup Complete");
}
private async void restorebutton_Click(object sender, EventArgs e)
@@ -1715,23 +1761,49 @@ namespace AndroidSideloader
};
if (dialog.Show(Handle))
{
Thread t1 = new Thread(() =>
{
Sideloader.RecursiveOutput = new ProcessOutput(String.Empty, String.Empty);
Sideloader.RecursiveCopyOBB(dialog.FileName);
})
{
IsBackground = true
};
t1.Start();
changeTitle("Copying OBB folders to device...");
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Copying OBB";
progressBar.StatusText = "Preparing...";
Sideloader.RecursiveOutput = new ProcessOutput(String.Empty, String.Empty);
string currentStatusBase = string.Empty;
await Sideloader.RecursiveCopyOBBAsync(
dialog.FileName,
(percent, eta) => this.Invoke(() =>
{
progressBar.Value = percent;
UpdateProgressStatus("Copying OBB", percent: (int)Math.Round(percent), eta: eta);
if (!string.IsNullOrEmpty(currentStatusBase))
{
progressBar.StatusText = $"{currentStatusBase} · {percent:0.0}%";
}
else
{
progressBar.StatusText = $"Copying · {percent:0.0}%";
}
}),
status => this.Invoke(() =>
{
currentStatusBase = status ?? string.Empty;
if (!string.IsNullOrEmpty(status))
{
changeTitle($"Copying: {status}");
}
}));
// Reset UI on completion
progressBar.Value = 0;
progressBar.StatusText = "";
progressBar.IsIndeterminate = false;
speedLabel.Text = "";
changeTitle("");
showAvailableSpace();
while (t1.IsAlive)
{
await Task.Delay(100);
}
ShowPrcOutput(Sideloader.RecursiveOutput);
}
}
@@ -1776,23 +1848,45 @@ namespace AndroidSideloader
if (!data.Contains("+") && !data.Contains("_") && data.Contains("."))
{
_ = Logger.Log($"Copying {data} to device");
changeTitle($"Copying {data} to device...");
string folderName = Path.GetFileName(data);
changeTitle($"Copying {folderName} to device...");
Thread t2 = new Thread(() =>
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Copying OBB";
progressBar.StatusText = "Preparing...";
{
output += ADB.CopyOBB(data);
})
{
IsBackground = true
};
t2.Start();
string currentStatusBase = string.Empty;
while (t2.IsAlive)
{
await Task.Delay(100);
}
output += await ADB.CopyOBBWithProgressAsync(
data,
(percent, eta) => this.Invoke(() =>
{
progressBar.Value = percent;
UpdateProgressStatus("Copying OBB", percent: (int)Math.Round(percent), eta: eta);
if (!string.IsNullOrEmpty(currentStatusBase))
{
progressBar.StatusText = $"{currentStatusBase} · {percent:0.0}%";
}
else
{
progressBar.StatusText = $"Copying · {percent:0.0}%";
}
}),
status => this.Invoke(() =>
{
currentStatusBase = status ?? string.Empty;
if (!string.IsNullOrEmpty(status))
{
changeTitle($"Copying: {status}");
}
}),
folderName);
// Reset UI after this operation
progressBar.StatusText = "";
speedLabel.Text = "";
changeTitle("");
settings.CurrPckg = dir;
settings.Save();
@@ -1827,42 +1921,88 @@ namespace AndroidSideloader
};
t3.Tick += timer_Tick4;
t3.Start();
changeTitle($"Sideloading APK ({filename})");
Thread t2 = new Thread(() =>
{
output += ADB.Sideload(file2);
})
{
IsBackground = true
};
t2.Start();
while (t2.IsAlive)
{
await Task.Delay(100);
}
changeTitle($"Sideloading APK ({filename})");
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Installing";
progressBar.StatusText = "Preparing...";
output += await ADB.SideloadWithProgressAsync(
file2,
(percent, eta) => this.Invoke(() =>
{
if (percent == 0)
{
progressBar.IsIndeterminate = true;
progressBar.OperationType = "Installing";
}
else
{
progressBar.IsIndeterminate = false;
progressBar.Value = percent;
}
UpdateProgressStatus("Installing", percent: (int)Math.Round(percent), eta: eta);
progressBar.StatusText = $"Installing · {percent:0.0}%";
}),
status => this.Invoke(() =>
{
if (!string.IsNullOrEmpty(status))
{
if (status.Contains("Completing Installation"))
{
speedLabel.Text = status;
}
progressBar.StatusText = status;
}
}),
cmdout,
filename);
t3.Stop();
// Reset after APK install
progressBar.StatusText = "";
speedLabel.Text = "";
if (Directory.Exists($"{pathname}\\{cmdout}"))
{
_ = Logger.Log($"Copying OBB folder to device- {cmdout}");
changeTitle($"Copying OBB folder to device...");
Thread t1 = new Thread(() =>
{
if (!string.IsNullOrEmpty(cmdout))
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Copying OBB";
progressBar.StatusText = "Preparing...";
string obbStatusBase = string.Empty;
output += await ADB.CopyOBBWithProgressAsync(
$"{pathname}\\{cmdout}",
(percent, eta) => this.Invoke(() =>
{
_ = ADB.RunAdbCommandToString($"shell rm -rf \"/sdcard/Android/obb/{cmdout}\" && mkdir \"/sdcard/Android/obb/{cmdout}\"");
}
_ = ADB.RunAdbCommandToString($"push \"{pathname}\\{cmdout}\" /sdcard/Android/obb/");
})
{
IsBackground = true
};
t1.Start();
while (t1.IsAlive)
{
await Task.Delay(100);
}
progressBar.Value = percent;
UpdateProgressStatus("Copying OBB", percent: (int)Math.Round(percent), eta: eta);
if (!string.IsNullOrEmpty(obbStatusBase))
{
progressBar.StatusText = $"{obbStatusBase} · {percent:0.0}%";
}
else
{
progressBar.StatusText = $"Copying · {percent:0.0}%";
}
}),
status => this.Invoke(() =>
{
obbStatusBase = status ?? string.Empty;
}),
cmdout);
// Reset after OBB copy
progressBar.StatusText = "";
speedLabel.Text = "";
changeTitle("");
}
}
@@ -1898,24 +2038,46 @@ namespace AndroidSideloader
string[] folders = Directory.GetDirectories(data);
foreach (string folder in folders)
{
string folderName = Path.GetFileName(folder);
_ = Logger.Log($"Copying {folder} to device");
changeTitle($"Copying {folder} to device...");
changeTitle($"Copying {folderName} to device...");
Thread t2 = new Thread(() =>
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Copying OBB";
progressBar.StatusText = "Preparing...";
{
output += ADB.CopyOBB(folder);
})
{
IsBackground = true
};
t2.Start();
string folderStatusBase = string.Empty;
while (t2.IsAlive)
{
await Task.Delay(100);
}
output += await ADB.CopyOBBWithProgressAsync(
folder,
(percent, eta) => this.Invoke(() =>
{
progressBar.Value = percent;
UpdateProgressStatus("Copying OBB", percent: (int)Math.Round(percent), eta: eta);
if (!string.IsNullOrEmpty(folderStatusBase))
{
progressBar.StatusText = $"{folderStatusBase} · {percent:0.0}%";
}
else
{
progressBar.StatusText = $"Copying · {percent:0.0}%";
}
}),
status => this.Invoke(() =>
{
folderStatusBase = status ?? string.Empty;
if (!string.IsNullOrEmpty(status))
{
changeTitle($"Copying: {status}");
}
}),
folderName);
// Reset after folder copy
progressBar.StatusText = "";
speedLabel.Text = "";
changeTitle("");
settings.CurrPckg = dir;
settings.Save();
@@ -1979,43 +2141,85 @@ namespace AndroidSideloader
timer.Start();
changeTitle($"Installing {dataname}...");
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Installing";
progressBar.StatusText = "Preparing...";
Thread t1 = new Thread(() =>
{
output += ADB.Sideload(data);
})
{
IsBackground = true
};
t1.Start();
while (t1.IsAlive)
{
await Task.Delay(100);
}
output += await ADB.SideloadWithProgressAsync(
data,
(percent, eta) => this.Invoke(() =>
{
if (percent == 0)
{
progressBar.IsIndeterminate = true;
progressBar.OperationType = "Installing";
}
else
{
progressBar.IsIndeterminate = false;
progressBar.Value = percent;
}
UpdateProgressStatus("Installing", percent: (int)Math.Round(percent), eta: eta);
progressBar.StatusText = $"Installing · {percent:0.0}%";
}),
status => this.Invoke(() =>
{
if (!string.IsNullOrEmpty(status))
{
if (status.Contains("Completing Installation"))
{
speedLabel.Text = status;
}
progressBar.StatusText = status;
}
}),
cmdout,
dataname);
timer.Stop();
// Reset after APK install
progressBar.StatusText = "";
speedLabel.Text = "";
if (Directory.Exists($"{pathname}\\{cmdout}"))
{
_ = Logger.Log($"Copying OBB folder to device- {cmdout}");
changeTitle($"Copying OBB folder to device...");
Thread t2 = new Thread(() =>
{
if (!string.IsNullOrEmpty(cmdout))
{
_ = ADB.RunAdbCommandToString($"shell rm -rf \"/sdcard/Android/obb/{cmdout}\" && mkdir \"/sdcard/Android/obb/{cmdout}\"");
}
_ = ADB.RunAdbCommandToString($"push \"{pathname}\\{cmdout}\" /sdcard/Android/obb/");
})
{
IsBackground = true
};
t2.Start();
while (t2.IsAlive)
{
await Task.Delay(100);
}
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Copying OBB";
progressBar.StatusText = "Preparing...";
string obbStatusBase = string.Empty;
output += await ADB.CopyOBBWithProgressAsync(
$"{pathname}\\{cmdout}",
(percent, eta) => this.Invoke(() =>
{
progressBar.Value = percent;
UpdateProgressStatus("Copying OBB", percent: (int)Math.Round(percent), eta: eta);
if (!string.IsNullOrEmpty(obbStatusBase))
{
progressBar.StatusText = $"{obbStatusBase} · {percent:0.0}%";
}
else
{
progressBar.StatusText = $"Copying · {percent:0.0}%";
}
}),
status => this.Invoke(() =>
{
obbStatusBase = status ?? string.Empty;
}),
cmdout);
// Reset after OBB copy
progressBar.StatusText = "";
speedLabel.Text = "";
changeTitle("");
}
}
@@ -2032,21 +2236,41 @@ namespace AndroidSideloader
File.Copy(data, Path.Combine(foldername, filename));
path = foldername;
Thread t1 = new Thread(() =>
{
output += ADB.CopyOBB(path);
})
{
IsBackground = true
};
_ = Logger.Log($"Copying OBB folder to device- {path}");
changeTitle($"Copying OBB folder to device ({filename})");
t1.Start();
while (t1.IsAlive)
{
await Task.Delay(100);
}
progressBar.IsIndeterminate = false;
progressBar.Value = 0;
progressBar.OperationType = "Copying OBB";
progressBar.StatusText = "Preparing...";
string obbStatusBase = string.Empty;
output += await ADB.CopyOBBWithProgressAsync(
path,
(percent, eta) => this.Invoke(() =>
{
progressBar.Value = percent;
UpdateProgressStatus("Copying OBB", percent: (int)Math.Round(percent), eta: eta);
if (!string.IsNullOrEmpty(obbStatusBase))
{
progressBar.StatusText = $"{obbStatusBase} · {percent:0.0}%";
}
else
{
progressBar.StatusText = $"Copying · {percent:0.0}%";
}
}),
status => this.Invoke(() =>
{
obbStatusBase = status ?? string.Empty;
}),
filename);
// Reset after OBB copy
progressBar.StatusText = "";
speedLabel.Text = "";
FileSystemUtilities.TryDeleteDirectory(foldername);
changeTitle("");
@@ -2104,7 +2328,11 @@ namespace AndroidSideloader
}
}
// Final reset of all UI elements
progressBar.Value = 0;
progressBar.StatusText = "";
progressBar.IsIndeterminate = false;
speedLabel.Text = "";
showAvailableSpace();
ShowPrcOutput(output);
@@ -2387,7 +2615,7 @@ namespace AndroidSideloader
// Check if this is a 0 MB entry that should be excluded
bool shouldSkip = false;
if (release.Length > 5 && StringUtilities.TryParseDouble(release[6], out double sizeInMB))
if (release.Length > 5 && StringUtilities.TryParseDouble(release[5], out double sizeInMB))
{
// If size is 0 MB and this is not already an MR-Fix version
if (sizeInMB == 0 && gameName.IndexOf("(MR-Fix)", StringComparison.OrdinalIgnoreCase) < 0)
@@ -5846,18 +6074,17 @@ function onYouTubeIframeAPIReady() {
return simpleMatch.Success ? simpleMatch.Groups[1].Value : string.Empty;
}
// Prepare game name words for matching
string lowerGameName = cleanedGameName.ToLowerInvariant();
// Normalize: remove apostrophes and convert to lowercase
string lowerGameName = cleanedGameName.ToLowerInvariant().Replace("'", "");
var gameWords = lowerGameName
.Split(new[] { ' ', '-', ':', '&' }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
int requiredMatches = Math.Max(1, gameWords.Count / 2);
int requiredMatches = gameWords.Count;
string bestVideoId = null;
int bestScore = 0;
int position = 0;
// Score each match
foreach (Match match in videoMatches)
{
string videoId = match.Groups[1].Value;
@@ -5866,18 +6093,20 @@ function onYouTubeIframeAPIReady() {
title = UnicodeEscapeRegex.Replace(title, m =>
((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString());
// Entry must match at least half the game name
int matchedWords = gameWords.Count(w => title.Contains(w));
if (matchedWords < requiredMatches)
// Normalize title: remove apostrophes for matching
string normalizedTitle = title.Replace("'", "");
// All game words must be present
if (!gameWords.All(w => normalizedTitle.Contains(w)))
continue;
position++;
// Only process first 5 matches
if (position > 5)
// Only process first 10 matches
if (position > 10)
break;
int score = matchedWords * 10;
int score = 0;
// Position bonus
if (position == 1) score += 30;
@@ -5885,9 +6114,10 @@ function onYouTubeIframeAPIReady() {
else if (position == 3) score += 10;
// Word bonus
if (title.Contains("trailer")) score += 20;
if (title.Contains("official") || title.Contains("launch") || title.Contains("release")) score += 15;
if (title.Contains("announce")) score += 12; // also includes "announcement"
if (title.Contains("trailer") || title.Contains("teaser")) score += 25;
if (title.Contains("official")) score += 20;
if (title.Contains("launch") || title.Contains("release")) score += 15;
if (title.Contains("announce") || title.Contains("reveal")) score += 12; // also includes "announcement"
if (title.Contains("gameplay") || title.Contains("vr")) score += 5;
// Noise penalty for extra words
@@ -5900,6 +6130,9 @@ function onYouTubeIframeAPIReady() {
if (title.Contains("review") ||
title.Contains("tutorial") ||
title.Contains("how to") ||
title.Contains("install") ||
title.Contains("guide") ||
title.Contains("setup") ||
title.Contains("reaction"))
score -= 30;
@@ -5941,27 +6174,6 @@ function onYouTubeIframeAPIReady() {
// Update the selected game label in the sidebar
selectedGameLabel.Text = CurrentGameName;
// Show uninstall button only for installed games
bool isInstalled = selectedItem.ForeColor.ToArgb() == ColorInstalled.ToArgb() ||
selectedItem.ForeColor.ToArgb() == ColorUpdateAvailable.ToArgb() ||
selectedItem.ForeColor.ToArgb() == ColorDonateGame.ToArgb();
if (isInstalled && _listViewUninstallButton != null)
{
// Position the button at the right side of the selected item
Rectangle itemBounds = selectedItem.Bounds;
int buttonX = gamesListView.ClientSize.Width - _listViewUninstallButton.Width - 5;
int buttonY = itemBounds.Top + (itemBounds.Height - _listViewUninstallButton.Height) / 2;
// Ensure the button stays within visible bounds
if (buttonY >= 0 && buttonY + _listViewUninstallButton.Height <= gamesListView.ClientSize.Height)
{
_listViewUninstallButton.Location = new Point(buttonX, buttonY);
_listViewUninstallButton.Tag = selectedItem; // Store reference to the item
_listViewUninstallButton.Visible = true;
}
}
// Thumbnail
if (!keyheld)
{
@@ -6014,7 +6226,7 @@ function onYouTubeIframeAPIReady() {
var videoId = await ResolveVideoIdAsync(CurrentGameName);
if (string.IsNullOrEmpty(videoId))
{
changeTitle("No Trailer found");
changeTitle("No Trailer found", true);
ShowVideoPlaceholder();
}
else
@@ -6547,6 +6759,29 @@ function onYouTubeIframeAPIReady() {
settings.AddFavoriteGame(packageName);
UpdateFavoriteMenuItemText();
// If currently viewing favorites, refresh the list to reflect the change
bool isViewingFavorites = favoriteSwitcher.Text == "ALL";
if (isViewingFavorites)
{
var favSet = new HashSet<string>(settings.FavoritedGames, StringComparer.OrdinalIgnoreCase);
var favoriteItems = _allItems
.Where(item => item.SubItems.Count > 1 && favSet.Contains(item.SubItems[1].Text))
.ToList();
gamesListView.BeginUpdate();
gamesListView.Items.Clear();
gamesListView.Items.AddRange(favoriteItems.ToArray());
gamesListView.EndUpdate();
_galleryDataSource = favoriteItems;
if (isGalleryView && _fastGallery != null)
{
_fastGallery.RefreshFavoritesCache();
_fastGallery.UpdateItems(favoriteItems);
}
}
}
private void UpdateFavoriteMenuItemText()
@@ -7055,6 +7290,7 @@ function onYouTubeIframeAPIReady() {
_fastGallery.TileDoubleClicked += FastGallery_TileDoubleClicked;
_fastGallery.TileDeleteClicked += FastGallery_TileDeleteClicked;
_fastGallery.SortChanged += FastGallery_SortChanged;
_fastGallery.TileHovered += FastGallery_TileHovered;
// Apply current shared sort state to gallery
_fastGallery.SetSortState(_sharedSortField, _sharedSortDirection);
@@ -7088,6 +7324,14 @@ function onYouTubeIframeAPIReady() {
SaveWindowState();
}
private void FastGallery_TileHovered(object sender, string releaseName)
{
if (string.IsNullOrEmpty(releaseName)) return;
string notePath = Path.Combine(SideloaderRCLONE.NotesFolder, $"{releaseName}.txt");
UpdateReleaseNotes(notePath);
}
private void GamesGalleryView_Resize(object sender, EventArgs e)
{
if (_fastGallery != null && !_fastGallery.IsDisposed)
@@ -8500,7 +8744,7 @@ function onYouTubeIframeAPIReady() {
UpdateSideloadingUI();
}
public void UpdateSideloadingUI()
public void UpdateSideloadingUI(bool isNoDeviceDialogShown = false)
{
// Update the sideload button text
if (settings.NodeviceMode)
@@ -8517,11 +8761,19 @@ function onYouTubeIframeAPIReady() {
{
sideloadingStatusLabel.Text = "Sideloading: Disabled";
sideloadingStatusLabel.ForeColor = Color.FromArgb(255, 100, 100); // Red-ish for disabled
downloadInstallGameButton.Text = "DOWNLOAD";
}
else if (isNoDeviceDialogShown || (!DeviceConnected && !isLoading))
{
sideloadingStatusLabel.Text = "Sideloading: No Device Connected";
sideloadingStatusLabel.ForeColor = Color.FromArgb(240, 150, 50); // Orange for no device
downloadInstallGameButton.Text = "DOWNLOAD";
}
else
{
sideloadingStatusLabel.Text = "Sideloading: Enabled";
sideloadingStatusLabel.ForeColor = Color.FromArgb(93, 203, 173); // Accent green for enabled
downloadInstallGameButton.Text = "DOWNLOAD AND INSTALL";
}
}

View File

@@ -193,5 +193,6 @@
</Setting>
<Setting Name="selectedMirror" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

View File

@@ -1,13 +1,14 @@
using JR.Utils.GUI.Forms;
using AndroidSideloader.Utilities;
using JR.Utils.GUI.Forms;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Management;
using System.Net;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using AndroidSideloader.Utilities;
namespace AndroidSideloader
{
@@ -84,9 +85,12 @@ namespace AndroidSideloader
return output;
}
//Recursive sideload any apk fileD
//Recursive sideload any apk file
public static ProcessOutput RecursiveOutput = new ProcessOutput();
public static void RecursiveSideload(string FolderPath)
public static async Task RecursiveSideloadAsync(
string FolderPath,
Action<float, TimeSpan?> progressCallback = null,
Action<string> statusCallback = null)
{
try
{
@@ -94,31 +98,40 @@ namespace AndroidSideloader
{
if (Path.GetExtension(f) == ".apk")
{
RecursiveOutput += ADB.Sideload(f);
string gameName = Path.GetFileNameWithoutExtension(f);
statusCallback?.Invoke(gameName);
RecursiveOutput += await ADB.SideloadWithProgressAsync(f, progressCallback, statusCallback, "", gameName);
}
}
foreach (string d in Directory.GetDirectories(FolderPath))
{
RecursiveSideload(d);
await RecursiveSideloadAsync(d, progressCallback, statusCallback);
}
}
catch (Exception ex) { _ = Logger.Log(ex.Message, LogLevel.ERROR); }
}
//Recursive copy any obb folder
public static void RecursiveCopyOBB(string FolderPath)
public static async Task RecursiveCopyOBBAsync(
string FolderPath,
Action<float, TimeSpan?> progressCallback = null,
Action<string> statusCallback = null)
{
try
{
foreach (string f in Directory.GetFiles(FolderPath))
{
RecursiveOutput += ADB.CopyOBB(f);
}
foreach (string d in Directory.GetDirectories(FolderPath))
{
RecursiveCopyOBB(d);
string folderName = Path.GetFileName(d);
if (folderName.Contains("."))
{
statusCallback?.Invoke(folderName);
RecursiveOutput += await ADB.CopyOBBWithProgressAsync(d, progressCallback, statusCallback, folderName);
}
else
{
await RecursiveCopyOBBAsync(d, progressCallback, statusCallback);
}
}
}
catch (Exception ex) { _ = Logger.Log(ex.Message, LogLevel.ERROR); }

View File

@@ -13,7 +13,7 @@ namespace AndroidSideloader
private static readonly string RawGitHubUrl = "https://raw.githubusercontent.com/VRPirates/rookie";
public static readonly string GitHubUrl = "https://github.com/VRPirates/rookie";
public static readonly string LocalVersion = "3.0";
public static readonly string LocalVersion = "3.0.1";
public static string currentVersion = string.Empty;
public static string changelog = string.Empty;

View File

@@ -1,16 +1,12 @@
RSL 3.0
RSL 3.0.1
Major Rookie overhaul with modernized UI, significant performance improvements and upgraded UX.
- Added high-performance Gallery View with search, filters, sorting, favorites, hover animations, smooth scrolling and uninstall buttons
- Toggle seamlessly between List and Gallery views with your preference remembered across launches
- Complete UI redesign with new dark theme, modernized components and subtle animations throughout
- Refined navigation, layouts, sizing and color consistency across the entire application
- Added uninstall buttons directly in List and Gallery views for quicker app management
- Improved startup performance through overhaul of initialization logic, removal of splash screen, parallelized async loading, batched version retrieval, optimized metadata extraction and game list initialization
- Instant list filtering through caching and streamlined filter logic (INSTALLED / UPDATE AVAILABLE / NEWER THAN LIST)
- Improved search speed and responsiveness
- Fixed and improved trailer handling with faster trailer loading
- Fixed multiple startup issues including connection errors and zombie ADB instances
- Added local blacklist support allowing users to permanently suppress donation prompts for specific apps
- Reduced application size by removal of now unused assets
- Fixed popularity ranking not working on some systems
- Fixed favorites not updating immediately when removing items
- Improved YouTube trailer matching accuracy
- Implemented real-time progress updates for drag and drop operations
- Refined backup button labels and dialogs
- Gallery View: Added grouped tiles for games with multiple versions (e.g. Beat Saber)
- ListView: Uninstall button now shows on hover instead of click
- Public config file is now created automatically without prompt
- Sideloading status label now shows device connection state
- Download button text now reflects sideloading status

View File

@@ -1 +1 @@
3.0
3.0.1