Compare commits
87 Commits
v3.0-beta2
...
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 | ||
|
|
58cb75c38c | ||
|
|
4383b9d398 | ||
|
|
b3ce3ab214 | ||
|
|
ae72432aee | ||
|
|
5a939d6234 | ||
|
|
e6ce947700 | ||
|
|
d24df061df | ||
|
|
1b06ab7981 | ||
|
|
5f4cfc09fe | ||
|
|
1de339da75 | ||
|
|
4f653f2131 | ||
|
|
3148ddcfa3 | ||
|
|
b793d2a140 | ||
|
|
5f16ad13e2 | ||
|
|
ddb503feec | ||
|
|
1bcfbd132d | ||
|
|
9862a6a8ca | ||
|
|
9ef3c9264e | ||
|
|
12c371da84 | ||
|
|
782ead1c1e | ||
|
|
e9f77449f0 | ||
|
|
fd77c4db8b | ||
|
|
d9a8d1c460 | ||
|
|
c5b151471a | ||
|
|
2f843bc458 | ||
|
|
f528520024 | ||
|
|
a92d4c0267 | ||
|
|
acaea1d243 | ||
|
|
2b770b30a8 | ||
|
|
e6d178cb2a | ||
|
|
c2f5b20e83 | ||
|
|
952251be35 | ||
|
|
385fe45d5d | ||
|
|
5db0ea301d | ||
|
|
523efba81c | ||
|
|
06dc91f130 | ||
|
|
252955da0a | ||
|
|
bd193c0d3f | ||
|
|
630fe98d31 | ||
|
|
0edc1deed0 |
383
ADB.cs
383
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);
|
||||
}
|
||||
@@ -154,13 +177,13 @@ namespace AndroidSideloader
|
||||
// Copies and installs an APK with real-time progress reporting using AdvancedSharpAdbClient
|
||||
public static async Task<ProcessOutput> SideloadWithProgressAsync(
|
||||
string path,
|
||||
Action<int> progressCallback = null,
|
||||
Action<float, TimeSpan?> progressCallback = null,
|
||||
Action<string> statusCallback = null,
|
||||
string packagename = "",
|
||||
string gameName = "")
|
||||
{
|
||||
statusCallback?.Invoke("Installing APK...");
|
||||
progressCallback?.Invoke(0);
|
||||
progressCallback?.Invoke(0, null);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -175,43 +198,87 @@ namespace AndroidSideloader
|
||||
|
||||
statusCallback?.Invoke("Installing APK...");
|
||||
|
||||
// Throttle UI updates to prevent lag
|
||||
DateTime lastProgressUpdate = DateTime.MinValue;
|
||||
float lastReportedPercent = -1;
|
||||
const int ThrottleMs = 100; // Update UI every 100ms
|
||||
|
||||
// Shared ETA engine (percent-units)
|
||||
var eta = new EtaEstimator(alpha: 0.05, reanchorThreshold: 0.20);
|
||||
|
||||
// Create install progress handler
|
||||
Action<InstallProgressEventArgs> installProgress = (args) =>
|
||||
{
|
||||
// Map PackageInstallProgressState to percentage
|
||||
int percent = 0;
|
||||
float percent = 0;
|
||||
string status = null;
|
||||
TimeSpan? displayEta = null;
|
||||
|
||||
switch (args.State)
|
||||
{
|
||||
case PackageInstallProgressState.Preparing:
|
||||
percent = 0;
|
||||
statusCallback?.Invoke("Preparing...");
|
||||
status = "Preparing...";
|
||||
eta.Reset();
|
||||
break;
|
||||
|
||||
case PackageInstallProgressState.Uploading:
|
||||
percent = (int)Math.Round(args.UploadProgress);
|
||||
statusCallback?.Invoke($"Installing · {args.UploadProgress:F0}%");
|
||||
percent = (float)args.UploadProgress;
|
||||
|
||||
// Update ETA engine using percent as units (0..100)
|
||||
if (percent > 0 && percent < 100)
|
||||
{
|
||||
eta.Update(totalUnits: 100, doneUnits: (long)Math.Round(percent));
|
||||
displayEta = eta.GetDisplayEta();
|
||||
}
|
||||
else
|
||||
{
|
||||
displayEta = eta.GetDisplayEta();
|
||||
}
|
||||
|
||||
status = $"Installing · {percent:0.0}%";
|
||||
break;
|
||||
|
||||
case PackageInstallProgressState.Installing:
|
||||
percent = 100;
|
||||
statusCallback?.Invoke("Completing Installation...");
|
||||
status = "Completing Installation...";
|
||||
displayEta = null;
|
||||
break;
|
||||
|
||||
case PackageInstallProgressState.Finished:
|
||||
percent = 100;
|
||||
statusCallback?.Invoke("");
|
||||
status = "";
|
||||
displayEta = null;
|
||||
break;
|
||||
|
||||
default:
|
||||
percent = 50;
|
||||
percent = 100;
|
||||
status = "";
|
||||
displayEta = null;
|
||||
break;
|
||||
}
|
||||
progressCallback?.Invoke(percent);
|
||||
|
||||
var updateNow = DateTime.UtcNow;
|
||||
bool shouldUpdate = (updateNow - lastProgressUpdate).TotalMilliseconds >= ThrottleMs
|
||||
|| Math.Abs(percent - lastReportedPercent) >= 0.1f
|
||||
|| args.State != PackageInstallProgressState.Uploading;
|
||||
|
||||
if (shouldUpdate)
|
||||
{
|
||||
lastProgressUpdate = updateNow;
|
||||
lastReportedPercent = percent;
|
||||
|
||||
// ETA goes back via progress callback (label); status remains percent-only string for inner bar
|
||||
progressCallback?.Invoke(percent, displayEta);
|
||||
if (status != null) statusCallback?.Invoke(status);
|
||||
}
|
||||
};
|
||||
|
||||
// Install the package with progress
|
||||
await Task.Run(() =>
|
||||
{
|
||||
packageManager.InstallPackage(path, installProgress);
|
||||
});
|
||||
|
||||
progressCallback?.Invoke(100);
|
||||
progressCallback?.Invoke(100, null);
|
||||
statusCallback?.Invoke("");
|
||||
|
||||
return new ProcessOutput($"{gameName}: Success\n");
|
||||
@@ -220,19 +287,38 @@ namespace AndroidSideloader
|
||||
{
|
||||
Logger.Log($"SideloadWithProgressAsync error: {ex.Message}", LogLevel.ERROR);
|
||||
|
||||
// Check for signature mismatch errors
|
||||
if (ex.Message.Contains("INSTALL_FAILED") ||
|
||||
ex.Message.Contains("signatures do not match"))
|
||||
// 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;
|
||||
});
|
||||
@@ -241,7 +327,6 @@ namespace AndroidSideloader
|
||||
if (cancelClicked)
|
||||
return new ProcessOutput("", "Installation cancelled by user");
|
||||
|
||||
// Perform reinstall
|
||||
statusCallback?.Invoke("Performing reinstall...");
|
||||
|
||||
try
|
||||
@@ -250,52 +335,49 @@ namespace AndroidSideloader
|
||||
var client = GetAdbClient();
|
||||
var packageManager = new PackageManager(client, device);
|
||||
|
||||
// Backup save data
|
||||
statusCallback?.Invoke("Backing up save data...");
|
||||
_ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\"");
|
||||
_ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{packagename}\" \"{Environment.CurrentDirectory}\"");
|
||||
|
||||
// Uninstall
|
||||
statusCallback?.Invoke("Uninstalling old version...");
|
||||
packageManager.UninstallPackage(packagename);
|
||||
|
||||
// Reinstall with progress
|
||||
statusCallback?.Invoke("Reinstalling game...");
|
||||
Action<InstallProgressEventArgs> reinstallProgress = (args) =>
|
||||
{
|
||||
if (args.State == PackageInstallProgressState.Uploading)
|
||||
{
|
||||
progressCallback?.Invoke((int)Math.Round(args.UploadProgress));
|
||||
progressCallback?.Invoke((float)args.UploadProgress, null);
|
||||
}
|
||||
};
|
||||
packageManager.InstallPackage(path, reinstallProgress);
|
||||
|
||||
// Restore save data
|
||||
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);
|
||||
progressCallback?.Invoke(100, null);
|
||||
return new ProcessOutput($"{gameName}: Reinstall: Success\n", "");
|
||||
}
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
// Copies OBB folder with real-time progress reporting using AdvancedSharpAdbClient
|
||||
public static async Task<ProcessOutput> CopyOBBWithProgressAsync(
|
||||
string localPath,
|
||||
Action<int> progressCallback = null,
|
||||
Action<float, TimeSpan?> progressCallback = null,
|
||||
Action<string> statusCallback = null,
|
||||
string gameName = "")
|
||||
{
|
||||
@@ -318,7 +400,7 @@ namespace AndroidSideloader
|
||||
string remotePath = $"/sdcard/Android/obb/{folderName}";
|
||||
|
||||
statusCallback?.Invoke($"Preparing: {folderName}");
|
||||
progressCallback?.Invoke(0);
|
||||
progressCallback?.Invoke(0, null);
|
||||
|
||||
// Delete existing OBB folder and create new one
|
||||
ExecuteShellCommand(client, device, $"rm -rf \"{remotePath}\"");
|
||||
@@ -329,6 +411,14 @@ namespace AndroidSideloader
|
||||
long totalBytes = files.Sum(f => new FileInfo(f).Length);
|
||||
long transferredBytes = 0;
|
||||
|
||||
// Throttle UI updates to prevent lag
|
||||
DateTime lastProgressUpdate = DateTime.MinValue;
|
||||
float lastReportedPercent = -1;
|
||||
const int ThrottleMs = 100; // Update UI every 100ms
|
||||
|
||||
// Shared ETA engine (bytes-units)
|
||||
var eta = new EtaEstimator(alpha: 0.10, reanchorThreshold: 0.20);
|
||||
|
||||
statusCallback?.Invoke($"Copying: {folderName}");
|
||||
|
||||
using (var syncService = new SyncService(client, device))
|
||||
@@ -341,9 +431,6 @@ namespace AndroidSideloader
|
||||
string remoteFilePath = $"{remotePath}/{relativePath}";
|
||||
string fileName = Path.GetFileName(file);
|
||||
|
||||
// Let UI know which file we're currently on
|
||||
statusCallback?.Invoke(fileName);
|
||||
|
||||
// Ensure remote directory exists
|
||||
string remoteDir = remoteFilePath.Substring(0, remoteFilePath.LastIndexOf('/'));
|
||||
ExecuteShellCommand(client, device, $"mkdir -p \"{remoteDir}\"");
|
||||
@@ -352,23 +439,37 @@ namespace AndroidSideloader
|
||||
long fileSize = fileInfo.Length;
|
||||
long capturedTransferredBytes = transferredBytes;
|
||||
|
||||
// Progress handler for this file
|
||||
Action<SyncProgressChangedEventArgs> progressHandler = (args) =>
|
||||
{
|
||||
long totalProgressBytes = capturedTransferredBytes + args.ReceivedBytesSize;
|
||||
|
||||
double overallPercent = totalBytes > 0
|
||||
? (totalProgressBytes * 100.0) / totalBytes
|
||||
: 0.0;
|
||||
float overallPercent = totalBytes > 0
|
||||
? (float)(totalProgressBytes * 100.0 / totalBytes)
|
||||
: 0f;
|
||||
|
||||
int overallPercentInt = (int)Math.Round(overallPercent);
|
||||
overallPercentInt = Math.Max(0, Math.Min(100, overallPercentInt));
|
||||
overallPercent = Math.Max(0, Math.Min(100, overallPercent));
|
||||
|
||||
// Single source of truth for UI (bar + label + text)
|
||||
progressCallback?.Invoke(overallPercentInt);
|
||||
// Update ETA engine in bytes
|
||||
if (totalBytes > 0 && totalProgressBytes > 0 && overallPercent < 100)
|
||||
{
|
||||
eta.Update(totalUnits: totalBytes, doneUnits: totalProgressBytes);
|
||||
}
|
||||
|
||||
TimeSpan? displayEta = eta.GetDisplayEta();
|
||||
|
||||
var now2 = DateTime.UtcNow;
|
||||
bool shouldUpdate = (now2 - lastProgressUpdate).TotalMilliseconds >= ThrottleMs
|
||||
|| Math.Abs(overallPercent - lastReportedPercent) >= 0.1f;
|
||||
|
||||
if (shouldUpdate)
|
||||
{
|
||||
lastProgressUpdate = now2;
|
||||
lastReportedPercent = overallPercent;
|
||||
progressCallback?.Invoke(overallPercent, displayEta);
|
||||
statusCallback?.Invoke(fileName);
|
||||
}
|
||||
};
|
||||
|
||||
// Push the file with progress
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
await Task.Run(() =>
|
||||
@@ -383,13 +484,11 @@ namespace AndroidSideloader
|
||||
});
|
||||
}
|
||||
|
||||
// Mark this file as fully transferred
|
||||
transferredBytes += fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure final 100% and clear status
|
||||
progressCallback?.Invoke(100);
|
||||
progressCallback?.Invoke(100, null);
|
||||
statusCallback?.Invoke("");
|
||||
|
||||
return new ProcessOutput($"{gameName}: OBB transfer: Success\n", "");
|
||||
@@ -397,7 +496,6 @@ namespace AndroidSideloader
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"CopyOBBWithProgressAsync error: {ex.Message}", LogLevel.ERROR);
|
||||
|
||||
return new ProcessOutput("", $"{gameName}: OBB transfer: Failed: {ex.Message}\n");
|
||||
}
|
||||
}
|
||||
@@ -582,78 +680,103 @@ 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 = "")
|
||||
internal class EtaEstimator
|
||||
{
|
||||
private readonly double _alpha; // EWMA smoothing
|
||||
private readonly double _reanchorThreshold; // % difference required to re-anchor
|
||||
private readonly double _minSampleSeconds; // ignore too-short dt
|
||||
|
||||
private DateTime _lastSampleTimeUtc;
|
||||
private long _lastSampleDoneUnits;
|
||||
private double _smoothedUnitsPerSecond;
|
||||
|
||||
private TimeSpan? _etaAnchorValue;
|
||||
private DateTime _etaAnchorTimeUtc;
|
||||
|
||||
public EtaEstimator(double alpha, double reanchorThreshold, double minSampleSeconds = 0.15)
|
||||
{
|
||||
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;
|
||||
_alpha = alpha;
|
||||
_reanchorThreshold = reanchorThreshold;
|
||||
_minSampleSeconds = minSampleSeconds;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public static ProcessOutput CopyOBB(string path)
|
||||
public void Reset()
|
||||
{
|
||||
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");
|
||||
_lastSampleTimeUtc = DateTime.UtcNow;
|
||||
_lastSampleDoneUnits = 0;
|
||||
_smoothedUnitsPerSecond = 0;
|
||||
_etaAnchorValue = null;
|
||||
_etaAnchorTimeUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// Updates internal rate estimate and re-anchors ETA
|
||||
// totalUnits: total work units (e.g., 100 for percent, or totalBytes for bytes)
|
||||
// doneUnits: completed work units so far (e.g., percent, or bytes transferred)
|
||||
public void Update(long totalUnits, long doneUnits)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (totalUnits <= 0) return;
|
||||
|
||||
doneUnits = Math.Max(0, Math.Min(totalUnits, doneUnits));
|
||||
|
||||
long remainingUnits = Math.Max(0, totalUnits - doneUnits);
|
||||
|
||||
double dt = (now - _lastSampleTimeUtc).TotalSeconds;
|
||||
long dUnits = doneUnits - _lastSampleDoneUnits;
|
||||
|
||||
if (dt >= _minSampleSeconds && dUnits > 0)
|
||||
{
|
||||
double instUnitsPerSecond = dUnits / dt;
|
||||
|
||||
if (_smoothedUnitsPerSecond <= 0)
|
||||
_smoothedUnitsPerSecond = instUnitsPerSecond;
|
||||
else
|
||||
_smoothedUnitsPerSecond = _alpha * instUnitsPerSecond + (1 - _alpha) * _smoothedUnitsPerSecond;
|
||||
|
||||
_lastSampleTimeUtc = now;
|
||||
_lastSampleDoneUnits = doneUnits;
|
||||
}
|
||||
|
||||
if (_smoothedUnitsPerSecond > 1e-6 && remainingUnits > 0)
|
||||
{
|
||||
var newEta = TimeSpan.FromSeconds(remainingUnits / _smoothedUnitsPerSecond);
|
||||
if (newEta < TimeSpan.Zero) newEta = TimeSpan.Zero;
|
||||
|
||||
if (!_etaAnchorValue.HasValue)
|
||||
{
|
||||
_etaAnchorValue = newEta;
|
||||
_etaAnchorTimeUtc = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
// What countdown would currently show
|
||||
var predictedNow = _etaAnchorValue.Value - (now - _etaAnchorTimeUtc);
|
||||
if (predictedNow < TimeSpan.Zero) predictedNow = TimeSpan.Zero;
|
||||
|
||||
double baseSeconds = Math.Max(1, predictedNow.TotalSeconds);
|
||||
double diffRatio = Math.Abs(newEta.TotalSeconds - predictedNow.TotalSeconds) / baseSeconds;
|
||||
|
||||
if (diffRatio > _reanchorThreshold)
|
||||
{
|
||||
_etaAnchorValue = newEta;
|
||||
_etaAnchorTimeUtc = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a countdown ETA for UI display
|
||||
public TimeSpan? GetDisplayEta()
|
||||
{
|
||||
if (!_etaAnchorValue.HasValue) return null;
|
||||
|
||||
var remaining = _etaAnchorValue.Value - (DateTime.UtcNow - _etaAnchorTimeUtc);
|
||||
if (remaining < TimeSpan.Zero) remaining = TimeSpan.Zero;
|
||||
|
||||
return TimeSpan.FromSeconds(Math.Ceiling(remaining.TotalSeconds));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,10 @@ namespace AndroidSideloader
|
||||
public AdbCommandForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Use same icon as the executable
|
||||
this.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
this.ShowIcon = true; // Enable icon
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
|
||||
@@ -190,9 +190,13 @@
|
||||
<Compile Include="GalleryView.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ModernListView.cs" />
|
||||
<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>
|
||||
@@ -240,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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Windows.Forms;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,23 +42,41 @@ public class ListViewColumnSorter : IComparer
|
||||
ListViewItem listviewX = (ListViewItem)x;
|
||||
ListViewItem listviewY = (ListViewItem)y;
|
||||
|
||||
// Determine if the column requires numeric comparison
|
||||
if (SortColumn == 3 || SortColumn == 5) // Numeric columns: VersionCodeIndex, VersionNameIndex
|
||||
// Special handling for column 6 (Popularity ranking)
|
||||
if (SortColumn == 6)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse and compare numeric values directly
|
||||
int xNum = ParseNumber(listviewX.SubItems[SortColumn].Text);
|
||||
int yNum = ParseNumber(listviewY.SubItems[SortColumn].Text);
|
||||
string textX = listviewX.SubItems[SortColumn].Text;
|
||||
string textY = listviewY.SubItems[SortColumn].Text;
|
||||
|
||||
// Compare numerically
|
||||
compareResult = xNum.CompareTo(yNum);
|
||||
}
|
||||
catch
|
||||
// Extract numeric values from "#1", "#10", etc.
|
||||
// "-" represents unranked and should go to the end
|
||||
int rankX = int.MaxValue; // Default for unranked (-)
|
||||
int rankY = int.MaxValue;
|
||||
|
||||
if (textX.StartsWith("#") && int.TryParse(textX.Substring(1), out int parsedX))
|
||||
{
|
||||
// Fallback to string comparison if parsing fails
|
||||
compareResult = ObjectCompare.Compare(listviewX.SubItems[SortColumn].Text, listviewY.SubItems[SortColumn].Text);
|
||||
rankX = parsedX;
|
||||
}
|
||||
|
||||
if (textY.StartsWith("#") && int.TryParse(textY.Substring(1), out int parsedY))
|
||||
{
|
||||
rankY = parsedY;
|
||||
}
|
||||
|
||||
// Compare the numeric ranks
|
||||
compareResult = rankX.CompareTo(rankY);
|
||||
}
|
||||
// Special handling for column 5 (Size)
|
||||
else if (SortColumn == 5)
|
||||
{
|
||||
string textX = listviewX.SubItems[SortColumn].Text;
|
||||
string textY = listviewY.SubItems[SortColumn].Text;
|
||||
|
||||
double sizeX = ParseSize(textX);
|
||||
double sizeY = ParseSize(textY);
|
||||
|
||||
// Compare the numeric sizes
|
||||
compareResult = sizeX.CompareTo(sizeY);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -91,6 +110,49 @@ public class ListViewColumnSorter : IComparer
|
||||
return int.TryParse(text, out int result) ? result : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses size strings with units (GB/MB) and converts them to MB for comparison.
|
||||
/// </summary>
|
||||
/// <param name="sizeStr">Size string (e.g., "1.23 GB", "123 MB")</param>
|
||||
/// <returns>Size in MB as a double</returns>
|
||||
private double ParseSize(string sizeStr)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sizeStr))
|
||||
return 0;
|
||||
|
||||
// Remove whitespace
|
||||
sizeStr = sizeStr.Trim();
|
||||
|
||||
// Handle new format: "1.23 GB" or "123 MB"
|
||||
if (sizeStr.EndsWith(" GB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string numPart = sizeStr.Substring(0, sizeStr.Length - 3).Trim();
|
||||
if (double.TryParse(numPart, System.Globalization.NumberStyles.Any,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out double gb))
|
||||
{
|
||||
return gb * 1024.0; // Convert GB to MB for consistent sorting
|
||||
}
|
||||
}
|
||||
else if (sizeStr.EndsWith(" MB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string numPart = sizeStr.Substring(0, sizeStr.Length - 3).Trim();
|
||||
if (double.TryParse(numPart, System.Globalization.NumberStyles.Any,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out double mb))
|
||||
{
|
||||
return mb;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try parsing as raw number
|
||||
if (double.TryParse(sizeStr, System.Globalization.NumberStyles.Any,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out double rawMb))
|
||||
{
|
||||
return rawMb;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the column to be sorted (default is '0').
|
||||
/// </summary>
|
||||
|
||||
2
DonorsListView.Designer.cs
generated
2
DonorsListView.Designer.cs
generated
@@ -30,7 +30,6 @@ namespace AndroidSideloader
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DonorsListViewForm));
|
||||
this.DonationTimer = new System.Windows.Forms.Timer(this.components);
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.skip_forever = new AndroidSideloader.RoundButton();
|
||||
@@ -310,7 +309,6 @@ namespace AndroidSideloader
|
||||
this.Controls.Add(this.panel1);
|
||||
this.ForeColor = System.Drawing.Color.White;
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "DonorsListViewForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Load += new System.EventHandler(this.DonorsListViewForm_Load);
|
||||
|
||||
@@ -42,6 +42,10 @@ namespace AndroidSideloader
|
||||
public DonorsListViewForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Use same icon as the executable
|
||||
this.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
|
||||
ApplyModernTheme();
|
||||
CenterToScreen();
|
||||
|
||||
|
||||
6849
DonorsListView.resx
6849
DonorsListView.resx
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -170,7 +184,7 @@ namespace JR.Utils.GUI.Forms
|
||||
titleLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
||||
titleLabel.Location = new System.Drawing.Point(0, 0);
|
||||
titleLabel.Name = "titleLabel";
|
||||
titleLabel.Padding = new System.Windows.Forms.Padding(18, 0, 0, 0);
|
||||
titleLabel.Padding = new System.Windows.Forms.Padding(12, 0, 0, 0);
|
||||
titleLabel.Size = new System.Drawing.Size(218, 28);
|
||||
titleLabel.TabIndex = 0;
|
||||
titleLabel.Text = "<Caption>";
|
||||
@@ -198,7 +212,7 @@ namespace JR.Utils.GUI.Forms
|
||||
//
|
||||
button1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
button1.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
button1.Location = new System.Drawing.Point(16, 80);
|
||||
button1.Location = new System.Drawing.Point(26, 80);
|
||||
button1.Name = "button1";
|
||||
button1.Size = new System.Drawing.Size(75, 28);
|
||||
button1.TabIndex = 2;
|
||||
@@ -222,7 +236,7 @@ namespace JR.Utils.GUI.Forms
|
||||
richTextBoxMessage.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
richTextBoxMessage.DataBindings.Add(new System.Windows.Forms.Binding("Text", FlexibleMessageBoxFormBindingSource, "MessageText", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
|
||||
richTextBoxMessage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
||||
richTextBoxMessage.Location = new System.Drawing.Point(52, 6);
|
||||
richTextBoxMessage.Location = new System.Drawing.Point(46, 6);
|
||||
richTextBoxMessage.Margin = new System.Windows.Forms.Padding(0);
|
||||
richTextBoxMessage.Name = "richTextBoxMessage";
|
||||
richTextBoxMessage.ReadOnly = true;
|
||||
@@ -259,7 +273,7 @@ namespace JR.Utils.GUI.Forms
|
||||
//
|
||||
button2.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
button2.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
button2.Location = new System.Drawing.Point(97, 80);
|
||||
button2.Location = new System.Drawing.Point(107, 80);
|
||||
button2.Name = "button2";
|
||||
button2.Size = new System.Drawing.Size(75, 28);
|
||||
button2.TabIndex = 3;
|
||||
@@ -277,7 +291,7 @@ namespace JR.Utils.GUI.Forms
|
||||
//
|
||||
button3.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||
button3.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
button3.Location = new System.Drawing.Point(178, 80);
|
||||
button3.Location = new System.Drawing.Point(188, 80);
|
||||
button3.Name = "button3";
|
||||
button3.Size = new System.Drawing.Size(75, 28);
|
||||
button3.TabIndex = 0;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -843,8 +857,50 @@ namespace JR.Utils.GUI.Forms
|
||||
flexibleMessageBoxForm.richTextBoxMessage.Font = FONT;
|
||||
|
||||
SetDialogSizes(flexibleMessageBoxForm, text, caption);
|
||||
|
||||
// Force panel resize to fix closebutton position
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
1292
GalleryView.cs
1292
GalleryView.cs
File diff suppressed because it is too large
Load Diff
324
MainForm.Designer.cs
generated
324
MainForm.Designer.cs
generated
@@ -34,9 +34,7 @@ namespace AndroidSideloader
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.m_combo = new SergeUtils.EasyCompletionComboBox();
|
||||
this.progressBar = new AndroidSideloader.ModernProgressBar();
|
||||
this.speedLabel = new System.Windows.Forms.Label();
|
||||
this.etaLabel = new System.Windows.Forms.Label();
|
||||
this.freeDisclaimer = new System.Windows.Forms.Label();
|
||||
this.gamesQueListBox = new System.Windows.Forms.ListBox();
|
||||
this.devicesComboBox = new System.Windows.Forms.ComboBox();
|
||||
@@ -51,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);
|
||||
@@ -121,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();
|
||||
@@ -132,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();
|
||||
@@ -153,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
|
||||
@@ -167,65 +165,24 @@ 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;
|
||||
//
|
||||
// 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, 18);
|
||||
this.progressBar.Maximum = 100;
|
||||
this.progressBar.Minimum = 0;
|
||||
this.progressBar.MinimumSize = new System.Drawing.Size(200, 13);
|
||||
this.progressBar.Name = "progressBar";
|
||||
this.progressBar.OperationType = "";
|
||||
this.progressBar.ProgressEndColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(160)))), ((int)(((byte)(130)))));
|
||||
this.progressBar.ProgressStartColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(220)))), ((int)(((byte)(190)))));
|
||||
this.progressBar.Radius = 6;
|
||||
this.progressBar.Size = new System.Drawing.Size(983, 13);
|
||||
this.progressBar.StatusText = "";
|
||||
this.progressBar.TabIndex = 7;
|
||||
this.progressBar.TextColor = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(230)))), ((int)(((byte)(230)))));
|
||||
this.progressBar.Value = 0;
|
||||
//
|
||||
// speedLabel
|
||||
//
|
||||
this.speedLabel.AutoSize = true;
|
||||
this.speedLabel.BackColor = System.Drawing.Color.Transparent;
|
||||
this.speedLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold);
|
||||
this.speedLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.speedLabel.Location = new System.Drawing.Point(-2, -3);
|
||||
this.speedLabel.Location = new System.Drawing.Point(-1, 3);
|
||||
this.speedLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.speedLabel.Name = "speedLabel";
|
||||
this.speedLabel.Size = new System.Drawing.Size(152, 16);
|
||||
this.speedLabel.TabIndex = 76;
|
||||
this.speedLabel.Text = "DLS: Speed in MBPS";
|
||||
this.speedLabel_Tooltip.SetToolTip(this.speedLabel, "Current download speed, updates every second, in mbps");
|
||||
//
|
||||
// etaLabel
|
||||
//
|
||||
this.etaLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.etaLabel.BackColor = System.Drawing.Color.Transparent;
|
||||
this.etaLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.etaLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.etaLabel.Location = new System.Drawing.Point(790, -3);
|
||||
this.etaLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
|
||||
this.etaLabel.Name = "etaLabel";
|
||||
this.etaLabel.Size = new System.Drawing.Size(196, 18);
|
||||
this.etaLabel.TabIndex = 75;
|
||||
this.etaLabel.Text = "ETA: HH:MM:SS Left";
|
||||
this.etaLabel.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
this.etaLabel_Tooltip.SetToolTip(this.etaLabel, "Estimated time when game will finish download, updates every 5 seconds, format is" +
|
||||
" HH:MM:SS");
|
||||
//
|
||||
// freeDisclaimer
|
||||
//
|
||||
@@ -257,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
|
||||
//
|
||||
@@ -272,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;
|
||||
@@ -288,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);
|
||||
@@ -310,10 +263,12 @@ namespace AndroidSideloader
|
||||
this.DownloadsIndex});
|
||||
this.gamesListView.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.gamesListView.ForeColor = System.Drawing.Color.White;
|
||||
this.gamesListView.FullRowSelect = true;
|
||||
this.gamesListView.HideSelection = false;
|
||||
this.gamesListView.ImeMode = System.Windows.Forms.ImeMode.Off;
|
||||
this.gamesListView.Location = new System.Drawing.Point(258, 44);
|
||||
this.gamesListView.Name = "gamesListView";
|
||||
this.gamesListView.OwnerDraw = true;
|
||||
this.gamesListView.ShowGroups = false;
|
||||
this.gamesListView.Size = new System.Drawing.Size(984, 409);
|
||||
this.gamesListView.TabIndex = 6;
|
||||
@@ -329,39 +284,41 @@ namespace AndroidSideloader
|
||||
// GameNameIndex
|
||||
//
|
||||
this.GameNameIndex.Text = "Game Name";
|
||||
this.GameNameIndex.Width = 158;
|
||||
this.GameNameIndex.Width = 160;
|
||||
//
|
||||
// ReleaseNameIndex
|
||||
//
|
||||
this.ReleaseNameIndex.Text = "Release Name";
|
||||
this.ReleaseNameIndex.Width = 244;
|
||||
this.ReleaseNameIndex.Width = 220;
|
||||
//
|
||||
// PackageNameIndex
|
||||
//
|
||||
this.PackageNameIndex.Text = "Package Name";
|
||||
this.PackageNameIndex.Width = 87;
|
||||
this.PackageNameIndex.Width = 120;
|
||||
//
|
||||
// VersionCodeIndex
|
||||
//
|
||||
this.VersionCodeIndex.Text = "Version";
|
||||
this.VersionCodeIndex.Width = 75;
|
||||
this.VersionCodeIndex.Text = "Version (Rookie/Local)";
|
||||
this.VersionCodeIndex.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
this.VersionCodeIndex.Width = 164;
|
||||
//
|
||||
// ReleaseAPKPathIndex
|
||||
//
|
||||
this.ReleaseAPKPathIndex.Text = "Last Updated";
|
||||
this.ReleaseAPKPathIndex.Width = 145;
|
||||
this.ReleaseAPKPathIndex.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
this.ReleaseAPKPathIndex.Width = 135;
|
||||
//
|
||||
// VersionNameIndex
|
||||
//
|
||||
this.VersionNameIndex.Text = "Size (MB)";
|
||||
this.VersionNameIndex.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
|
||||
this.VersionNameIndex.Width = 66;
|
||||
this.VersionNameIndex.Text = "Size";
|
||||
this.VersionNameIndex.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
this.VersionNameIndex.Width = 85;
|
||||
//
|
||||
// DownloadsIndex
|
||||
//
|
||||
this.DownloadsIndex.Text = "Popularity";
|
||||
this.DownloadsIndex.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
|
||||
this.DownloadsIndex.Width = 80;
|
||||
this.DownloadsIndex.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
|
||||
this.DownloadsIndex.Width = 100;
|
||||
//
|
||||
// gamesQueueLabel
|
||||
//
|
||||
@@ -382,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";
|
||||
@@ -392,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)));
|
||||
@@ -514,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;
|
||||
@@ -534,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);
|
||||
//
|
||||
@@ -554,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);
|
||||
//
|
||||
@@ -808,12 +752,11 @@ namespace AndroidSideloader
|
||||
this.progressDLbtnContainer.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
this.progressDLbtnContainer.BackColor = System.Drawing.Color.Transparent;
|
||||
this.progressDLbtnContainer.Controls.Add(this.progressBar);
|
||||
this.progressDLbtnContainer.Controls.Add(this.etaLabel);
|
||||
this.progressDLbtnContainer.Controls.Add(this.speedLabel);
|
||||
this.progressDLbtnContainer.Location = new System.Drawing.Point(258, 459);
|
||||
this.progressDLbtnContainer.Location = new System.Drawing.Point(258, 453);
|
||||
this.progressDLbtnContainer.MinimumSize = new System.Drawing.Size(600, 34);
|
||||
this.progressDLbtnContainer.Name = "progressDLbtnContainer";
|
||||
this.progressDLbtnContainer.Size = new System.Drawing.Size(984, 34);
|
||||
this.progressDLbtnContainer.Size = new System.Drawing.Size(984, 40);
|
||||
this.progressDLbtnContainer.TabIndex = 96;
|
||||
//
|
||||
// diskLabel
|
||||
@@ -1281,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)))));
|
||||
@@ -1348,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)))));
|
||||
@@ -1531,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
|
||||
//
|
||||
@@ -1594,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);
|
||||
@@ -1633,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();
|
||||
|
||||
@@ -1646,7 +1613,6 @@ namespace AndroidSideloader
|
||||
#endregion
|
||||
private SergeUtils.EasyCompletionComboBox m_combo;
|
||||
private ModernProgressBar progressBar;
|
||||
private System.Windows.Forms.Label etaLabel;
|
||||
private System.Windows.Forms.Label speedLabel;
|
||||
private System.Windows.Forms.Label freeDisclaimer;
|
||||
private System.Windows.Forms.ComboBox devicesComboBox;
|
||||
@@ -1657,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;
|
||||
@@ -1751,5 +1716,6 @@ namespace AndroidSideloader
|
||||
private Label activeMirrorLabel;
|
||||
private Label sideloadingStatusLabel;
|
||||
private Label rookieStatusLabel;
|
||||
private ModernListView _listViewRenderer;
|
||||
}
|
||||
}
|
||||
4123
MainForm.cs
4123
MainForm.cs
File diff suppressed because it is too large
Load Diff
@@ -120,9 +120,6 @@
|
||||
<metadata name="speedLabel_Tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>1165, 17</value>
|
||||
</metadata>
|
||||
<metadata name="etaLabel_Tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>428, 54</value>
|
||||
</metadata>
|
||||
<metadata name="startsideloadbutton_Tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>966, 17</value>
|
||||
</metadata>
|
||||
@@ -177,6 +174,9 @@
|
||||
<metadata name="listApkButton_Tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>1320, 17</value>
|
||||
</metadata>
|
||||
<metadata name="etaLabel_Tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>428, 54</value>
|
||||
</metadata>
|
||||
<metadata name="favoriteGame.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>1021, 91</value>
|
||||
</metadata>
|
||||
|
||||
1308
ModernListView.cs
Normal file
1308
ModernListView.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,9 @@ namespace AndroidSideloader
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private int _value;
|
||||
private int _minimum;
|
||||
private int _maximum = 100;
|
||||
private float _value;
|
||||
private float _minimum;
|
||||
private float _maximum = 100f;
|
||||
private int _radius = 8;
|
||||
private bool _isIndeterminate;
|
||||
private string _statusText = string.Empty;
|
||||
@@ -66,7 +66,7 @@ namespace AndroidSideloader
|
||||
|
||||
[Category("Progress")]
|
||||
[Description("The current value of the progress bar.")]
|
||||
public int Value
|
||||
public float Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
@@ -78,7 +78,7 @@ namespace AndroidSideloader
|
||||
|
||||
[Category("Progress")]
|
||||
[Description("The minimum value of the progress bar.")]
|
||||
public int Minimum
|
||||
public float Minimum
|
||||
{
|
||||
get => _minimum;
|
||||
set
|
||||
@@ -91,7 +91,7 @@ namespace AndroidSideloader
|
||||
|
||||
[Category("Progress")]
|
||||
[Description("The maximum value of the progress bar.")]
|
||||
public int Maximum
|
||||
public float Maximum
|
||||
{
|
||||
get => _maximum;
|
||||
set
|
||||
@@ -122,7 +122,7 @@ namespace AndroidSideloader
|
||||
set
|
||||
{
|
||||
// If there is no change, do nothing
|
||||
if (_isIndeterminate == value)
|
||||
if (_isIndeterminate == value)
|
||||
return;
|
||||
|
||||
_isIndeterminate = value;
|
||||
@@ -205,7 +205,7 @@ namespace AndroidSideloader
|
||||
|
||||
// Gets the progress as a percentage (0-100)
|
||||
public float ProgressPercent =>
|
||||
_maximum > _minimum ? (float)(_value - _minimum) / (_maximum - _minimum) * 100f : 0f;
|
||||
_maximum > _minimum ? (_value - _minimum) / (_maximum - _minimum) * 100f : 0f;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -250,7 +250,7 @@ namespace AndroidSideloader
|
||||
private void DrawProgress(Graphics g, Rectangle outerRect)
|
||||
{
|
||||
float percent = (_maximum > _minimum)
|
||||
? (float)(_value - _minimum) / (_maximum - _minimum)
|
||||
? (_value - _minimum) / (_maximum - _minimum)
|
||||
: 0f;
|
||||
|
||||
if (percent <= 0f) return;
|
||||
@@ -363,10 +363,11 @@ namespace AndroidSideloader
|
||||
|
||||
if (!_isIndeterminate && _value > _minimum)
|
||||
{
|
||||
string percentText = $"{(int)ProgressPercent}%";
|
||||
// Show one decimal place for sub-percent precision
|
||||
string percentText = $"{ProgressPercent:0.0}%";
|
||||
if (!string.IsNullOrEmpty(_operationType))
|
||||
{
|
||||
// E.g. "Downloading · 73%"
|
||||
// E.g. "Downloading · 73.5%"
|
||||
return $"{_operationType} · {percentText}";
|
||||
}
|
||||
return percentText;
|
||||
@@ -435,4 +436,4 @@ namespace AndroidSideloader
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
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; }
|
||||
}
|
||||
}
|
||||
2
NewApps.Designer.cs
generated
2
NewApps.Designer.cs
generated
@@ -29,7 +29,6 @@ namespace AndroidSideloader
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NewApps));
|
||||
this.NewAppsListView = new System.Windows.Forms.ListView();
|
||||
this.GameNameIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.PackageNameIndex = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
@@ -173,7 +172,6 @@ namespace AndroidSideloader
|
||||
this.Controls.Add(this.panel1);
|
||||
this.ForeColor = System.Drawing.Color.White;
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "NewApps";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Load += new System.EventHandler(this.NewApps_Load);
|
||||
|
||||
@@ -39,6 +39,10 @@ namespace AndroidSideloader
|
||||
public NewApps()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Use same icon as the executable
|
||||
this.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
|
||||
ApplyModernTheme();
|
||||
CenterToScreen();
|
||||
}
|
||||
|
||||
6849
NewApps.resx
6849
NewApps.resx
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
3
QuestForm.Designer.cs
generated
3
QuestForm.Designer.cs
generated
@@ -30,7 +30,6 @@ namespace AndroidSideloader
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(QuestForm));
|
||||
this.lblUsernameSection = new System.Windows.Forms.Label();
|
||||
this.lblMediaSection = new System.Windows.Forms.Label();
|
||||
this.lblPerformanceSection = new System.Windows.Forms.Label();
|
||||
@@ -444,11 +443,9 @@ namespace AndroidSideloader
|
||||
this.Controls.Add(this.btnApplyTempSettings);
|
||||
this.Controls.Add(this.btnClose);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "QuestForm";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Quest Settings";
|
||||
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.QuestForm_FormClosed);
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace AndroidSideloader
|
||||
public QuestForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Use same icon as the executable
|
||||
this.Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
}
|
||||
|
||||
private void btnApplyTempSettings_Click(object sender, EventArgs e)
|
||||
|
||||
6849
QuestForm.resx
6849
QuestForm.resx
File diff suppressed because it is too large
Load Diff
3
SettingsForm.Designer.cs
generated
3
SettingsForm.Designer.cs
generated
@@ -28,7 +28,6 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SettingsForm));
|
||||
this.downloadDirectorySetter = new System.Windows.Forms.FolderBrowserDialog();
|
||||
this.backupDirectorySetter = new System.Windows.Forms.FolderBrowserDialog();
|
||||
this.crashlogID = new System.Windows.Forms.Label();
|
||||
@@ -866,11 +865,9 @@
|
||||
this.Controls.Add(this.resetSettingsButton);
|
||||
this.ForeColor = System.Drawing.Color.White;
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "SettingsForm";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Settings";
|
||||
this.Load += new System.EventHandler(this.SettingsForm_Load);
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace AndroidSideloader
|
||||
public SettingsForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Use same icon as the executable
|
||||
this.Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
}
|
||||
|
||||
private void SettingsForm_Load(object sender, EventArgs e)
|
||||
@@ -324,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,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);
|
||||
}
|
||||
|
||||
|
||||
6849
SettingsForm.resx
6849
SettingsForm.resx
File diff suppressed because it is too large
Load Diff
@@ -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}");
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace AndroidSideloader
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
string wantedRcloneVersion = "1.68.2";
|
||||
string wantedRcloneVersion = "1.72.1";
|
||||
bool rcloneSuccess = false;
|
||||
|
||||
rcloneSuccess = downloadRclone(wantedRcloneVersion, false);
|
||||
@@ -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))
|
||||
|
||||
@@ -14,8 +14,6 @@ namespace AndroidSideloader
|
||||
|
||||
public static string RcloneGamesFolder = "Quest Games";
|
||||
|
||||
//This shit sucks but i'll switch to programatically adding indexes from the gamelist txt sometimes maybe
|
||||
|
||||
public static int GameNameIndex = 0;
|
||||
public static int ReleaseNameIndex = 1;
|
||||
public static int PackageNameIndex = 2;
|
||||
@@ -23,6 +21,7 @@ namespace AndroidSideloader
|
||||
public static int ReleaseAPKPathIndex = 4;
|
||||
public static int VersionNameIndex = 5;
|
||||
public static int DownloadsIndex = 6;
|
||||
public static int InstalledVersion = 7;
|
||||
|
||||
public static List<string> gameProperties = new List<string>();
|
||||
/* Game Name
|
||||
@@ -291,7 +290,7 @@ namespace AndroidSideloader
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
Directory.Delete(path, true);
|
||||
FileSystemUtilities.TryDeleteDirectory(path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
2
UpdateForm.Designer.cs
generated
2
UpdateForm.Designer.cs
generated
@@ -28,7 +28,6 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UpdateForm));
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.YesUpdate = new AndroidSideloader.RoundButton();
|
||||
this.panel3 = new System.Windows.Forms.Panel();
|
||||
@@ -166,7 +165,6 @@
|
||||
this.ControlBox = false;
|
||||
this.Controls.Add(this.panel1);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "UpdateForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.UpdateForm_MouseDown);
|
||||
|
||||
@@ -20,6 +20,10 @@ namespace AndroidSideloader
|
||||
public UpdateForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Use same icon as the executable
|
||||
this.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
|
||||
ApplyModernTheme();
|
||||
CenterToScreen();
|
||||
CurVerLabel.Text = $"Current Version: {Updater.LocalVersion}";
|
||||
|
||||
6849
UpdateForm.resx
6849
UpdateForm.resx
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
|
||||
20
UsernameForm.Designer.cs
generated
20
UsernameForm.Designer.cs
generated
@@ -28,28 +28,30 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UsernameForm));
|
||||
this.textBox1 = new System.Windows.Forms.TextBox();
|
||||
this.button1 = new AndroidSideloader.RoundButton();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
//
|
||||
// textBox1
|
||||
//
|
||||
//
|
||||
this.textBox1.BackColor = global::AndroidSideloader.Properties.Settings.Default.TextBoxColor;
|
||||
this.textBox1.Font = global::AndroidSideloader.Properties.Settings.Default.FontStyle;
|
||||
this.textBox1.ForeColor = System.Drawing.Color.White;
|
||||
this.textBox1.Location = new System.Drawing.Point(13, 13);
|
||||
this.textBox1.Name = "textBox1";
|
||||
this.textBox1.Size = new System.Drawing.Size(418, 24);
|
||||
this.textBox1.Size = new System.Drawing.Size(418, 23);
|
||||
this.textBox1.TabIndex = 0;
|
||||
this.textBox1.Text = "Enter your username here";
|
||||
//
|
||||
//
|
||||
// button1
|
||||
//
|
||||
//
|
||||
this.button1.Active1 = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(45)))));
|
||||
this.button1.Active2 = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(45)))));
|
||||
this.button1.BackColor = System.Drawing.Color.Transparent;
|
||||
this.button1.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
this.button1.Disabled1 = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(35)))), ((int)(((byte)(45)))));
|
||||
this.button1.Disabled2 = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(28)))), ((int)(((byte)(35)))));
|
||||
this.button1.DisabledStrokeColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(55)))), ((int)(((byte)(65)))));
|
||||
this.button1.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F);
|
||||
this.button1.ForeColor = System.Drawing.Color.White;
|
||||
this.button1.Inactive1 = System.Drawing.Color.FromArgb(((int)(((byte)(25)))), ((int)(((byte)(25)))), ((int)(((byte)(25)))));
|
||||
@@ -64,9 +66,9 @@
|
||||
this.button1.Text = "Create User.Json";
|
||||
this.button1.Transparency = false;
|
||||
this.button1.Click += new System.EventHandler(this.button1_Click);
|
||||
//
|
||||
//
|
||||
// UsernameForm
|
||||
//
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = global::AndroidSideloader.Properties.Settings.Default.BackColor;
|
||||
@@ -74,11 +76,9 @@
|
||||
this.Controls.Add(this.button1);
|
||||
this.Controls.Add(this.textBox1);
|
||||
this.ForeColor = System.Drawing.Color.White;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MaximumSize = new System.Drawing.Size(459, 139);
|
||||
this.MinimumSize = new System.Drawing.Size(459, 139);
|
||||
this.Name = "UsernameForm";
|
||||
this.ShowIcon = false;
|
||||
this.Text = "USER.JSON";
|
||||
this.Load += new System.EventHandler(this.usernameForm_Load);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace AndroidSideloader
|
||||
public UsernameForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Use same icon as the executable
|
||||
this.Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
}
|
||||
|
||||
private string defaultText;
|
||||
|
||||
6849
UsernameForm.resx
6849
UsernameForm.resx
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
105
Utilities/Zip.cs
105
Utilities/Zip.cs
@@ -3,10 +3,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace AndroidSideloader.Utilities
|
||||
@@ -19,6 +16,11 @@ namespace AndroidSideloader.Utilities
|
||||
internal class Zip
|
||||
{
|
||||
private static readonly SettingsManager settings = SettingsManager.Instance;
|
||||
|
||||
// Progress callback: (percent, eta)
|
||||
public static Action<float, TimeSpan?> ExtractionProgressCallback { get; set; }
|
||||
public static Action<string> ExtractionStatusCallback { get; set; }
|
||||
|
||||
public static void ExtractFile(string sourceArchive, string destination)
|
||||
{
|
||||
string args = $"x \"{sourceArchive}\" -y -o\"{destination}\" -bsp1";
|
||||
@@ -33,6 +35,7 @@ namespace AndroidSideloader.Utilities
|
||||
|
||||
private static string extractionError = null;
|
||||
private static bool errorMessageShown = false;
|
||||
|
||||
private static void DoExtract(string args)
|
||||
{
|
||||
if (!File.Exists(Path.Combine(Environment.CurrentDirectory, "7z.exe")) || !File.Exists(Path.Combine(Environment.CurrentDirectory, "7z.dll")))
|
||||
@@ -68,24 +71,98 @@ namespace AndroidSideloader.Utilities
|
||||
|
||||
_ = Logger.Log($"Extract: 7z {string.Join(" ", args.Split(' ').Where(a => !a.StartsWith("-p")))}");
|
||||
|
||||
// Throttle percent reports
|
||||
float lastReportedPercent = -1;
|
||||
|
||||
// ETA engine (percent units)
|
||||
var etaEstimator = new EtaEstimator(alpha: 0.10, reanchorThreshold: 0.20, minSampleSeconds: 0.10);
|
||||
|
||||
// Smooth progress (sub-percent) interpolation (because 7z -bsp1 is integer-only)
|
||||
System.Threading.Timer smoothTimer = null;
|
||||
int extractingFlag = 1; // 1 = extracting, 0 = stop
|
||||
float smoothLastTickPercent = 0f;
|
||||
DateTime smoothLastTickTime = DateTime.UtcNow;
|
||||
float smoothLastReported = -1f;
|
||||
const int SmoothIntervalMs = 80; // ~12.5 updates/sec
|
||||
const float SmoothReportDelta = 0.10f; // report only if change >= 0.10%
|
||||
|
||||
using (Process x = new Process())
|
||||
{
|
||||
x.StartInfo = pro;
|
||||
|
||||
if (MainForm.isInDownloadExtract && x != null)
|
||||
{
|
||||
// Smooth sub-percent UI, while keeping ETA ticking
|
||||
smoothTimer = new System.Threading.Timer(_ =>
|
||||
{
|
||||
if (System.Threading.Volatile.Read(ref extractingFlag) == 0) return;
|
||||
if (smoothLastTickPercent <= 0) return; // need at least one 7z tick
|
||||
|
||||
// Use current ETA to approximate seconds-per-percent
|
||||
TimeSpan? displayEta = etaEstimator.GetDisplayEta();
|
||||
if (!displayEta.HasValue) return; // Skip until ETA exists
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var elapsed = (now - smoothLastTickTime).TotalSeconds;
|
||||
|
||||
// Approx seconds-per-percent from remaining ETA / remaining percent
|
||||
double remainingPercent = Math.Max(1.0, 100.0 - smoothLastTickPercent);
|
||||
double spp = Math.Max(0.05, displayEta.Value.TotalSeconds / remainingPercent);
|
||||
|
||||
float candidate = smoothLastTickPercent + (float)(elapsed / spp);
|
||||
|
||||
// Clamp
|
||||
float floorTick = (float)Math.Floor(smoothLastTickPercent);
|
||||
float ceiling = Math.Min(99.99f, floorTick + 0.999f);
|
||||
|
||||
if (candidate > ceiling) candidate = ceiling;
|
||||
if (candidate < smoothLastTickPercent) candidate = smoothLastTickPercent;
|
||||
|
||||
if (smoothLastReported >= 0 && Math.Abs(candidate - smoothLastReported) < SmoothReportDelta) return;
|
||||
smoothLastReported = candidate;
|
||||
|
||||
try
|
||||
{
|
||||
MainForm mainForm = (MainForm)Application.OpenForms[0];
|
||||
if (mainForm != null && !mainForm.IsDisposed)
|
||||
{
|
||||
mainForm.BeginInvoke((Action)(() => mainForm.SetProgress(candidate)));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// ETA countdown ticks even if 7z percent is unchanged
|
||||
ExtractionProgressCallback?.Invoke(candidate, etaEstimator.GetDisplayEta());
|
||||
|
||||
}, null, SmoothIntervalMs, SmoothIntervalMs);
|
||||
|
||||
x.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
var match = Regex.Match(e.Data, @"(\d+)%");
|
||||
if (match.Success)
|
||||
var match = Regex.Match(e.Data, @"^\s*(\d+)%");
|
||||
if (match.Success && float.TryParse(match.Groups[1].Value, out float percent))
|
||||
{
|
||||
int progress = int.Parse(match.Groups[1].Value);
|
||||
MainForm mainForm = (MainForm)Application.OpenForms[0];
|
||||
if (mainForm != null)
|
||||
// Update ETA from integer percent
|
||||
if (percent <= 0.0f) etaEstimator.Reset();
|
||||
else if (percent < 100.0f) etaEstimator.Update(totalUnits: 100, doneUnits: (long)Math.Round(percent));
|
||||
|
||||
// Reset smoothing baseline on each integer tick
|
||||
smoothLastTickPercent = percent;
|
||||
smoothLastTickTime = DateTime.UtcNow;
|
||||
smoothLastReported = percent;
|
||||
|
||||
if (Math.Abs(percent - lastReportedPercent) >= 0.1f)
|
||||
{
|
||||
mainForm.Invoke((Action)(() => mainForm.SetProgress(progress)));
|
||||
lastReportedPercent = percent;
|
||||
|
||||
MainForm mainForm = (MainForm)Application.OpenForms[0];
|
||||
if (mainForm != null)
|
||||
{
|
||||
mainForm.Invoke((Action)(() => mainForm.SetProgress(percent)));
|
||||
}
|
||||
|
||||
ExtractionProgressCallback?.Invoke(percent, etaEstimator.GetDisplayEta());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +196,16 @@ namespace AndroidSideloader.Utilities
|
||||
x.BeginOutputReadLine();
|
||||
x.BeginErrorReadLine();
|
||||
x.WaitForExit();
|
||||
|
||||
// Stop smoother
|
||||
System.Threading.Interlocked.Exchange(ref extractingFlag, 0);
|
||||
smoothTimer?.Dispose();
|
||||
smoothTimer = null;
|
||||
|
||||
// Clear callbacks
|
||||
ExtractionProgressCallback?.Invoke(100, null);
|
||||
ExtractionStatusCallback?.Invoke("");
|
||||
|
||||
errorMessageShown = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(extractionError))
|
||||
|
||||
@@ -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