Compare commits
59 Commits
v3.0-beta4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15efd91769 | ||
|
|
28fe132012 | ||
|
|
db93c7b610 | ||
|
|
3bae614d7e | ||
|
|
3e3a452ceb | ||
|
|
4dc11191f0 | ||
|
|
3e626c6390 | ||
|
|
5b44295418 | ||
|
|
95b9059bed | ||
|
|
163a0d9fce | ||
|
|
af84f2cf8c | ||
|
|
9f2e824df2 | ||
|
|
f21efcd476 | ||
|
|
50b52d963b | ||
|
|
61e6c143fa | ||
|
|
60d68cbc8c | ||
|
|
93ed359ed1 | ||
|
|
8bd7efc22e | ||
|
|
07957d93fe | ||
|
|
f91ee27c35 | ||
|
|
d8cc5dc539 | ||
|
|
ed17322983 | ||
|
|
0970b73fe7 | ||
|
|
7cc437309a | ||
|
|
a050d82c8b | ||
|
|
4c5a651b2b | ||
|
|
af17f5bb12 | ||
|
|
670ace1963 | ||
|
|
77cfb81545 | ||
|
|
3d241cc634 | ||
|
|
187f1d3689 | ||
|
|
6ccaf06ab4 | ||
|
|
f289026438 | ||
|
|
de6b0e4c70 | ||
|
|
debb204719 | ||
|
|
f80d5f841c | ||
|
|
38a1968c42 | ||
|
|
b92def0283 | ||
|
|
62343f8cf6 | ||
|
|
fdb091cee6 | ||
|
|
3993e574a8 | ||
|
|
fb36826091 | ||
|
|
84ddce1423 | ||
|
|
f363550351 | ||
|
|
02ab0f0e2a | ||
|
|
5819bc8083 | ||
|
|
311f0cfb7e | ||
|
|
2b770b30a8 | ||
|
|
e6d178cb2a | ||
|
|
c2f5b20e83 | ||
|
|
952251be35 | ||
|
|
385fe45d5d | ||
|
|
5db0ea301d | ||
|
|
523efba81c | ||
|
|
06dc91f130 | ||
|
|
252955da0a | ||
|
|
bd193c0d3f | ||
|
|
630fe98d31 | ||
|
|
0edc1deed0 |
165
ADB.cs
165
ADB.cs
@@ -72,7 +72,7 @@ namespace AndroidSideloader
|
||||
return _currentDevice;
|
||||
}
|
||||
|
||||
public static ProcessOutput RunAdbCommandToString(string command)
|
||||
public static ProcessOutput RunAdbCommandToString(string command, bool suppressLogging = false)
|
||||
{
|
||||
command = command.Replace("adb", "");
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace AndroidSideloader
|
||||
command = $" -s {DeviceID} {command}";
|
||||
}
|
||||
|
||||
if (!command.Contains("dumpsys") && !command.Contains("shell pm list packages") && !command.Contains("KEYCODE_WAKEUP"))
|
||||
if (!suppressLogging && !command.Contains("dumpsys") && !command.Contains("shell pm list packages") && !command.Contains("KEYCODE_WAKEUP"))
|
||||
{
|
||||
string logcmd = command;
|
||||
if (logcmd.Contains(Environment.CurrentDirectory))
|
||||
@@ -95,6 +95,9 @@ namespace AndroidSideloader
|
||||
_ = Logger.Log($"Running command: {logcmd}");
|
||||
}
|
||||
|
||||
bool isConnectCommand = command.Contains("connect");
|
||||
int timeoutMs = isConnectCommand ? 5000 : -1; // 5 second timeout for connect commands
|
||||
|
||||
using (Process adb = new Process())
|
||||
{
|
||||
adb.StartInfo.FileName = adbFilePath;
|
||||
@@ -111,19 +114,39 @@ namespace AndroidSideloader
|
||||
|
||||
try
|
||||
{
|
||||
output = adb.StandardOutput.ReadToEnd();
|
||||
error = adb.StandardError.ReadToEnd();
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (command.Contains("connect"))
|
||||
{
|
||||
bool graceful = adb.WaitForExit(3000);
|
||||
if (!graceful)
|
||||
if (isConnectCommand)
|
||||
{
|
||||
adb.Kill();
|
||||
adb.WaitForExit();
|
||||
// For connect commands, we use async reading with timeout to avoid blocking on TCP timeout
|
||||
var outputTask = adb.StandardOutput.ReadToEndAsync();
|
||||
var errorTask = adb.StandardError.ReadToEndAsync();
|
||||
|
||||
bool exited = adb.WaitForExit(timeoutMs);
|
||||
|
||||
if (!exited)
|
||||
{
|
||||
try { adb.Kill(); } catch { }
|
||||
adb.WaitForExit(1000);
|
||||
output = "Connection timed out";
|
||||
error = "cannot connect: Connection timed out";
|
||||
Logger.Log($"ADB connect command timed out after {timeoutMs}ms", LogLevel.WARNING);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Process exited within timeout, safe to read output
|
||||
output = outputTask.Result;
|
||||
error = errorTask.Result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-connect commands, read output normally
|
||||
output = adb.StandardOutput.ReadToEnd();
|
||||
error = adb.StandardError.ReadToEnd();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"Error reading ADB output: {ex.Message}", LogLevel.WARNING);
|
||||
}
|
||||
|
||||
if (error.Contains("ADB_VENDOR_KEYS") && !settings.AdbDebugWarned)
|
||||
@@ -134,7 +157,7 @@ namespace AndroidSideloader
|
||||
{
|
||||
_ = FlexibleMessageBox.Show(Program.form, "There is not enough room on your device to install this package. Please clear AT LEAST 2x the amount of the app you are trying to install.");
|
||||
}
|
||||
if (!output.Contains("version") && !output.Contains("KEYCODE_WAKEUP") && !output.Contains("Filesystem") && !output.Contains("package:") && !output.Equals(null))
|
||||
if (!suppressLogging && !output.Contains("version") && !output.Contains("KEYCODE_WAKEUP") && !output.Contains("Filesystem") && !output.Contains("package:") && !output.Equals(null))
|
||||
{
|
||||
_ = Logger.Log(output);
|
||||
}
|
||||
@@ -264,18 +287,38 @@ namespace AndroidSideloader
|
||||
{
|
||||
Logger.Log($"SideloadWithProgressAsync error: {ex.Message}", LogLevel.ERROR);
|
||||
|
||||
if (ex.Message.Contains("INSTALL_FAILED") ||
|
||||
ex.Message.Contains("signatures do not match"))
|
||||
// Signature mismatches and version downgrades can be fixed by reinstalling
|
||||
bool isReinstallEligible = ex.Message.Contains("signatures do not match") ||
|
||||
ex.Message.Contains("INSTALL_FAILED_VERSION_DOWNGRADE") ||
|
||||
ex.Message.Contains("failed to install");
|
||||
|
||||
// For insufficient storage, offer reinstall if it's an upgrade
|
||||
// As uninstalling old version frees space for the new one
|
||||
bool isStorageIssue = ex.Message.Contains("INSUFFICIENT_STORAGE");
|
||||
bool isUpgrade = !string.IsNullOrEmpty(packagename) &&
|
||||
settings.InstalledApps.Contains(packagename);
|
||||
|
||||
if (isStorageIssue && isUpgrade)
|
||||
{
|
||||
isReinstallEligible = true;
|
||||
}
|
||||
|
||||
if (isReinstallEligible)
|
||||
{
|
||||
bool cancelClicked = false;
|
||||
|
||||
if (!settings.AutoReinstall)
|
||||
{
|
||||
string message = isStorageIssue
|
||||
? "Installation failed due to insufficient storage. Since this is an upgrade, Rookie can uninstall the old version first to free up space, then install the new version.\n\nRookie will also 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 has failed. Rookie will 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?";
|
||||
|
||||
string title = isStorageIssue ? "Insufficient Storage" : "In place upgrade failed";
|
||||
|
||||
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);
|
||||
message, title, MessageBoxButtons.OKCancel);
|
||||
if (dialogResult1 == DialogResult.Cancel)
|
||||
cancelClicked = true;
|
||||
});
|
||||
@@ -293,7 +336,7 @@ namespace AndroidSideloader
|
||||
var packageManager = new PackageManager(client, device);
|
||||
|
||||
statusCallback?.Invoke("Backing up save data...");
|
||||
_ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\"");
|
||||
_ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{packagename}\" \"{Environment.CurrentDirectory}\"");
|
||||
|
||||
statusCallback?.Invoke("Uninstalling old version...");
|
||||
packageManager.UninstallPackage(packagename);
|
||||
@@ -309,12 +352,12 @@ namespace AndroidSideloader
|
||||
packageManager.InstallPackage(path, reinstallProgress);
|
||||
|
||||
statusCallback?.Invoke("Restoring save data...");
|
||||
_ = RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{MainForm.CurrPCKG}\" /sdcard/Android/data/");
|
||||
_ = RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{packagename}\" /sdcard/Android/data/");
|
||||
|
||||
string directoryToDelete = Path.Combine(Environment.CurrentDirectory, MainForm.CurrPCKG);
|
||||
string directoryToDelete = Path.Combine(Environment.CurrentDirectory, packagename);
|
||||
if (Directory.Exists(directoryToDelete) && directoryToDelete != Environment.CurrentDirectory)
|
||||
{
|
||||
Directory.Delete(directoryToDelete, true);
|
||||
FileSystemUtilities.TryDeleteDirectory(directoryToDelete);
|
||||
}
|
||||
|
||||
progressCallback?.Invoke(100, null);
|
||||
@@ -322,11 +365,12 @@ namespace AndroidSideloader
|
||||
}
|
||||
catch (Exception reinstallEx)
|
||||
{
|
||||
return new ProcessOutput($"{gameName}: Reinstall: Failed: {reinstallEx.Message}\n");
|
||||
return new ProcessOutput("", $"{gameName}: Reinstall Failed: {reinstallEx.Message}\n");
|
||||
}
|
||||
}
|
||||
|
||||
return new ProcessOutput("", ex.Message);
|
||||
// Return the error message so it's displayed to the user
|
||||
return new ProcessOutput("", $"\n{gameName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,79 +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"))
|
||||
{
|
||||
_ = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), $"Rookie Backups");
|
||||
_ = Logger.Log(out2);
|
||||
|
||||
if (out2.Contains("offline") && !settings.NodeviceMode)
|
||||
{
|
||||
DialogResult dialogResult2 = FlexibleMessageBox.Show(Program.form, "Device is offline. Press Yes to reconnect, or if you don't wish to connect and just want to download the game (requires unchecking \"Delete games after install\" from settings menu) then press No.", "Device offline.", MessageBoxButtons.YesNoCancel);
|
||||
}
|
||||
|
||||
if (out2.Contains($"signatures do not match previously") || out2.Contains("INSTALL_FAILED_VERSION_DOWNGRADE") || out2.Contains("signatures do not match") || out2.Contains("failed to install"))
|
||||
{
|
||||
ret.Error = string.Empty;
|
||||
ret.Output = string.Empty;
|
||||
|
||||
bool cancelClicked = false;
|
||||
|
||||
if (!settings.AutoReinstall)
|
||||
{
|
||||
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)
|
||||
{
|
||||
Directory.Delete(directoryToDelete, true);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -194,6 +194,9 @@
|
||||
<Compile Include="ModernProgessBar.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ModernQueuePanel.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
@@ -241,6 +244,7 @@
|
||||
<Compile Include="Sideloader\RCLONE.cs" />
|
||||
<Compile Include="Sideloader\Utilities.cs" />
|
||||
<Compile Include="Utilities\DnsHelper.cs" />
|
||||
<Compile Include="Utilities\FileSystemUtilities.cs" />
|
||||
<Compile Include="Utilities\Logger.cs" />
|
||||
<Compile Include="QuestForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
|
||||
@@ -200,6 +200,9 @@
|
||||
<setting name="proxyPort" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
<setting name="selectedMirror" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
</AndroidSideloader.Properties.Settings>
|
||||
<AndroidADB.Sideloader.Properties.Settings>
|
||||
<setting name="checkForUpdates" serializeAs="String">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -83,7 +83,18 @@ namespace JR.Utils.GUI.Forms
|
||||
private const int CS_DROPSHADOW = 0x00020000;
|
||||
private const int WM_NCLBUTTONDOWN = 0xA1;
|
||||
private const int HT_CAPTION = 0x2;
|
||||
private const int BORDER_RADIUS = 12;
|
||||
private const uint FLASHW_TRAY = 0x00000002;
|
||||
private const uint FLASHW_TIMERNOFG = 0x0000000C;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct FLASHWINFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public IntPtr hwnd;
|
||||
public uint dwFlags;
|
||||
public uint uCount;
|
||||
public uint dwTimeout;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
|
||||
@@ -91,6 +102,9 @@ namespace JR.Utils.GUI.Forms
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ReleaseCapture();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool FlashWindowEx(ref FLASHWINFO pwfi);
|
||||
|
||||
protected override CreateParams CreateParams
|
||||
{
|
||||
get
|
||||
@@ -604,7 +618,7 @@ namespace JR.Utils.GUI.Forms
|
||||
ownerForm = Form.ActiveForm;
|
||||
}
|
||||
|
||||
if (ownerForm != null && ownerForm.Visible)
|
||||
if (ownerForm != null && ownerForm.Visible && ownerForm.WindowState != FormWindowState.Minimized)
|
||||
{
|
||||
// Center relative to owner window
|
||||
int x = ownerForm.Left + (ownerForm.Width - flexibleMessageBoxForm.Width) / 2;
|
||||
@@ -620,7 +634,7 @@ namespace JR.Utils.GUI.Forms
|
||||
}
|
||||
else
|
||||
{
|
||||
// No owner found: center on current screen
|
||||
// No owner found or minimized: center on current screen
|
||||
CenterOnScreen(flexibleMessageBoxForm);
|
||||
}
|
||||
}
|
||||
@@ -848,8 +862,45 @@ namespace JR.Utils.GUI.Forms
|
||||
int contentWidth = flexibleMessageBoxForm.ClientSize.Width - 16; // 8px padding
|
||||
flexibleMessageBoxForm.titlePanel.Width = contentWidth;
|
||||
|
||||
// Get owner form
|
||||
Form ownerForm = owner as Form ?? Form.ActiveForm;
|
||||
bool ownerWasMinimized = ownerForm != null && ownerForm.WindowState == FormWindowState.Minimized;
|
||||
|
||||
SetDialogStartPosition(flexibleMessageBoxForm, owner);
|
||||
|
||||
// If owner was minimized, reposition dialog when owner is restored
|
||||
if (ownerWasMinimized && ownerForm != null)
|
||||
{
|
||||
EventHandler resizeHandler = null;
|
||||
resizeHandler = (s, e) =>
|
||||
{
|
||||
if (ownerForm.WindowState != FormWindowState.Minimized && flexibleMessageBoxForm.Visible && !flexibleMessageBoxForm.IsDisposed)
|
||||
{
|
||||
SetDialogStartPosition(flexibleMessageBoxForm, owner);
|
||||
ownerForm.Resize -= resizeHandler;
|
||||
}
|
||||
};
|
||||
ownerForm.Resize += resizeHandler;
|
||||
}
|
||||
|
||||
// Flash taskbar if application is inactive or minimized
|
||||
if (Form.ActiveForm == null || ownerWasMinimized)
|
||||
{
|
||||
Form targetForm = ownerForm ?? Application.OpenForms.Cast<Form>().FirstOrDefault(f => f != null && !f.IsDisposed && f != flexibleMessageBoxForm && f.Visible);
|
||||
if (targetForm != null && !targetForm.IsDisposed)
|
||||
{
|
||||
FLASHWINFO info = new FLASHWINFO
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf(typeof(FLASHWINFO)),
|
||||
hwnd = targetForm.Handle,
|
||||
dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG,
|
||||
uCount = 5,
|
||||
dwTimeout = 0
|
||||
};
|
||||
_ = FlashWindowEx(ref info);
|
||||
}
|
||||
}
|
||||
|
||||
return flexibleMessageBoxForm.ShowDialog(owner);
|
||||
}
|
||||
|
||||
|
||||
1340
GalleryView.cs
1340
GalleryView.cs
File diff suppressed because it is too large
Load Diff
272
MainForm.Designer.cs
generated
272
MainForm.Designer.cs
generated
@@ -49,7 +49,6 @@ namespace AndroidSideloader
|
||||
this.DownloadsIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.gamesQueueLabel = new System.Windows.Forms.Label();
|
||||
this.notesRichTextBox = new System.Windows.Forms.RichTextBox();
|
||||
this.DragDropLbl = new System.Windows.Forms.Label();
|
||||
this.lblNotes = new System.Windows.Forms.Label();
|
||||
this.gamesPictureBox = new System.Windows.Forms.PictureBox();
|
||||
this.startsideloadbutton_Tooltip = new System.Windows.Forms.ToolTip(this.components);
|
||||
@@ -91,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();
|
||||
@@ -120,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();
|
||||
@@ -131,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();
|
||||
@@ -152,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
|
||||
@@ -166,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;
|
||||
@@ -215,10 +214,6 @@ namespace AndroidSideloader
|
||||
this.gamesQueListBox.Name = "gamesQueListBox";
|
||||
this.gamesQueListBox.Size = new System.Drawing.Size(266, 192);
|
||||
this.gamesQueListBox.TabIndex = 9;
|
||||
this.gamesQueListBox.MouseClick += new System.Windows.Forms.MouseEventHandler(this.gamesQueListBox_MouseClick);
|
||||
this.gamesQueListBox.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.gamesQueListBox_DrawItem);
|
||||
this.gamesQueListBox.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop);
|
||||
this.gamesQueListBox.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter);
|
||||
//
|
||||
// devicesComboBox
|
||||
//
|
||||
@@ -230,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;
|
||||
@@ -246,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);
|
||||
@@ -344,8 +339,8 @@ namespace AndroidSideloader
|
||||
| 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;
|
||||
this.notesRichTextBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F);
|
||||
this.notesRichTextBox.ForeColor = System.Drawing.Color.White;
|
||||
this.notesRichTextBox.Font = new System.Drawing.Font("Segoe UI", 8.5F, System.Drawing.FontStyle.Italic);
|
||||
this.notesRichTextBox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(140)))), ((int)(((byte)(140)))));
|
||||
this.notesRichTextBox.HideSelection = false;
|
||||
this.notesRichTextBox.Location = new System.Drawing.Point(954, 496);
|
||||
this.notesRichTextBox.Name = "notesRichTextBox";
|
||||
@@ -354,24 +349,11 @@ namespace AndroidSideloader
|
||||
this.notesRichTextBox.ShowSelectionMargin = true;
|
||||
this.notesRichTextBox.Size = new System.Drawing.Size(265, 192);
|
||||
this.notesRichTextBox.TabIndex = 10;
|
||||
this.notesRichTextBox.Text = "Tip: Press F1 to see all shortcuts";
|
||||
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);
|
||||
//
|
||||
// DragDropLbl
|
||||
//
|
||||
this.DragDropLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.DragDropLbl.AutoSize = true;
|
||||
this.DragDropLbl.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(20)))), ((int)(((byte)(20)))), ((int)(((byte)(20)))));
|
||||
this.DragDropLbl.DataBindings.Add(new System.Windows.Forms.Binding("ForeColor", global::AndroidSideloader.Properties.Settings.Default, "FontColor", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
|
||||
this.DragDropLbl.Font = new System.Drawing.Font("Microsoft Sans Serif", 36F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.DragDropLbl.ForeColor = global::AndroidSideloader.Properties.Settings.Default.FontColor;
|
||||
this.DragDropLbl.Location = new System.Drawing.Point(620, 561);
|
||||
this.DragDropLbl.Name = "DragDropLbl";
|
||||
this.DragDropLbl.Size = new System.Drawing.Size(320, 55);
|
||||
this.DragDropLbl.TabIndex = 25;
|
||||
this.DragDropLbl.Text = "DragDropLBL";
|
||||
this.DragDropLbl.Visible = false;
|
||||
//
|
||||
// lblNotes
|
||||
//
|
||||
this.lblNotes.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
@@ -476,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;
|
||||
@@ -496,9 +478,9 @@ 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 GAMEDATA";
|
||||
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 sideloader folder (Does not save APKs or OBBs)");
|
||||
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;
|
||||
this.backupbutton.Click += new System.EventHandler(this.backupbutton_Click);
|
||||
//
|
||||
@@ -516,9 +498,9 @@ namespace AndroidSideloader
|
||||
this.restorebutton.Padding = new System.Windows.Forms.Padding(30, 0, 0, 0);
|
||||
this.restorebutton.Size = new System.Drawing.Size(233, 28);
|
||||
this.restorebutton.TabIndex = 0;
|
||||
this.restorebutton.Text = "RESTORE GAMEDATA";
|
||||
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 GAMEDATA 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);
|
||||
//
|
||||
@@ -777,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;
|
||||
@@ -1267,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)))));
|
||||
@@ -1334,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)))));
|
||||
@@ -1517,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
|
||||
//
|
||||
@@ -1580,7 +1562,6 @@ namespace AndroidSideloader
|
||||
this.Controls.Add(this.ULLabel);
|
||||
this.Controls.Add(this.tableLayoutPanel1);
|
||||
this.Controls.Add(this.progressDLbtnContainer);
|
||||
this.Controls.Add(this.DragDropLbl);
|
||||
this.Controls.Add(this.lblNotes);
|
||||
this.Controls.Add(this.gamesQueueLabel);
|
||||
this.Controls.Add(this.gamesQueListBox);
|
||||
@@ -1619,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();
|
||||
|
||||
@@ -1642,7 +1623,6 @@ namespace AndroidSideloader
|
||||
private System.Windows.Forms.PictureBox gamesPictureBox;
|
||||
private System.Windows.Forms.Label gamesQueueLabel;
|
||||
private System.Windows.Forms.RichTextBox notesRichTextBox;
|
||||
private System.Windows.Forms.Label DragDropLbl;
|
||||
private System.Windows.Forms.Label lblNotes;
|
||||
public System.Windows.Forms.ComboBox remotesList;
|
||||
public System.Windows.Forms.ColumnHeader GameNameIndex;
|
||||
|
||||
3536
MainForm.cs
3536
MainForm.cs
File diff suppressed because it is too large
Load Diff
@@ -122,9 +122,31 @@ namespace AndroidSideloader
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern bool DeleteObject(IntPtr hObject);
|
||||
|
||||
private bool _suppressHeader = true;
|
||||
|
||||
public bool SuppressHeader
|
||||
{
|
||||
get => _suppressHeader;
|
||||
set
|
||||
{
|
||||
if (_suppressHeader == value) return;
|
||||
_suppressHeader = value;
|
||||
|
||||
// Invalidate ListView and header control
|
||||
_listView.Invalidate();
|
||||
if (_headerCursor.Handle != IntPtr.Zero)
|
||||
{
|
||||
InvalidateRect(_headerCursor.Handle, IntPtr.Zero, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct HDHITTESTINFO
|
||||
{
|
||||
@@ -178,6 +200,57 @@ namespace AndroidSideloader
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
const int WM_LBUTTONDOWN = 0x0201;
|
||||
const int WM_LBUTTONUP = 0x0202;
|
||||
const int WM_LBUTTONDBLCLK = 0x0203;
|
||||
const int WM_RBUTTONDOWN = 0x0204;
|
||||
const int WM_RBUTTONUP = 0x0205;
|
||||
|
||||
// Block mouse interaction when header is suppressed, but still handle layout
|
||||
if (_owner._suppressHeader)
|
||||
{
|
||||
// Block mouse clicks
|
||||
if (m.Msg == WM_LBUTTONDOWN || m.Msg == WM_LBUTTONUP || m.Msg == WM_LBUTTONDBLCLK ||
|
||||
m.Msg == WM_RBUTTONDOWN || m.Msg == WM_RBUTTONUP)
|
||||
{
|
||||
m.Result = IntPtr.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m.Msg == WM_SETCURSOR)
|
||||
{
|
||||
Cursor.Current = Cursors.Default;
|
||||
m.Result = (IntPtr)1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Still handle HDM_LAYOUT to maintain custom header height
|
||||
if (m.Msg == HDM_LAYOUT)
|
||||
{
|
||||
base.WndProc(ref m);
|
||||
|
||||
try
|
||||
{
|
||||
HDLAYOUT hdl = Marshal.PtrToStructure<HDLAYOUT>(m.LParam);
|
||||
WINDOWPOS wpos = Marshal.PtrToStructure<WINDOWPOS>(hdl.pwpos);
|
||||
RECT rc = Marshal.PtrToStructure<RECT>(hdl.prc);
|
||||
|
||||
wpos.cy = CUSTOM_HEADER_HEIGHT;
|
||||
rc.top = CUSTOM_HEADER_HEIGHT;
|
||||
|
||||
Marshal.StructureToPtr(wpos, hdl.pwpos, false);
|
||||
Marshal.StructureToPtr(rc, hdl.prc, false);
|
||||
}
|
||||
catch { }
|
||||
|
||||
m.Result = (IntPtr)1;
|
||||
return;
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m.Msg == WM_ERASEBKGND)
|
||||
{
|
||||
m.Result = (IntPtr)1;
|
||||
@@ -610,6 +683,14 @@ namespace AndroidSideloader
|
||||
var g = e.Graphics;
|
||||
int listViewWidth = _listView.ClientSize.Width;
|
||||
|
||||
// During loading, just fill with background color
|
||||
if (_suppressHeader)
|
||||
{
|
||||
g.FillRectangle(RowNormalBrush, new Rectangle(0, e.Bounds.Y, listViewWidth, e.Bounds.Height));
|
||||
e.DrawDefault = false;
|
||||
return;
|
||||
}
|
||||
|
||||
g.FillRectangle(HeaderBgBrush, e.Bounds);
|
||||
|
||||
if (e.ColumnIndex == _listView.Columns.Count - 1)
|
||||
@@ -1187,6 +1268,28 @@ namespace AndroidSideloader
|
||||
_listView.RedrawItems(_marqueeSelectedIndex, _marqueeSelectedIndex, true);
|
||||
}
|
||||
|
||||
public void ApplySort(int columnIndex, SortOrder order)
|
||||
{
|
||||
if (_columnSorter == null) return;
|
||||
|
||||
_columnSorter.SortColumn = columnIndex;
|
||||
_columnSorter.Order = order;
|
||||
|
||||
_listView.BeginUpdate();
|
||||
try
|
||||
{
|
||||
_listView.Sort();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_listView.EndUpdate();
|
||||
}
|
||||
|
||||
// Invalidate header to update sort indicators
|
||||
_listView.Invalidate(new Rectangle(0, 0, _listView.ClientSize.Width,
|
||||
_listView.Font.Height + 8));
|
||||
}
|
||||
|
||||
private static float Ease(float p)
|
||||
{
|
||||
p = Clamp01(p);
|
||||
|
||||
511
ModernQueuePanel.cs
Normal file
511
ModernQueuePanel.cs
Normal file
@@ -0,0 +1,511 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace AndroidSideloader
|
||||
{
|
||||
// Modern download queue panel with drag-reorder, cancel buttons
|
||||
// and custom scrollbar with auto-scrolling during drag
|
||||
public sealed class ModernQueuePanel : Control
|
||||
{
|
||||
// Layout constants
|
||||
private const int ItemHeight = 28, ItemMargin = 4, ItemRadius = 5;
|
||||
private const int XButtonSize = 18, DragHandleWidth = 20, TextPadding = 6;
|
||||
private const int ScrollbarWidth = 6, ScrollbarWidthHover = 8, ScrollbarMargin = 2;
|
||||
private const int ScrollbarRadius = 3, MinThumbHeight = 20;
|
||||
private const int AutoScrollZoneHeight = 30, AutoScrollSpeed = 3;
|
||||
|
||||
// Color palette
|
||||
private static readonly Color BgColor = Color.FromArgb(24, 26, 30);
|
||||
private static readonly Color ItemBg = Color.FromArgb(32, 36, 44);
|
||||
private static readonly Color ItemHoverBg = Color.FromArgb(42, 46, 54);
|
||||
private static readonly Color ItemDragBg = Color.FromArgb(45, 55, 70);
|
||||
private static readonly Color TextColor = Color.FromArgb(210, 210, 210);
|
||||
private static readonly Color TextDimColor = Color.FromArgb(140, 140, 140);
|
||||
private static readonly Color AccentColor = Color.FromArgb(93, 203, 173);
|
||||
private static readonly Color XButtonBg = Color.FromArgb(55, 60, 70);
|
||||
private static readonly Color XButtonHoverBg = Color.FromArgb(200, 60, 60);
|
||||
private static readonly Color GripColor = Color.FromArgb(70, 75, 85);
|
||||
private static readonly Color ItemDragBorder = Color.FromArgb(55, 65, 80);
|
||||
private static readonly Color ScrollTrackColor = Color.FromArgb(35, 38, 45);
|
||||
private static readonly Color ScrollThumbColor = Color.FromArgb(70, 75, 85);
|
||||
private static readonly Color ScrollThumbHoverColor = Color.FromArgb(90, 95, 105);
|
||||
private static readonly Color ScrollThumbDragColor = Color.FromArgb(110, 115, 125);
|
||||
|
||||
private readonly List<string> _items = new List<string>();
|
||||
private readonly Timer _autoScrollTimer;
|
||||
|
||||
// State tracking
|
||||
private int _hoveredIndex = -1, _dragIndex = -1, _dropIndex = -1, _scrollOffset;
|
||||
private bool _hoveringX, _scrollbarHovered, _scrollbarDragging;
|
||||
private int _scrollDragStartY, _scrollDragStartOffset, _autoScrollDirection;
|
||||
private Rectangle _scrollThumbRect, _scrollTrackRect;
|
||||
|
||||
public event EventHandler<int> ItemRemoved;
|
||||
public event EventHandler<ReorderEventArgs> ItemReordered;
|
||||
|
||||
public ModernQueuePanel()
|
||||
{
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint |
|
||||
ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
|
||||
BackColor = BgColor;
|
||||
_autoScrollTimer = new Timer { Interval = 16 }; // ~60 FPS
|
||||
_autoScrollTimer.Tick += (s, e) => HandleAutoScroll();
|
||||
}
|
||||
|
||||
public bool IsDownloading { get; set; }
|
||||
public int Count => _items.Count;
|
||||
private int ContentHeight => _items.Count * (ItemHeight + ItemMargin) + ItemMargin;
|
||||
private int MaxScroll => Math.Max(0, ContentHeight - Height);
|
||||
private bool ScrollbarVisible => ContentHeight > Height;
|
||||
|
||||
public void SetItems(IEnumerable<string> items)
|
||||
{
|
||||
_items.Clear();
|
||||
_items.AddRange(items);
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void ResetState()
|
||||
{
|
||||
_hoveredIndex = _dragIndex = -1;
|
||||
ClampScroll();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void ClampScroll() =>
|
||||
_scrollOffset = Math.Max(0, Math.Min(MaxScroll, _scrollOffset));
|
||||
|
||||
// Auto-scroll when dragging near edges
|
||||
private void HandleAutoScroll()
|
||||
{
|
||||
if (_dragIndex < 0 || _autoScrollDirection == 0)
|
||||
{
|
||||
_autoScrollTimer.Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
int oldOffset = _scrollOffset;
|
||||
_scrollOffset += _autoScrollDirection * AutoScrollSpeed;
|
||||
ClampScroll();
|
||||
|
||||
if (_scrollOffset != oldOffset)
|
||||
{
|
||||
UpdateDropIndex(PointToClient(MousePosition).Y);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAutoScroll(int mouseY)
|
||||
{
|
||||
if (_dragIndex < 0 || MaxScroll <= 0)
|
||||
{
|
||||
StopAutoScroll();
|
||||
return;
|
||||
}
|
||||
|
||||
_autoScrollDirection = mouseY < AutoScrollZoneHeight && _scrollOffset > 0 ? -1 :
|
||||
mouseY > Height - AutoScrollZoneHeight && _scrollOffset < MaxScroll ? 1 : 0;
|
||||
|
||||
if (_autoScrollDirection != 0 && !_autoScrollTimer.Enabled)
|
||||
_autoScrollTimer.Start();
|
||||
else if (_autoScrollDirection == 0)
|
||||
_autoScrollTimer.Stop();
|
||||
}
|
||||
|
||||
private void StopAutoScroll()
|
||||
{
|
||||
_autoScrollDirection = 0;
|
||||
_autoScrollTimer.Stop();
|
||||
}
|
||||
|
||||
private void UpdateDropIndex(int mouseY) =>
|
||||
_dropIndex = Math.Max(1, Math.Min(_items.Count, (mouseY + _scrollOffset + ItemHeight / 2) / (ItemHeight + ItemMargin)));
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
var g = e.Graphics;
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
g.Clear(BgColor);
|
||||
|
||||
if (_items.Count == 0)
|
||||
{
|
||||
DrawEmptyState(g);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw visible items
|
||||
for (int i = 0; i < _items.Count; i++)
|
||||
{
|
||||
var rect = GetItemRect(i);
|
||||
if (rect.Bottom >= 0 && rect.Top <= Height)
|
||||
DrawItem(g, i, rect);
|
||||
}
|
||||
|
||||
// Draw drop indicator and scrollbar
|
||||
if (_dragIndex >= 0 && _dropIndex >= 0 && _dropIndex != _dragIndex)
|
||||
DrawDropIndicator(g);
|
||||
if (ScrollbarVisible)
|
||||
DrawScrollbar(g);
|
||||
}
|
||||
|
||||
private void DrawEmptyState(Graphics g)
|
||||
{
|
||||
using (var brush = new SolidBrush(TextDimColor))
|
||||
using (var font = new Font("Segoe UI", 8.5f, FontStyle.Italic))
|
||||
{
|
||||
var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
|
||||
g.DrawString("Queue is empty", font, brush, ClientRectangle, sf);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawItem(Graphics g, int index, Rectangle rect)
|
||||
{
|
||||
bool isFirst = index == 0;
|
||||
bool isDragging = index == _dragIndex;
|
||||
bool isHovered = !isDragging && index == _hoveredIndex;
|
||||
Color bg = isDragging ? ItemDragBg : isHovered ? ItemHoverBg : ItemBg;
|
||||
|
||||
// Draw item background
|
||||
using (var path = CreateRoundedRect(rect, ItemRadius))
|
||||
using (var brush = new SolidBrush(bg))
|
||||
g.FillPath(brush, path);
|
||||
|
||||
// Active download (first item) gets gradient accent and border
|
||||
if (isFirst)
|
||||
DrawFirstItemAccent(g, rect);
|
||||
// Dragged items get subtle highlight border
|
||||
else if (isDragging)
|
||||
DrawBorder(g, rect, ItemDragBorder, 0.5f);
|
||||
|
||||
// Draw drag handle, text, and close button
|
||||
if (!isFirst)
|
||||
DrawDragHandle(g, rect);
|
||||
DrawItemText(g, index, rect, isFirst);
|
||||
DrawXButton(g, rect, isHovered && _hoveringX);
|
||||
}
|
||||
|
||||
// Draw gradient accent and border for active download (first item)
|
||||
private void DrawFirstItemAccent(Graphics g, Rectangle rect)
|
||||
{
|
||||
using (var path = CreateRoundedRect(rect, ItemRadius))
|
||||
using (var gradBrush = new LinearGradientBrush(rect,
|
||||
Color.FromArgb(60, AccentColor), Color.FromArgb(0, AccentColor), LinearGradientMode.Horizontal))
|
||||
{
|
||||
var oldClip = g.Clip;
|
||||
g.SetClip(path);
|
||||
g.FillRectangle(gradBrush, rect);
|
||||
g.Clip = oldClip;
|
||||
}
|
||||
DrawBorder(g, rect, AccentColor, 1.5f);
|
||||
}
|
||||
|
||||
private void DrawBorder(Graphics g, Rectangle rect, Color color, float width)
|
||||
{
|
||||
using (var path = CreateRoundedRect(rect, ItemRadius))
|
||||
using (var pen = new Pen(color, width))
|
||||
g.DrawPath(pen, path);
|
||||
}
|
||||
|
||||
private void DrawDragHandle(Graphics g, Rectangle rect)
|
||||
{
|
||||
int cx = rect.X + 8, cy = rect.Y + rect.Height / 2;
|
||||
using (var brush = new SolidBrush(GripColor))
|
||||
{
|
||||
for (int row = -1; row <= 1; row++)
|
||||
for (int col = 0; col < 2; col++)
|
||||
g.FillEllipse(brush, cx + col * 4, cy + row * 4 - 1, 2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawItemText(Graphics g, int index, Rectangle rect, bool isFirst)
|
||||
{
|
||||
int textLeft = isFirst ? rect.X + TextPadding : rect.X + DragHandleWidth;
|
||||
int rightPad = ScrollbarVisible ? ScrollbarWidthHover + ScrollbarMargin * 2 : 0;
|
||||
var textRect = new Rectangle(textLeft, rect.Y, rect.Right - XButtonSize - 6 - textLeft - rightPad, rect.Height);
|
||||
|
||||
using (var brush = new SolidBrush(TextColor))
|
||||
using (var font = new Font("Segoe UI", isFirst ? 8.5f : 8f, isFirst ? FontStyle.Bold : FontStyle.Regular))
|
||||
{
|
||||
var sf = new StringFormat
|
||||
{
|
||||
Alignment = StringAlignment.Near,
|
||||
LineAlignment = StringAlignment.Center,
|
||||
Trimming = StringTrimming.EllipsisCharacter,
|
||||
FormatFlags = StringFormatFlags.NoWrap
|
||||
};
|
||||
g.DrawString(_items[index], font, brush, textRect, sf);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawXButton(Graphics g, Rectangle itemRect, bool hovered)
|
||||
{
|
||||
var xRect = GetXButtonRect(itemRect);
|
||||
using (var path = CreateRoundedRect(xRect, 3))
|
||||
using (var brush = new SolidBrush(hovered ? XButtonHoverBg : XButtonBg))
|
||||
g.FillPath(brush, path);
|
||||
|
||||
using (var pen = new Pen(Color.White, 1.4f) { StartCap = LineCap.Round, EndCap = LineCap.Round })
|
||||
{
|
||||
int p = 4;
|
||||
g.DrawLine(pen, xRect.X + p, xRect.Y + p, xRect.Right - p, xRect.Bottom - p);
|
||||
g.DrawLine(pen, xRect.Right - p, xRect.Y + p, xRect.X + p, xRect.Bottom - p);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDropIndicator(Graphics g)
|
||||
{
|
||||
int y = (_dropIndex >= _items.Count ? _items.Count : _dropIndex) * (ItemHeight + ItemMargin) + ItemMargin / 2 - _scrollOffset;
|
||||
int left = ItemMargin + 2;
|
||||
int right = Width - ItemMargin - 2 - (ScrollbarVisible ? ScrollbarWidthHover + ScrollbarMargin : 0);
|
||||
|
||||
using (var pen = new Pen(AccentColor, 2.5f) { StartCap = LineCap.Round, EndCap = LineCap.Round })
|
||||
g.DrawLine(pen, left, y, right, y);
|
||||
}
|
||||
|
||||
// Draw custom scrollbar with hover expansion
|
||||
private void DrawScrollbar(Graphics g)
|
||||
{
|
||||
if (MaxScroll <= 0) return;
|
||||
|
||||
bool expanded = _scrollbarHovered || _scrollbarDragging;
|
||||
int sbWidth = expanded ? ScrollbarWidthHover : ScrollbarWidth;
|
||||
int trackX = Width - ScrollbarWidth - ScrollbarMargin - (expanded ? (ScrollbarWidthHover - ScrollbarWidth) / 2 : 0);
|
||||
|
||||
_scrollTrackRect = new Rectangle(trackX, ScrollbarMargin, sbWidth, Height - ScrollbarMargin * 2);
|
||||
|
||||
using (var trackBrush = new SolidBrush(Color.FromArgb(40, ScrollTrackColor)))
|
||||
using (var trackPath = CreateRoundedRect(_scrollTrackRect, ScrollbarRadius))
|
||||
g.FillPath(trackBrush, trackPath);
|
||||
|
||||
// Calculate thumb position and size
|
||||
int trackHeight = _scrollTrackRect.Height;
|
||||
int thumbHeight = Math.Max(MinThumbHeight, (int)(trackHeight * ((float)Height / ContentHeight)));
|
||||
float scrollRatio = MaxScroll > 0 ? (float)_scrollOffset / MaxScroll : 0;
|
||||
int thumbY = ScrollbarMargin + (int)((trackHeight - thumbHeight) * scrollRatio);
|
||||
|
||||
_scrollThumbRect = new Rectangle(trackX, thumbY, sbWidth, thumbHeight);
|
||||
Color thumbColor = _scrollbarDragging ? ScrollThumbDragColor : _scrollbarHovered ? ScrollThumbHoverColor : ScrollThumbColor;
|
||||
|
||||
using (var thumbBrush = new SolidBrush(thumbColor))
|
||||
using (var thumbPath = CreateRoundedRect(_scrollThumbRect, ScrollbarRadius))
|
||||
g.FillPath(thumbBrush, thumbPath);
|
||||
}
|
||||
|
||||
private Rectangle GetItemRect(int index)
|
||||
{
|
||||
int y = index * (ItemHeight + ItemMargin) + ItemMargin - _scrollOffset;
|
||||
int w = Width - ItemMargin * 2 - (ScrollbarVisible ? ScrollbarWidthHover + ScrollbarMargin + 2 : 0);
|
||||
return new Rectangle(ItemMargin, y, w, ItemHeight);
|
||||
}
|
||||
|
||||
private Rectangle GetXButtonRect(Rectangle itemRect) =>
|
||||
new Rectangle(itemRect.Right - XButtonSize - 3, itemRect.Y + (itemRect.Height - XButtonSize) / 2, XButtonSize, XButtonSize);
|
||||
|
||||
private int HitTest(Point pt)
|
||||
{
|
||||
for (int i = 0; i < _items.Count; i++)
|
||||
if (GetItemRect(i).Contains(pt)) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private bool HitTestScrollbar(Point pt) =>
|
||||
ScrollbarVisible && new Rectangle(_scrollTrackRect.X - 4, _scrollTrackRect.Y, _scrollTrackRect.Width + 8, _scrollTrackRect.Height).Contains(pt);
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
|
||||
if (_scrollbarDragging)
|
||||
{
|
||||
HandleScrollbarDrag(e.Y);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update scrollbar hover state
|
||||
bool wasHovered = _scrollbarHovered;
|
||||
_scrollbarHovered = HitTestScrollbar(e.Location);
|
||||
if (_scrollbarHovered != wasHovered) Invalidate();
|
||||
|
||||
if (_scrollbarHovered)
|
||||
{
|
||||
Cursor = Cursors.Default;
|
||||
_hoveredIndex = -1;
|
||||
_hoveringX = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle drag operation
|
||||
if (_dragIndex >= 0)
|
||||
{
|
||||
UpdateAutoScroll(e.Y);
|
||||
int newDrop = Math.Max(1, Math.Min(_items.Count, (e.Y + _scrollOffset + ItemHeight / 2) / (ItemHeight + ItemMargin)));
|
||||
if (newDrop != _dropIndex) { _dropIndex = newDrop; Invalidate(); }
|
||||
return;
|
||||
}
|
||||
|
||||
// Update hover state
|
||||
int hit = HitTest(e.Location);
|
||||
bool overX = hit >= 0 && GetXButtonRect(GetItemRect(hit)).Contains(e.Location);
|
||||
|
||||
if (hit != _hoveredIndex || overX != _hoveringX)
|
||||
{
|
||||
_hoveredIndex = hit;
|
||||
_hoveringX = overX;
|
||||
Cursor = overX ? Cursors.Hand : hit > 0 ? Cursors.SizeNS : Cursors.Default;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleScrollbarDrag(int mouseY)
|
||||
{
|
||||
int trackHeight = Height - ScrollbarMargin * 2;
|
||||
int thumbHeight = Math.Max(MinThumbHeight, (int)(trackHeight * ((float)Height / ContentHeight)));
|
||||
int scrollableHeight = trackHeight - thumbHeight;
|
||||
|
||||
if (scrollableHeight > 0)
|
||||
{
|
||||
float scrollRatio = (float)(mouseY - _scrollDragStartY) / scrollableHeight;
|
||||
_scrollOffset = _scrollDragStartOffset + (int)(scrollRatio * MaxScroll);
|
||||
ClampScroll();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseDown(e);
|
||||
if (e.Button != MouseButtons.Left) return;
|
||||
|
||||
// Handle scrollbar thumb drag
|
||||
if (ScrollbarVisible && _scrollThumbRect.Contains(e.Location))
|
||||
{
|
||||
_scrollbarDragging = true;
|
||||
_scrollDragStartY = e.Y;
|
||||
_scrollDragStartOffset = _scrollOffset;
|
||||
Capture = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle scrollbar track click
|
||||
if (ScrollbarVisible && HitTestScrollbar(e.Location))
|
||||
{
|
||||
_scrollOffset += e.Y < _scrollThumbRect.Top ? -Height : Height;
|
||||
ClampScroll();
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
int hit = HitTest(e.Location);
|
||||
if (hit < 0) return;
|
||||
|
||||
// Handle close button click
|
||||
if (GetXButtonRect(GetItemRect(hit)).Contains(e.Location))
|
||||
{
|
||||
ItemRemoved?.Invoke(this, hit);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start drag operation (only for non-first items)
|
||||
if (hit > 0)
|
||||
{
|
||||
_dragIndex = _dropIndex = hit;
|
||||
Capture = true;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseUp(e);
|
||||
StopAutoScroll();
|
||||
|
||||
if (_scrollbarDragging)
|
||||
{
|
||||
_scrollbarDragging = false;
|
||||
Capture = false;
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete drag reorder operation
|
||||
if (_dragIndex > 0 && _dropIndex > 0 && _dropIndex != _dragIndex)
|
||||
{
|
||||
int from = _dragIndex;
|
||||
int to = Math.Max(1, _dropIndex > _dragIndex ? _dropIndex - 1 : _dropIndex);
|
||||
var item = _items[from];
|
||||
_items.RemoveAt(from);
|
||||
_items.Insert(to, item);
|
||||
ItemReordered?.Invoke(this, new ReorderEventArgs(from, to));
|
||||
}
|
||||
|
||||
_dragIndex = _dropIndex = -1;
|
||||
Capture = false;
|
||||
Cursor = Cursors.Default;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(EventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
if (_dragIndex < 0 && !_scrollbarDragging)
|
||||
{
|
||||
_hoveredIndex = -1;
|
||||
_hoveringX = _scrollbarHovered = false;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
if (MaxScroll <= 0) return;
|
||||
_scrollOffset -= e.Delta / 4;
|
||||
ClampScroll();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
protected override void OnResize(EventArgs e)
|
||||
{
|
||||
base.OnResize(e);
|
||||
ClampScroll();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_autoScrollTimer?.Stop();
|
||||
_autoScrollTimer?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private static GraphicsPath CreateRoundedRect(Rectangle rect, int radius)
|
||||
{
|
||||
var path = new GraphicsPath();
|
||||
if (radius <= 0 || rect.Width <= 0 || rect.Height <= 0)
|
||||
{
|
||||
path.AddRectangle(rect);
|
||||
return path;
|
||||
}
|
||||
int d = Math.Min(radius * 2, Math.Min(rect.Width, rect.Height));
|
||||
path.AddArc(rect.X, rect.Y, d, d, 180, 90);
|
||||
path.AddArc(rect.Right - d, rect.Y, d, d, 270, 90);
|
||||
path.AddArc(rect.Right - d, rect.Bottom - d, d, d, 0, 90);
|
||||
path.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
|
||||
path.CloseFigure();
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public class ReorderEventArgs : EventArgs
|
||||
{
|
||||
public int FromIndex { get; }
|
||||
public int ToIndex { get; }
|
||||
public ReorderEventArgs(int from, int to) { FromIndex = from; ToIndex = to; }
|
||||
}
|
||||
}
|
||||
@@ -191,5 +191,8 @@
|
||||
<Setting Name="proxyPort" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="selectedMirror" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
@@ -327,20 +327,33 @@ namespace AndroidSideloader
|
||||
|
||||
private void setDownloadDirectory_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (downloadDirectorySetter.ShowDialog() == DialogResult.OK)
|
||||
var dialog = new FolderSelectDialog
|
||||
{
|
||||
Title = "Select Download Folder",
|
||||
InitialDirectory = _settings.CustomDownloadDir && Directory.Exists(_settings.DownloadDir)
|
||||
? _settings.DownloadDir
|
||||
: Environment.CurrentDirectory
|
||||
};
|
||||
|
||||
if (dialog.Show(this.Handle))
|
||||
{
|
||||
_settings.CustomDownloadDir = true;
|
||||
_settings.DownloadDir = downloadDirectorySetter.SelectedPath;
|
||||
_settings.DownloadDir = dialog.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
private void setBackupDirectory_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (backupDirectorySetter.ShowDialog() == DialogResult.OK)
|
||||
var dialog = new FolderSelectDialog
|
||||
{
|
||||
Title = "Select Backup Folder",
|
||||
InitialDirectory = _settings.GetEffectiveBackupDir()
|
||||
};
|
||||
|
||||
if (dialog.Show(this.Handle))
|
||||
{
|
||||
_settings.CustomBackupDir = true;
|
||||
_settings.BackupDir = backupDirectorySetter.SelectedPath;
|
||||
MainForm.backupFolder = _settings.BackupDir;
|
||||
_settings.BackupDir = dialog.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,9 +375,7 @@ namespace AndroidSideloader
|
||||
|
||||
private void openBackupDirectory_Click(object sender, EventArgs e)
|
||||
{
|
||||
string pathToOpen = _settings.CustomBackupDir
|
||||
? Path.Combine(_settings.BackupDir, "Rookie Backups")
|
||||
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Rookie Backups");
|
||||
string pathToOpen = _settings.GetEffectiveBackupDir();
|
||||
MainForm.OpenDirectory(pathToOpen);
|
||||
}
|
||||
|
||||
|
||||
@@ -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); }
|
||||
@@ -136,14 +149,7 @@ namespace AndroidSideloader
|
||||
|
||||
public static void BackupGame(string packagename)
|
||||
{
|
||||
if (!settings.CustomBackupDir)
|
||||
{
|
||||
MainForm.backupFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), $"Rookie Backups");
|
||||
}
|
||||
else
|
||||
{
|
||||
MainForm.backupFolder = Path.Combine((settings.BackupDir), $"Rookie Backups");
|
||||
}
|
||||
MainForm.backupFolder = settings.GetEffectiveBackupDir();
|
||||
if (!Directory.Exists(MainForm.backupFolder))
|
||||
{
|
||||
_ = Directory.CreateDirectory(MainForm.backupFolder);
|
||||
@@ -204,7 +210,7 @@ namespace AndroidSideloader
|
||||
|
||||
if (Directory.Exists($"{settings.MainDir}\\{packageName}"))
|
||||
{
|
||||
Directory.Delete($"{settings.MainDir}\\{packageName}", true);
|
||||
FileSystemUtilities.TryDeleteDirectory($"{settings.MainDir}\\{packageName}");
|
||||
}
|
||||
|
||||
_ = Directory.CreateDirectory($"{settings.MainDir}\\{packageName}");
|
||||
|
||||
@@ -309,7 +309,7 @@ namespace AndroidSideloader
|
||||
}
|
||||
File.Move(file, destFile);
|
||||
}
|
||||
Directory.Delete(dirExtractedRclone, true);
|
||||
FileSystemUtilities.TryDeleteDirectory(dirExtractedRclone);
|
||||
|
||||
// Restore vrp.download.config if it was backed up
|
||||
if (hasConfig && File.Exists(tempConfigPath))
|
||||
|
||||
@@ -290,7 +290,7 @@ namespace AndroidSideloader
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
Directory.Delete(path, true);
|
||||
FileSystemUtilities.TryDeleteDirectory(path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AndroidSideloader.Utilities
|
||||
{
|
||||
@@ -20,49 +22,161 @@ namespace AndroidSideloader.Utilities
|
||||
"raw.githubusercontent.com",
|
||||
"downloads.rclone.org",
|
||||
"vrpirates.wiki",
|
||||
"go.vrpyourself.online",
|
||||
"github.com"
|
||||
};
|
||||
|
||||
private static readonly ConcurrentDictionary<string, IPAddress> _dnsCache =
|
||||
new ConcurrentDictionary<string, IPAddress>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private static bool _initialized;
|
||||
private static bool _useFallbackDns;
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
// Local proxy for rclone
|
||||
private static TcpListener _proxyListener;
|
||||
private static CancellationTokenSource _proxyCts;
|
||||
private static int _proxyPort;
|
||||
private static bool _initialized;
|
||||
private static bool _proxyRunning;
|
||||
|
||||
public static bool UseFallbackDns
|
||||
{
|
||||
get { if (!_initialized) Initialize(); return _useFallbackDns; }
|
||||
}
|
||||
public static bool UseFallbackDns { get; private set; }
|
||||
|
||||
// Gets the proxy URL for rclone to use, or empty string if not needed
|
||||
public static string ProxyUrl => _proxyRunning ? $"http://127.0.0.1:{_proxyPort}" : string.Empty;
|
||||
|
||||
// Called after vrp-public.json is created/updated to test the hostname
|
||||
// Enable fallback DNS if the hostname fails on system DNS but works with fallback DNS
|
||||
public static void TestPublicConfigDns()
|
||||
{
|
||||
string hostname = GetPublicConfigHostname();
|
||||
if (string.IsNullOrEmpty(hostname))
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
// If already using fallback, just pre-resolve the new hostname
|
||||
if (UseFallbackDns)
|
||||
{
|
||||
var ip = ResolveWithFallbackDns(hostname);
|
||||
if (ip != null)
|
||||
{
|
||||
_dnsCache[hostname] = ip;
|
||||
Logger.Log($"Pre-resolved public config hostname {hostname} -> {ip}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Test if system DNS can resolve the public config hostname
|
||||
bool systemDnsWorks = TestHostnameWithSystemDns(hostname);
|
||||
|
||||
if (!systemDnsWorks)
|
||||
{
|
||||
Logger.Log($"System DNS failed for {hostname}. Testing fallback...", LogLevel.WARNING);
|
||||
|
||||
// Test if fallback DNS works for this hostname
|
||||
var ip = ResolveWithFallbackDns(hostname);
|
||||
if (ip != null)
|
||||
{
|
||||
UseFallbackDns = true;
|
||||
_dnsCache[hostname] = ip;
|
||||
Logger.Log($"Enabled fallback DNS for {hostname} -> {ip}", LogLevel.INFO);
|
||||
ServicePointManager.DnsRefreshTimeout = 0;
|
||||
|
||||
// Pre-resolve base hostnames too
|
||||
PreResolveHostnames(CriticalHostnames);
|
||||
|
||||
// Start proxy if not already running
|
||||
if (!_proxyRunning)
|
||||
{
|
||||
StartProxy();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log($"Both system and fallback DNS failed for {hostname}", LogLevel.ERROR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log($"System DNS works for public config hostname: {hostname}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPublicConfigHostname()
|
||||
{
|
||||
try
|
||||
{
|
||||
string configPath = Path.Combine(Environment.CurrentDirectory, "vrp-public.json");
|
||||
if (!File.Exists(configPath))
|
||||
return null;
|
||||
|
||||
var config = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(configPath));
|
||||
if (config != null && config.TryGetValue("baseUri", out string baseUri))
|
||||
{
|
||||
return ExtractHostname(baseUri);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"Failed to get hostname from vrp-public.json: {ex.Message}", LogLevel.WARNING);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string[] GetCriticalHostnames()
|
||||
{
|
||||
var hostnames = new List<string>(CriticalHostnames);
|
||||
|
||||
string host = GetPublicConfigHostname();
|
||||
if (!string.IsNullOrWhiteSpace(host) && !hostnames.Contains(host))
|
||||
{
|
||||
hostnames.Add(host);
|
||||
Logger.Log($"Added {host} from vrp-public.json to critical hostnames");
|
||||
}
|
||||
|
||||
return hostnames.ToArray();
|
||||
}
|
||||
|
||||
private static string ExtractHostname(string uriString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(uriString)) return null;
|
||||
|
||||
if (!uriString.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
|
||||
!uriString.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
uriString = "https://" + uriString;
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(uriString, UriKind.Absolute, out Uri uri))
|
||||
return uri.Host;
|
||||
|
||||
// Fallback: manual extraction
|
||||
string hostname = uriString.Replace("https://", "").Replace("http://", "");
|
||||
int idx = hostname.IndexOfAny(new[] { '/', ':' });
|
||||
return idx > 0 ? hostname.Substring(0, idx) : hostname;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_initialized) return;
|
||||
Logger.Log("Testing DNS resolution for critical hostnames...");
|
||||
|
||||
if (!TestSystemDns())
|
||||
Logger.Log("Testing DNS resolution for critical hostnames...");
|
||||
var hostnames = GetCriticalHostnames();
|
||||
|
||||
if (TestDns(hostnames, useSystem: true))
|
||||
{
|
||||
Logger.Log("System DNS is working correctly.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("System DNS failed. Testing Cloudflare DNS fallback...", LogLevel.WARNING);
|
||||
if (TestFallbackDns())
|
||||
if (TestDns(hostnames, useSystem: false))
|
||||
{
|
||||
_useFallbackDns = true;
|
||||
UseFallbackDns = true;
|
||||
Logger.Log("Using Cloudflare DNS fallback.", LogLevel.INFO);
|
||||
PreResolveHostnames();
|
||||
PreResolveHostnames(hostnames);
|
||||
ServicePointManager.DnsRefreshTimeout = 0;
|
||||
|
||||
// Start local proxy for rclone
|
||||
StartProxy();
|
||||
}
|
||||
else
|
||||
@@ -70,77 +184,65 @@ namespace AndroidSideloader.Utilities
|
||||
Logger.Log("Both system and fallback DNS failed.", LogLevel.ERROR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("System DNS is working correctly.");
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleans up resources. Called on application exit
|
||||
public static void Cleanup()
|
||||
{
|
||||
StopProxy();
|
||||
}
|
||||
public static void Cleanup() => StopProxy();
|
||||
|
||||
private static void PreResolveHostnames()
|
||||
private static bool TestHostnameWithSystemDns(string hostname)
|
||||
{
|
||||
foreach (string hostname in CriticalHostnames)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var ip = ResolveWithFallbackDns(hostname);
|
||||
if (ip != null)
|
||||
{
|
||||
_dnsCache[hostname] = ip;
|
||||
Logger.Log($"Pre-resolved {hostname} -> {ip}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"Failed to pre-resolve {hostname}: {ex.Message}", LogLevel.WARNING);
|
||||
}
|
||||
var addresses = Dns.GetHostAddresses(hostname);
|
||||
return addresses?.Length > 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TestSystemDns()
|
||||
private static bool TestDns(string[] hostnames, bool useSystem)
|
||||
{
|
||||
foreach (string hostname in CriticalHostnames)
|
||||
if (useSystem)
|
||||
{
|
||||
try
|
||||
return hostnames.All(h =>
|
||||
{
|
||||
var addresses = Dns.GetHostAddresses(hostname);
|
||||
if (addresses == null || addresses.Length == 0) return false;
|
||||
}
|
||||
try { return Dns.GetHostAddresses(h)?.Length > 0; }
|
||||
catch { return false; }
|
||||
});
|
||||
}
|
||||
|
||||
return FallbackDnsServers.Any(server =>
|
||||
{
|
||||
try { return ResolveWithDns(hostnames[0], server)?.Count > 0; }
|
||||
catch { return false; }
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static bool TestFallbackDns()
|
||||
private static void PreResolveHostnames(string[] hostnames)
|
||||
{
|
||||
foreach (string dnsServer in FallbackDnsServers)
|
||||
foreach (string hostname in hostnames)
|
||||
{
|
||||
try
|
||||
var ip = ResolveWithFallbackDns(hostname);
|
||||
if (ip != null)
|
||||
{
|
||||
var addresses = ResolveWithDns(CriticalHostnames[0], dnsServer);
|
||||
if (addresses != null && addresses.Count > 0) return true;
|
||||
_dnsCache[hostname] = ip;
|
||||
Logger.Log($"Pre-resolved {hostname} -> {ip}");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IPAddress ResolveWithFallbackDns(string hostname)
|
||||
{
|
||||
foreach (string dnsServer in FallbackDnsServers)
|
||||
foreach (string server in FallbackDnsServers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var addresses = ResolveWithDns(hostname, dnsServer);
|
||||
if (addresses != null && addresses.Count > 0)
|
||||
return addresses[0];
|
||||
var addresses = ResolveWithDns(hostname, server);
|
||||
if (addresses?.Count > 0) return addresses[0];
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -149,56 +251,59 @@ namespace AndroidSideloader.Utilities
|
||||
|
||||
private static List<IPAddress> ResolveWithDns(string hostname, string dnsServer, int timeoutMs = 5000)
|
||||
{
|
||||
byte[] query = BuildDnsQuery(hostname);
|
||||
using (var udp = new UdpClient())
|
||||
using (var udp = new UdpClient { Client = { ReceiveTimeout = timeoutMs, SendTimeout = timeoutMs } })
|
||||
{
|
||||
udp.Client.ReceiveTimeout = timeoutMs;
|
||||
udp.Client.SendTimeout = timeoutMs;
|
||||
byte[] query = BuildDnsQuery(hostname);
|
||||
udp.Send(query, query.Length, new IPEndPoint(IPAddress.Parse(dnsServer), 53));
|
||||
IPEndPoint remoteEp = null;
|
||||
byte[] response = udp.Receive(ref remoteEp);
|
||||
return ParseDnsResponse(response);
|
||||
return ParseDnsResponse(udp.Receive(ref remoteEp));
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] BuildDnsQuery(string hostname)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
var writer = new BinaryWriter(ms);
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)new Random().Next(0, ushort.MaxValue)));
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)0x0100));
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)1));
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)0));
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)0));
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)0));
|
||||
foreach (string label in hostname.Split('.'))
|
||||
using (var ms = new MemoryStream())
|
||||
using (var writer = new BinaryWriter(ms))
|
||||
{
|
||||
writer.Write((byte)label.Length);
|
||||
writer.Write(Encoding.ASCII.GetBytes(label));
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)new Random().Next(0, ushort.MaxValue)));
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)0x0100)); // Flags
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)1)); // Questions
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)0)); // Answer RRs
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)0)); // Authority RRs
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)0)); // Additional RRs
|
||||
|
||||
foreach (string label in hostname.Split('.'))
|
||||
{
|
||||
writer.Write((byte)label.Length);
|
||||
writer.Write(Encoding.ASCII.GetBytes(label));
|
||||
}
|
||||
writer.Write((byte)0);
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)1)); // Type A
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)1)); // Class IN
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
writer.Write((byte)0);
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)1));
|
||||
writer.Write(IPAddress.HostToNetworkOrder((short)1));
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private static List<IPAddress> ParseDnsResponse(byte[] response)
|
||||
{
|
||||
var addresses = new List<IPAddress>();
|
||||
if (response.Length < 12) return addresses;
|
||||
|
||||
int pos = 12;
|
||||
while (pos < response.Length && response[pos] != 0) pos += response[pos] + 1;
|
||||
pos += 5;
|
||||
|
||||
int answerCount = (response[6] << 8) | response[7];
|
||||
for (int i = 0; i < answerCount && pos + 12 <= response.Length; i++)
|
||||
{
|
||||
if ((response[pos] & 0xC0) == 0xC0) pos += 2;
|
||||
else { while (pos < response.Length && response[pos] != 0) pos += response[pos] + 1; pos++; }
|
||||
pos += (response[pos] & 0xC0) == 0xC0 ? 2 : SkipName(response, pos);
|
||||
if (pos + 10 > response.Length) break;
|
||||
|
||||
ushort type = (ushort)((response[pos] << 8) | response[pos + 1]);
|
||||
pos += 8;
|
||||
ushort rdLength = (ushort)((response[pos] << 8) | response[pos + 1]);
|
||||
pos += 2;
|
||||
ushort rdLength = (ushort)((response[pos + 8] << 8) | response[pos + 9]);
|
||||
pos += 10;
|
||||
|
||||
if (pos + rdLength > response.Length) break;
|
||||
if (type == 1 && rdLength == 4)
|
||||
addresses.Add(new IPAddress(new[] { response[pos], response[pos + 1], response[pos + 2], response[pos + 3] }));
|
||||
@@ -207,7 +312,73 @@ namespace AndroidSideloader.Utilities
|
||||
return addresses;
|
||||
}
|
||||
|
||||
#region Local HTTP CONNECT Proxy for rclone
|
||||
private static int SkipName(byte[] data, int pos)
|
||||
{
|
||||
int start = pos;
|
||||
while (pos < data.Length && data[pos] != 0) pos += data[pos] + 1;
|
||||
return pos - start + 1;
|
||||
}
|
||||
|
||||
public static IPAddress ResolveHostname(string hostname, bool alwaysTryFallback = false)
|
||||
{
|
||||
if (_dnsCache.TryGetValue(hostname, out IPAddress cached))
|
||||
return cached;
|
||||
|
||||
try
|
||||
{
|
||||
var addresses = Dns.GetHostAddresses(hostname);
|
||||
if (addresses?.Length > 0)
|
||||
{
|
||||
_dnsCache[hostname] = addresses[0];
|
||||
return addresses[0];
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (alwaysTryFallback || UseFallbackDns || !_initialized)
|
||||
{
|
||||
var ip = ResolveWithFallbackDns(hostname);
|
||||
if (ip != null)
|
||||
{
|
||||
_dnsCache[hostname] = ip;
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static HttpWebRequest CreateWebRequest(string url)
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
|
||||
if (!UseFallbackDns)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dns.GetHostAddresses(uri.Host);
|
||||
return (HttpWebRequest)WebRequest.Create(url);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!_initialized) Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
if (UseFallbackDns)
|
||||
{
|
||||
var ip = ResolveHostname(uri.Host, alwaysTryFallback: true);
|
||||
if (ip != null)
|
||||
{
|
||||
var builder = new UriBuilder(uri) { Host = ip.ToString() };
|
||||
var request = (HttpWebRequest)WebRequest.Create(builder.Uri);
|
||||
request.Host = uri.Host;
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
return (HttpWebRequest)WebRequest.Create(url);
|
||||
}
|
||||
|
||||
private static void StartProxy()
|
||||
{
|
||||
@@ -246,7 +417,7 @@ namespace AndroidSideloader.Utilities
|
||||
try
|
||||
{
|
||||
var client = await _proxyListener.AcceptTcpClientAsync();
|
||||
_ = Task.Run(() => HandleProxyClient(client, ct));
|
||||
_ = HandleProxyClient(client, ct);
|
||||
}
|
||||
catch (ObjectDisposedException) { break; }
|
||||
catch (Exception ex)
|
||||
@@ -264,10 +435,8 @@ namespace AndroidSideloader.Utilities
|
||||
using (client)
|
||||
using (var stream = client.GetStream())
|
||||
{
|
||||
client.ReceiveTimeout = 30000;
|
||||
client.SendTimeout = 30000;
|
||||
client.ReceiveTimeout = client.SendTimeout = 30000;
|
||||
|
||||
// Read the HTTP request
|
||||
var buffer = new byte[8192];
|
||||
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, ct);
|
||||
if (bytesRead == 0) return;
|
||||
@@ -279,19 +448,12 @@ namespace AndroidSideloader.Utilities
|
||||
string[] requestLine = lines[0].Split(' ');
|
||||
if (requestLine.Length < 2) return;
|
||||
|
||||
string method = requestLine[0];
|
||||
string target = requestLine[1];
|
||||
|
||||
if (method == "CONNECT")
|
||||
{
|
||||
if (requestLine[0] == "CONNECT")
|
||||
// HTTPS proxy - tunnel mode
|
||||
await HandleConnectRequest(stream, target, ct);
|
||||
}
|
||||
await HandleConnectRequest(stream, requestLine[1], ct);
|
||||
else
|
||||
{
|
||||
// HTTP proxy - forward mode
|
||||
await HandleHttpRequest(stream, request, target, ct);
|
||||
}
|
||||
await HandleHttpRequest(stream, request, requestLine[1], ct);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -306,14 +468,13 @@ namespace AndroidSideloader.Utilities
|
||||
// Parse host:port
|
||||
string[] parts = target.Split(':');
|
||||
string host = parts[0];
|
||||
int port = parts.Length > 1 ? int.Parse(parts[1]) : 443;
|
||||
int port = parts.Length > 1 && int.TryParse(parts[1], out int p) ? p : 443;
|
||||
|
||||
// Resolve hostname using our DNS
|
||||
IPAddress ip = ResolveAnyHostname(host);
|
||||
// Resolve hostname
|
||||
IPAddress ip = ResolveHostname(host, alwaysTryFallback: true);
|
||||
if (ip == null)
|
||||
{
|
||||
byte[] errorResponse = Encoding.ASCII.GetBytes("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
||||
await clientStream.WriteAsync(errorResponse, 0, errorResponse.Length, ct);
|
||||
await SendResponse(clientStream, "HTTP/1.1 502 Bad Gateway\r\n\r\n", ct);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -326,21 +487,16 @@ namespace AndroidSideloader.Utilities
|
||||
using (var targetStream = targetClient.GetStream())
|
||||
{
|
||||
// Send 200 OK to client
|
||||
byte[] okResponse = Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n");
|
||||
await clientStream.WriteAsync(okResponse, 0, okResponse.Length, ct);
|
||||
|
||||
await SendResponse(clientStream, "HTTP/1.1 200 Connection Established\r\n\r\n", ct);
|
||||
// Tunnel data bidirectionally
|
||||
var clientToTarget = RelayData(clientStream, targetStream, ct);
|
||||
var targetToClient = RelayData(targetStream, clientStream, ct);
|
||||
await Task.WhenAny(clientToTarget, targetToClient);
|
||||
await Task.WhenAny(RelayData(clientStream, targetStream, ct), RelayData(targetStream, clientStream, ct));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"CONNECT tunnel error to {host}: {ex.Message}", LogLevel.WARNING);
|
||||
byte[] errorResponse = Encoding.ASCII.GetBytes("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
||||
try { await clientStream.WriteAsync(errorResponse, 0, errorResponse.Length, ct); } catch { }
|
||||
await SendResponse(clientStream, "HTTP/1.1 502 Bad Gateway\r\n\r\n", ct);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,19 +505,16 @@ namespace AndroidSideloader.Utilities
|
||||
try
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
IPAddress ip = ResolveAnyHostname(uri.Host);
|
||||
IPAddress ip = ResolveHostname(uri.Host, alwaysTryFallback: true);
|
||||
if (ip == null)
|
||||
{
|
||||
byte[] errorResponse = Encoding.ASCII.GetBytes("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
||||
await clientStream.WriteAsync(errorResponse, 0, errorResponse.Length, ct);
|
||||
await SendResponse(clientStream, "HTTP/1.1 502 Bad Gateway\r\n\r\n", ct);
|
||||
return;
|
||||
}
|
||||
|
||||
int port = uri.Port > 0 ? uri.Port : 80;
|
||||
|
||||
using (var targetClient = new TcpClient())
|
||||
{
|
||||
await targetClient.ConnectAsync(ip, port);
|
||||
await targetClient.ConnectAsync(ip, uri.Port > 0 ? uri.Port : 80);
|
||||
using (var targetStream = targetClient.GetStream())
|
||||
{
|
||||
// Modify request to use relative path
|
||||
@@ -380,6 +533,12 @@ namespace AndroidSideloader.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SendResponse(NetworkStream stream, string response, CancellationToken ct)
|
||||
{
|
||||
byte[] bytes = Encoding.ASCII.GetBytes(response);
|
||||
try { await stream.WriteAsync(bytes, 0, bytes.Length, ct); } catch { }
|
||||
}
|
||||
|
||||
private static async Task RelayData(NetworkStream from, NetworkStream to, CancellationToken ct)
|
||||
{
|
||||
byte[] buffer = new byte[8192];
|
||||
@@ -387,105 +546,9 @@ namespace AndroidSideloader.Utilities
|
||||
{
|
||||
int bytesRead;
|
||||
while ((bytesRead = await from.ReadAsync(buffer, 0, buffer.Length, ct)) > 0)
|
||||
{
|
||||
await to.WriteAsync(buffer, 0, bytesRead, ct);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static IPAddress ResolveHostname(string hostname)
|
||||
{
|
||||
if (_dnsCache.TryGetValue(hostname, out IPAddress cached))
|
||||
return cached;
|
||||
|
||||
try
|
||||
{
|
||||
var addresses = Dns.GetHostAddresses(hostname);
|
||||
if (addresses != null && addresses.Length > 0)
|
||||
{
|
||||
_dnsCache[hostname] = addresses[0];
|
||||
return addresses[0];
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (_useFallbackDns || !_initialized)
|
||||
{
|
||||
var ip = ResolveWithFallbackDns(hostname);
|
||||
if (ip != null)
|
||||
{
|
||||
_dnsCache[hostname] = ip;
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IPAddress ResolveAnyHostname(string hostname)
|
||||
{
|
||||
if (_dnsCache.TryGetValue(hostname, out IPAddress cached))
|
||||
return cached;
|
||||
|
||||
try
|
||||
{
|
||||
var addresses = Dns.GetHostAddresses(hostname);
|
||||
if (addresses != null && addresses.Length > 0)
|
||||
{
|
||||
_dnsCache[hostname] = addresses[0];
|
||||
return addresses[0];
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
var ip = ResolveWithFallbackDns(hostname);
|
||||
if (ip != null)
|
||||
{
|
||||
_dnsCache[hostname] = ip;
|
||||
return ip;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static HttpWebRequest CreateWebRequest(string url)
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
|
||||
if (!_useFallbackDns)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dns.GetHostAddresses(uri.Host);
|
||||
return (HttpWebRequest)WebRequest.Create(url);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!_initialized) Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
if (_useFallbackDns)
|
||||
{
|
||||
var ip = ResolveHostname(uri.Host);
|
||||
if (ip == null)
|
||||
{
|
||||
ip = ResolveAnyHostname(uri.Host);
|
||||
}
|
||||
|
||||
if (ip != null)
|
||||
{
|
||||
var builder = new UriBuilder(uri) { Host = ip.ToString() };
|
||||
var request = (HttpWebRequest)WebRequest.Create(builder.Uri);
|
||||
request.Host = uri.Host;
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
return (HttpWebRequest)WebRequest.Create(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Utilities/FileSystemUtilities.cs
Normal file
91
Utilities/FileSystemUtilities.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace AndroidSideloader.Utilities
|
||||
{
|
||||
internal static class FileSystemUtilities
|
||||
{
|
||||
public static bool TryDeleteDirectory(string directoryPath, int maxRetries = 3, int delayMs = 150) // 3x 150ms = 450ms total
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryPath))
|
||||
return true;
|
||||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
return true;
|
||||
|
||||
Exception lastError = null;
|
||||
|
||||
// Retry deletion several times in case of lock ups
|
||||
for (int attempt = 0; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
StripReadOnlyAttributes(directoryPath);
|
||||
Directory.Delete(directoryPath, true);
|
||||
return true;
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException)
|
||||
{
|
||||
lastError = ex;
|
||||
|
||||
if (attempt < maxRetries)
|
||||
{
|
||||
Thread.Sleep(delayMs);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Non-retryable error
|
||||
lastError = ex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: rename then delete
|
||||
try
|
||||
{
|
||||
string renamedPath = directoryPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
|
||||
+ ".deleting." + DateTime.UtcNow.Ticks;
|
||||
|
||||
Directory.Move(directoryPath, renamedPath);
|
||||
|
||||
StripReadOnlyAttributes(renamedPath);
|
||||
Directory.Delete(renamedPath, true);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastError = ex;
|
||||
}
|
||||
|
||||
Logger.Log($"Failed to delete directory: {directoryPath}. Error: {lastError}", LogLevel.WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void StripReadOnlyAttributes(string directoryPath)
|
||||
{
|
||||
var root = new DirectoryInfo(directoryPath);
|
||||
if (!root.Exists) return;
|
||||
|
||||
root.Attributes &= ~FileAttributes.ReadOnly;
|
||||
|
||||
foreach (var dir in root.EnumerateDirectories("*", SearchOption.AllDirectories))
|
||||
{
|
||||
dir.Attributes &= ~FileAttributes.ReadOnly;
|
||||
}
|
||||
|
||||
foreach (var file in root.EnumerateFiles("*", SearchOption.AllDirectories))
|
||||
{
|
||||
file.Attributes &= ~FileAttributes.ReadOnly;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,8 +135,24 @@ namespace AndroidSideloader.Utilities
|
||||
public bool useProxy { get; set; } = false;
|
||||
public string ProxyAddress { get; set; } = string.Empty;
|
||||
public string ProxyPort { get; set; } = string.Empty;
|
||||
public string selectedMirror { get; set; } = string.Empty;
|
||||
public bool TrailersEnabled { get; set; } = true;
|
||||
public bool UseGalleryView { get; set; } = true;
|
||||
|
||||
// Window state persistence
|
||||
public int WindowX { get; set; } = -1;
|
||||
public int WindowY { get; set; } = -1;
|
||||
public int WindowWidth { get; set; } = -1;
|
||||
public int WindowHeight { get; set; } = -1;
|
||||
public bool WindowMaximized { get; set; } = false;
|
||||
|
||||
// Sort state persistence
|
||||
public int SortColumn { get; set; } = 0;
|
||||
public bool SortAscending { get; set; } = true;
|
||||
|
||||
// Download queue persistence
|
||||
public string[] QueuedGames { get; set; } = new string[0];
|
||||
|
||||
private SettingsManager()
|
||||
{
|
||||
Load();
|
||||
@@ -259,8 +275,17 @@ namespace AndroidSideloader.Utilities
|
||||
useProxy = false;
|
||||
ProxyAddress = string.Empty;
|
||||
ProxyPort = string.Empty;
|
||||
selectedMirror = string.Empty;
|
||||
TrailersEnabled = true;
|
||||
UseGalleryView = true;
|
||||
WindowX = -1;
|
||||
WindowY = -1;
|
||||
WindowWidth = -1;
|
||||
WindowHeight = -1;
|
||||
WindowMaximized = false;
|
||||
SortColumn = 0;
|
||||
SortAscending = true;
|
||||
QueuedGames = new string[0];
|
||||
|
||||
Save();
|
||||
Debug.WriteLine("Default settings created.");
|
||||
@@ -288,6 +313,16 @@ namespace AndroidSideloader.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
public string GetEffectiveBackupDir()
|
||||
{
|
||||
if (CustomBackupDir && Directory.Exists(BackupDir))
|
||||
{
|
||||
return BackupDir;
|
||||
}
|
||||
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Rookie Backups");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FontStyle?.Dispose();
|
||||
|
||||
@@ -61,5 +61,13 @@ namespace AndroidSideloader.Utilities
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static bool TryParseDouble(string value, out double result)
|
||||
{
|
||||
return double.TryParse(value,
|
||||
System.Globalization.NumberStyles.Any,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user