Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7419426
Added download pause/resume feature with cross-session progress persi…
jp64k Apr 14, 2026
3b67e82
Updated changelog and bumped version to 3.11
jp64k Apr 14, 2026
2e71217
Updated version in Updater.cs
jp64k Apr 14, 2026
dca45df
Bumped version to 3.2
jp64k Apr 14, 2026
5d4805f
Added persistent setting to show/hide adult (18+) content (off by def…
jp64k Apr 14, 2026
70c96e4
Updated changelog.txt
jp64k Apr 14, 2026
f5f5672
Fixed reinstall freezing UI
jp64k Apr 14, 2026
b16d44d
Updated changelog.txt
jp64k Apr 14, 2026
848806d
Implemented true byte-level download resume via local rclone proxy
jp64k Apr 15, 2026
26ca870
Fixed part counter not incrementing (now counts completed files for a…
jp64k Apr 15, 2026
2576210
ChunkedDownloader: Proper disposal
jp64k Apr 15, 2026
adf071a
Refactored ChunkedDownloader to make download behavior more efficient…
jp64k Apr 15, 2026
570d134
Updated changelog.txt
jp64k Apr 15, 2026
19d2942
Revert "Updated changelog.txt"
jp64k Apr 15, 2026
95ad8b7
Revert "Refactored ChunkedDownloader to make download behavior more e…
jp64k Apr 15, 2026
a29898f
Fixed not being able to cancel paused downloads
jp64k Apr 15, 2026
05884b2
Added visual indicator for downloaded titles (gallery: badge, list: r…
jp64k Apr 15, 2026
3b7377a
Added "Delete from PC" option to right-click context menu for downloa…
jp64k Apr 15, 2026
6e3381a
Modified row tint color
jp64k Apr 15, 2026
8cf9fb5
Added subtle background tint for downloaded titles in grouped tile view
jp64k Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 26 additions & 23 deletions ADB.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,34 +336,37 @@ await Task.Run(() =>

try
{
var device = GetCurrentDevice();
var client = GetAdbClient();
var packageManager = new PackageManager(client, device);
await Task.Run(() =>
{
var device = GetCurrentDevice();
var client = GetAdbClient();
var packageManager = new PackageManager(client, device);

statusCallback?.Invoke("Backing up save data...");
_ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{packagename}\" \"{Environment.CurrentDirectory}\"");
statusCallback?.Invoke("Backing up save data...");
_ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{packagename}\" \"{Environment.CurrentDirectory}\"");

statusCallback?.Invoke("Uninstalling old version...");
packageManager.UninstallPackage(packagename);
statusCallback?.Invoke("Uninstalling old version...");
packageManager.UninstallPackage(packagename);

statusCallback?.Invoke("Reinstalling game...");
Action<InstallProgressEventArgs> reinstallProgress = (args) =>
{
if (args.State == PackageInstallProgressState.Uploading)
statusCallback?.Invoke("Reinstalling game...");
Action<InstallProgressEventArgs> reinstallProgress = (args) =>
{
if (args.State == PackageInstallProgressState.Uploading)
{
progressCallback?.Invoke((float)args.UploadProgress, null);
}
};
packageManager.InstallPackage(path, reinstallProgress);

statusCallback?.Invoke("Restoring save data...");
_ = RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{packagename}\" /sdcard/Android/data/");

string directoryToDelete = Path.Combine(Environment.CurrentDirectory, packagename);
if (Directory.Exists(directoryToDelete) && directoryToDelete != Environment.CurrentDirectory)
{
progressCallback?.Invoke((float)args.UploadProgress, null);
FileSystemUtilities.TryDeleteDirectory(directoryToDelete);
}
};
packageManager.InstallPackage(path, reinstallProgress);

statusCallback?.Invoke("Restoring save data...");
_ = RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{packagename}\" /sdcard/Android/data/");

string directoryToDelete = Path.Combine(Environment.CurrentDirectory, packagename);
if (Directory.Exists(directoryToDelete) && directoryToDelete != Environment.CurrentDirectory)
{
FileSystemUtilities.TryDeleteDirectory(directoryToDelete);
}
});

progressCallback?.Invoke(100, null);
return new ProcessOutput($"{gameName}: Reinstall: Success\n", "");
Expand Down
1 change: 1 addition & 0 deletions AndroidSideloader.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
<Compile Include="Sideloader\ProcessOutput.cs" />
<Compile Include="Sideloader\RCLONE.cs" />
<Compile Include="Sideloader\Utilities.cs" />
<Compile Include="Utilities\ChunkedDownloader.cs" />
<Compile Include="Utilities\DnsHelper.cs" />
<Compile Include="Utilities\FileSystemUtilities.cs" />
<Compile Include="Utilities\Logger.cs" />
Expand Down
23 changes: 22 additions & 1 deletion ChangelogHistory.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
RSL 3.0.1
RSL 3.1

- Introduced startup dialog to choose between online (public config or rclone config) or offline (local library) mode, also accessible anytime via dedicated 'SETUP' button; configs can be provided via local file, URL, or paste
- Local library mode (serverless): Scans your directory, calculates sizes, fetches APK version codes, and caches results per directory (allowing multiple caches for different directories)
- Necessary dependencies are downloaded early on startup; rclone is only initialized when online
- Gallery View: Visual refinements to badges, sizing, and grouped tiles; new slider to dynamically adjust tile size
- Added 'DOWNLOADED' filter to browse and install locally saved apps
- Added folder button in 'DOWNLOADED' filter for quick access to your download directory
- Added 'Open Folder' right-click context menu action when right-clicking apps in 'DOWNLOADED' view
- Renamed 'NEWER THAN LIST' filter to 'AHEAD OF SERVER'
- Reworked left-side menu, window layout, controls, labels, and tooltips
- Device and mirror switching now via dropdowns for faster access
- Dedicated 'SETTINGS' button for quicker access to rookie settings
- Device settings moved to device category
- Added 'RESET ADB AUTH' button to fix rare ADB connection issues
- Improved error handling for runtimes, configs, mirrors, and ADB connections
- Added support for alternative repos and branches
- Moved runtimes.7z hosting to GitHub
- Removed crashlog upload functionality
- Removed deprecated Metrics utility and autoUpdateConfig setting

RSL 3.0.1

- Fixed popularity ranking not working on some systems
- Fixed favorites not updating immediately when removing items
Expand Down
61 changes: 56 additions & 5 deletions GalleryView.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using AndroidSideloader;
using AndroidSideloader.Utilities;
using JR.Utils.GUI.Forms;
using System;
using System.Collections.Generic;
using System.Drawing;
Expand Down Expand Up @@ -114,6 +115,7 @@ private class GroupedTile
private static readonly Color BadgeFavoriteBg = Color.FromArgb(200, 255, 180, 0);
private static readonly Color TextColor = Color.FromArgb(245, 255, 255, 255);
private static readonly Color BadgeInstalledBg = Color.FromArgb(180, 60, 145, 230);
private static readonly Color BadgeDownloadedBg = Color.FromArgb(180, 80, 175, 150);
private static readonly Color DeleteButtonBg = Color.FromArgb(200, 180, 50, 50);
private static readonly Color DeleteButtonHoverBg = Color.FromArgb(255, 220, 70, 70);
private static readonly Color SortButtonBg = Color.FromArgb(40, 42, 48);
Expand Down Expand Up @@ -1182,11 +1184,19 @@ private void DrawGroupOverlay(Graphics g, int scrollY)
bool isExactInstalled = installedVersionCode > 0 && thisVersionCode == installedVersionCode;
bool isNewerThanInstalled = installedVersionCode > 0 && thisVersionCode > installedVersionCode;
bool isOlderThanInstalled = installedVersionCode > 0 && thisVersionCode < installedVersionCode;
bool isDownloaded = version.SubItems.Count > 1 && MainForm.DownloadedReleaseNames.Contains(version.SubItems[1].Text);

if (isDownloaded && rowRect.IntersectsWith(contentRect))
{
using (var dlPath = CreateRoundedRectangle(rowRect, 6))
using (var dlBrush = new SolidBrush(Color.FromArgb(alpha, 27, 41, 44)))
g.FillPath(dlBrush, dlPath);
}

if (isHovered && rowRect.IntersectsWith(contentRect))
{
using (var hoverPath = CreateRoundedRectangle(rowRect, 6))
using (var hoverBrush = new SolidBrush(Color.FromArgb(alpha, VersionRowHoverBg)))
using (var hoverBrush = new SolidBrush(Color.FromArgb(alpha, isDownloaded ? Color.FromArgb(45, 64, 68) : VersionRowHoverBg)))
g.FillPath(hoverBrush, hoverPath);
}

Expand Down Expand Up @@ -1388,10 +1398,12 @@ private void DrawTile(Graphics g, int index, int row, int col, int scrollY)
bool hasUpdate = tile.Versions.Any(v => v.ForeColor.ToArgb() == MainForm.ColorUpdateAvailable.ToArgb());
bool installed = IsAnyVersionInstalled(tile);
bool canDonate = tile.Versions.Any(v => v.ForeColor.ToArgb() == MainForm.ColorDonateGame.ToArgb());
bool downloaded = tile.Versions.Any(v => v.SubItems.Count > 1 && MainForm.DownloadedReleaseNames.Contains(v.SubItems[1].Text));

if (hasUpdate) { DrawBadge(g, "UPDATE", x + 4, badgeY, Color.FromArgb(180, MainForm.ColorUpdateAvailable)); badgeY += 15; }
if (canDonate) { DrawBadge(g, "AHEAD", x + 4, badgeY, Color.FromArgb(180, MainForm.ColorDonateGame)); badgeY += 15; }
if (installed) DrawBadge(g, "INSTALLED", x + 4, badgeY, BadgeInstalledBg);
if (installed) { DrawBadge(g, "INSTALLED", x + 4, badgeY, BadgeInstalledBg); badgeY += 15; }
if (downloaded) DrawBadge(g, "DOWNLOADED", x + 4, badgeY, BadgeDownloadedBg);

// Right-side badges (top)
int rightBadgeY = y + 4;
Expand Down Expand Up @@ -1885,6 +1897,10 @@ private void CreateContextMenu()
openFolderItem.Click += ContextMenu_OpenFolderClick;
_contextMenu.Items.Add(openFolderItem);

var deleteFolderItem = new ToolStripMenuItem("🗑 Delete from PC");
deleteFolderItem.Click += ContextMenu_DeleteFolderClick;
_contextMenu.Items.Add(deleteFolderItem);

_contextMenu.Opening += ContextMenu_Opening;
}

Expand All @@ -1907,15 +1923,17 @@ private void ContextMenu_Opening(object sender, System.ComponentModel.CancelEven
bool isFavorite = _favoritesCache.Contains(packageName);
((ToolStripMenuItem)_contextMenu.Items[0]).Text = isFavorite ? "Remove from Favorites" : "★ Add to Favorites";

// Show "Open Folder" only if the game folder exists locally
if (_contextMenu.Items.Count > 1)
// Show "Open Folder" / "Delete from PC" if the game folder exists locally
if (_contextMenu.Items.Count > 2)
{
string releaseName = targetItem.SubItems.Count > SideloaderRCLONE.ReleaseNameIndex
? targetItem.SubItems[SideloaderRCLONE.ReleaseNameIndex].Text : "";
var settings = SettingsManager.Instance;
string dlDir = settings.CustomDownloadDir ? settings.DownloadDir : Environment.CurrentDirectory;
string folderPath = Path.Combine(dlDir, releaseName);
_contextMenu.Items[1].Visible = !string.IsNullOrEmpty(releaseName) && Directory.Exists(folderPath);
bool folderExists = !string.IsNullOrEmpty(releaseName) && Directory.Exists(folderPath);
_contextMenu.Items[1].Visible = folderExists;
_contextMenu.Items[2].Visible = folderExists;
}
}

Expand Down Expand Up @@ -1978,6 +1996,39 @@ private void ContextMenu_OpenFolderClick(object sender, EventArgs e)
MainForm.OpenDirectory(folderPath);
}

private void ContextMenu_DeleteFolderClick(object sender, EventArgs e)
{
if (_rightClickedIndex < 0 || _rightClickedIndex >= _displayTiles.Count) return;

var tile = _displayTiles[_rightClickedIndex];
ListViewItem targetItem = (_rightClickedVersionIndex >= 0 && _rightClickedVersionIndex < tile.Versions.Count)
? tile.Versions[_rightClickedVersionIndex] : tile.Primary;

if (targetItem.SubItems.Count <= SideloaderRCLONE.ReleaseNameIndex) return;

string releaseName = targetItem.SubItems[SideloaderRCLONE.ReleaseNameIndex].Text;
var settings = SettingsManager.Instance;
string dlDir = settings.CustomDownloadDir ? settings.DownloadDir : Environment.CurrentDirectory;
string folderPath = Path.Combine(dlDir, releaseName);

if (!Directory.Exists(folderPath)) return;

DialogResult confirm = FlexibleMessageBox.Show(FindForm(),
$"Delete downloaded files for {releaseName}?\n\nFiles will be moved to the Recycle Bin.",
"Delete Downloaded Files?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);

if (confirm != DialogResult.Yes) return;

if (!FileSystemUtilities.MoveToRecycleBin(folderPath))
{
MessageBox.Show("Failed to move folder to Recycle Bin.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}

var mainForm = FindForm() as MainForm;
mainForm?.RefreshDownloadedState();
}

private void RemoveVersionFromDisplay(GroupedTile tile, ListViewItem item, int tileIndex)
{
_items.Remove(item);
Expand Down
14 changes: 13 additions & 1 deletion MainForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading