Compare commits

...

8 Commits

Author SHA1 Message Date
Fenopy
c48043a178 Merge pull request #271 from jp64k/RSL-3.0-2
Added efficient automatic Cloudflare DNS fallback with local proxy for RCLONE, fixed several error messages being opened behind mainwindow, refactored ShowError_QuotaExceeded() logic, fixed proxy settings parsing, fixed broken config preventing startup, fixed ampersands (&) not being rendered in selectedGameLabel and rookieStatusLabel
2025-12-15 06:15:31 -06:00
Fenopy
15f0c1ee72 chore: update proxy handling order
changed setRcloneProxy to default to user's proxy first.
2025-12-15 06:14:03 -06:00
jp64k
44df1666f4 Fixed ampersands (&) not being rendered in selectedGameLabel and rookieStatusLabel 2025-12-15 02:42:43 +01:00
jp64k
6cbfdbe52c Refactored file download logic to use DNS fallback for 7-zip and WebView2 downloads, fixed crash craused by corrupted user.config preventing startup
Refactored all file download logic to use DNS fallback and applied it to 7-zip and WebView2 runtime downloads. Moved WebView2 runtime download logic to GetDependencies and ensures it is downloaded at startup if missing. Added robust handling for corrupted user.config files in Program.cs, including auto-repair and fallback guidance.
2025-12-15 00:07:54 +01:00
jp64k
75d22ab504 Parse proxy settings only when proxy is enabled
Updated the applyButton_Click handler to parse and validate proxy address and port only if the proxy toggle is checked. This prevents unnecessary validation and error messages when the proxy is not enabled
2025-12-14 20:45:13 +01:00
jp64k
0c20841db3 Added efficient Cloudflare DNS fallback with local proxy for RCLONE
Introduced DnsHelper to detect system DNS failures and fall back to Cloudflare (1.1.1.1 / 1.0.0.1) DNS resolving, with caching and helpers for downloads. When fallback is required a lightweight local proxy is started and HTTP_PROXY/HTTPS_PROXY are set for spawned rclone processes so rclone uses the proxy’s DNS resolution; the proxy is cleaned up on exit. This finally resolves the very common ISP DNS blockage issues of users.
2025-12-14 20:25:48 +01:00
jp64k
b33251d98b Moved ShowError_QuotaExceeded() outside of try-catch, updated message, close application after showing the error
Moved quota exceeded check outside of try-catch block so it always runs the check. Updated the error message in ShowError_QuotaExceeded(). The application now exits after showing the error.
2025-12-14 18:45:21 +01:00
jp64k
3ef0652a85 Fixed several error messages being opened behind mainwindow
Updated all FlexibleMessageBox.Show invocations to include Program.form as the parent form. This ensures message boxes are properly parented to the main application window, putting them infront of the main window, instead of behind it.
2025-12-14 16:54:35 +01:00
10 changed files with 753 additions and 126 deletions

View File

@@ -239,6 +239,7 @@
<Compile Include="Sideloader\ProcessOutput.cs" />
<Compile Include="Sideloader\RCLONE.cs" />
<Compile Include="Sideloader\Utilities.cs" />
<Compile Include="Utilities\DnsHelper.cs" />
<Compile Include="Utilities\Logger.cs" />
<Compile Include="QuestForm.cs">
<SubType>Form</SubType>

2
MainForm.Designer.cs generated
View File

@@ -1266,6 +1266,7 @@ namespace AndroidSideloader
this.rookieStatusLabel.Size = new System.Drawing.Size(225, 17);
this.rookieStatusLabel.TabIndex = 0;
this.rookieStatusLabel.Text = "Status";
this.rookieStatusLabel.UseMnemonic = false;
//
// sidebarMediaPanel
//
@@ -1320,6 +1321,7 @@ namespace AndroidSideloader
this.selectedGameLabel.Size = new System.Drawing.Size(217, 20);
this.selectedGameLabel.TabIndex = 99;
this.selectedGameLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.selectedGameLabel.UseMnemonic = false;
//
// tableLayoutPanel1
//

View File

@@ -2989,19 +2989,6 @@ Additional Thanks & Resources
quotaTries++;
remotesList.Invoke((MethodInvoker)delegate
{
if (quotaTries > remotesList.Items.Count)
{
ShowError_QuotaExceeded();
if (System.Windows.Forms.Application.MessageLoop)
{
// Process.GetCurrentProcess().Kill();
isOffline = true;
success = false;
return;
}
}
if (remotesList.SelectedIndex + 1 == remotesList.Items.Count)
{
reset = true;
@@ -3026,20 +3013,35 @@ Additional Thanks & Resources
success = false;
}
// If we've tried all remotes and failed, show quota exceeded error
if (quotaTries > remotesList.Items.Count)
{
ShowError_QuotaExceeded();
if (Application.MessageLoop)
{
isOffline = true;
success = false;
return success;
}
}
return success;
}
private static void ShowError_QuotaExceeded()
{
string errorMessage =
$@"Unable to connect to Remote Server. Rookie is unable to connect to our Servers.
$@"Rookie cannot reach our servers.
First time launching Rookie? Please relaunch and try again.
If this is your first time launching Rookie, please relaunch and try again.
Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.gg/tBKMZy7QDA) for Troubleshooting steps!
";
If the problem persists, visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.gg/tBKMZy7QDA) for troubleshooting steps.";
_ = FlexibleMessageBox.Show(Program.form, errorMessage, "Unable to connect to Remote Server");
FlexibleMessageBox.Show(Program.form, errorMessage, "Unable to connect to remote server");
// Close application after showing the message
Application.Exit();
}
public async void cleanupActiveDownloadStatus()
@@ -3865,6 +3867,9 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// Cleanup DNS helper (stops proxy)
DnsHelper.Cleanup();
if (isinstalling)
{
DialogResult res1 = FlexibleMessageBox.Show(Program.form, "There are downloads and/or installations in progress,\nif you exit now you'll have to start the entire process over again.\nAre you sure you want to exit?", "Still downloading/installing.",
@@ -4649,6 +4654,7 @@ CTRL + F4 - Instantly relaunch Rookie Sideloader");
if (webView21.CoreWebView2 != null) return;
// Check if WebView2 runtime DLLs are present
// (downloadFiles() should have already downloaded them, but check anyway)
string runtimesPath = Path.Combine(Environment.CurrentDirectory, "runtimes");
string webView2LoaderArm64 = Path.Combine(runtimesPath, "win-arm64", "native", "WebView2Loader.dll");
string webView2LoaderX86 = Path.Combine(runtimesPath, "win-x86", "native", "WebView2Loader.dll");
@@ -4658,32 +4664,11 @@ CTRL + F4 - Instantly relaunch Rookie Sideloader");
if (!runtimeExists)
{
try
{
changeTitle("Downloading Runtime...");
string archivePath = Path.Combine(Environment.CurrentDirectory, "runtimes.7z");
using (var client = new WebClient())
{
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
await Task.Run(() => client.DownloadFile("https://vrpirates.wiki/downloads/runtimes.7z", archivePath));
}
changeTitle("Extracting Runtime...");
await Task.Run(() => Utilities.Zip.ExtractFile(archivePath, Environment.CurrentDirectory));
File.Delete(archivePath);
}
catch (Exception ex)
{
Logger.Log($"Failed to download WebView2 runtime: {ex.Message}", LogLevel.ERROR);
_ = FlexibleMessageBox.Show(Program.form,
$"Unable to download WebView2 runtime: {ex.Message}\n\nTrailer playback will be disabled.",
"WebView2 Download Failed");
enviromentCreated = true;
webView21.Hide();
return;
}
// Runtime wasn't downloaded during startup - disable trailers
Logger.Log("WebView2 runtime not found, disabling trailer playback", LogLevel.WARNING);
enviromentCreated = true;
webView21.Hide();
return;
}
try
@@ -4708,14 +4693,6 @@ CTRL + F4 - Instantly relaunch Rookie Sideloader");
}
catch (Exception /* ex */)
{
/*
Logger.Log($"Failed to initialize WebView2: {ex.Message}", LogLevel.ERROR);
_ = FlexibleMessageBox.Show(Program.form,
$"WebView2 Runtime is not installed on this system.\n\n" +
"Please download from: https://go.microsoft.com/fwlink/p/?LinkId=2124703\n\n" +
"Trailer playback will be disabled.",
"WebView2 Runtime Required");
*/
enviromentCreated = true;
webView21.Hide();
}

View File

@@ -8,7 +8,7 @@ namespace AndroidSideloader
{
internal static class Program
{
private static readonly SettingsManager settings = SettingsManager.Instance;
private static SettingsManager settings;
/// <summary>
/// The main entry point for the application.
/// </summary>
@@ -16,7 +16,54 @@ namespace AndroidSideloader
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
private static void Main()
{
// Handle corrupted user.config files
bool configFixed = false;
Exception configException = null;
try
{
// Force settings initialization to trigger any config errors early
var test = AndroidSideloader.Properties.Settings.Default.FontStyle;
}
catch (Exception ex)
{
configException = ex;
// Delete the corrupted config file and retry
try
{
string configPath = GetUserConfigPath();
if (!string.IsNullOrEmpty(configPath) && File.Exists(configPath))
{
File.Delete(configPath);
configFixed = true;
}
}
catch
{
// If we can't delete it, try to continue anyway
}
}
if (configFixed)
{
// Restart the application after fixing config
Application.Restart();
return;
}
if (configException != null)
{
MessageBox.Show(
"Settings file is corrupted and could not be repaired automatically.\n\n" +
"Please delete this folder and restart the application:\n" +
Path.GetDirectoryName(GetUserConfigPath()),
"Configuration Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
settings = SettingsManager.Instance;
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler);
Application.EnableVisualStyles();
@@ -25,6 +72,23 @@ namespace AndroidSideloader
Application.Run(form);
//form.Show();
}
private static string GetUserConfigPath()
{
try
{
string appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string companyName = "Rookie.AndroidSideloader";
string exeName = "AndroidSideloader.exe_Url_dkp0unsd4fjaabhwwafgfxvvbrerf10b";
string version = "2.0.0.0";
return Path.Combine(appData, companyName, exeName, version, "user.config");
}
catch
{
return null;
}
}
public static MainForm form;
private static void CrashHandler(object sender, UnhandledExceptionEventArgs args)
@@ -37,10 +101,10 @@ namespace AndroidSideloader
string date_time = DateTime.Now.ToString("dddd, MMMM dd @ hh:mmtt (UTC)");
File.WriteAllText(Sideloader.CrashLogPath, $"Date/Time of crash: {date_time}\nMessage: {e.Message}\nInner Message: {innerExceptionMessage}\nData: {e.Data}\nSource: {e.Source}\nTargetSite: {e.TargetSite}\nStack Trace: \n{e.StackTrace}\n\n\nDebuglog: \n\n\n");
// If a debuglog exists we append it to the crashlog.
if (File.Exists(settings.CurrentLogPath))
if (settings != null && File.Exists(settings.CurrentLogPath))
{
File.AppendAllText(Sideloader.CrashLogPath, File.ReadAllText($"{settings.CurrentLogPath}"));
}
}
}
}
}

View File

@@ -357,27 +357,35 @@ namespace AndroidSideloader
private static void setRcloneProxy()
{
// Use the user's proxy settings if set, otherwise fallback to DNS fallback proxy if active
string proxyUrl = DnsHelper.ProxyUrl;
if (settings.useProxy)
{
if (!rclone.StartInfo.EnvironmentVariables.ContainsKey("HTTP_PROXY")) {
rclone.StartInfo.EnvironmentVariables.Add("HTTP_PROXY", $"http://{settings.ProxyAddress}:{settings.ProxyPort}");
}
if (!rclone.StartInfo.EnvironmentVariables.ContainsKey("HTTPS_PROXY"))
{
rclone.StartInfo.EnvironmentVariables.Add("HTTPS_PROXY", $"http://{settings.ProxyAddress}:{settings.ProxyPort}");
}
// Use user's configured proxy
var url = $"http://{settings.ProxyAddress}:{settings.ProxyPort}";
rclone.StartInfo.EnvironmentVariables["HTTP_PROXY"] = url;
rclone.StartInfo.EnvironmentVariables["HTTPS_PROXY"] = url;
rclone.StartInfo.EnvironmentVariables["http_proxy"] = url;
rclone.StartInfo.EnvironmentVariables["https_proxy"] = url;
}
else if (!string.IsNullOrEmpty(proxyUrl))
{
// Use our DNS-resolving proxy
rclone.StartInfo.EnvironmentVariables["HTTP_PROXY"] = proxyUrl;
rclone.StartInfo.EnvironmentVariables["HTTPS_PROXY"] = proxyUrl;
rclone.StartInfo.EnvironmentVariables["http_proxy"] = proxyUrl;
rclone.StartInfo.EnvironmentVariables["https_proxy"] = proxyUrl;
}
else
{
if (rclone.StartInfo.EnvironmentVariables.ContainsKey("HTTP_PROXY"))
{
rclone.StartInfo.EnvironmentVariables.Remove("HTTP_PROXY");
}
if (rclone.StartInfo.EnvironmentVariables.ContainsKey("HTTPS_PROXY"))
{
rclone.StartInfo.EnvironmentVariables.Remove("HTTPS_PROXY");
}
// No proxy
rclone.StartInfo.EnvironmentVariables.Remove("HTTP_PROXY");
rclone.StartInfo.EnvironmentVariables.Remove("HTTPS_PROXY");
rclone.StartInfo.EnvironmentVariables.Remove("http_proxy");
rclone.StartInfo.EnvironmentVariables.Remove("https_proxy");
}
}
}
}

View File

@@ -139,7 +139,7 @@ namespace AndroidSideloader
private void applyButton_Click(object sender, EventArgs e)
{
//parsing bandwidth value
// Parse bandwidth value
var bandwidthInput = bandwidthLimitTextBox.Text;
Regex regex = new Regex(@"^\d+(\.\d+)?$");
@@ -153,38 +153,42 @@ namespace AndroidSideloader
return;
}
//parsing proxy address
var proxyAddressInput = proxyAddressTextBox.Text;
// Parse proxy values if proxy is enabled
if (toggleProxy.Checked)
{
// Parse proxy address
var proxyAddressInput = proxyAddressTextBox.Text;
if (proxyAddressInput.StartsWith("http://"))
{
proxyAddressInput = proxyAddressInput.Substring("http://".Length);
}
else if (proxyAddressInput.StartsWith("https://"))
{
proxyAddressInput = proxyAddressInput.Substring("https://".Length);
}
if (proxyAddressInput.StartsWith("http://"))
{
proxyAddressInput = proxyAddressInput.Substring("http://".Length);
}
else if (proxyAddressInput.StartsWith("https://"))
{
proxyAddressInput = proxyAddressInput.Substring("https://".Length);
}
if (proxyAddressInput.Equals("localhost", StringComparison.OrdinalIgnoreCase) ||
IPAddress.TryParse(proxyAddressInput, out _))
{
_settings.ProxyAddress = proxyAddressInput;
}
else
{
MessageBox.Show("Please enter a valid address for the proxy.");
}
if (proxyAddressInput.Equals("localhost", StringComparison.OrdinalIgnoreCase) ||
IPAddress.TryParse(proxyAddressInput, out _))
{
_settings.ProxyAddress = proxyAddressInput;
}
else
{
MessageBox.Show("Please enter a valid address for the proxy.");
}
//parsing proxy port
var proxyPortInput = proxyPortTextBox.Text;
// Parse proxy port
var proxyPortInput = proxyPortTextBox.Text;
if (ushort.TryParse(proxyPortInput, out _))
{
_settings.ProxyPort = proxyPortInput;
}
else
{
MessageBox.Show("Please enter a valid port for the proxy.");
if (ushort.TryParse(proxyPortInput, out _))
{
_settings.ProxyPort = proxyPortInput;
}
else
{
MessageBox.Show("Please enter a valid port for the proxy.");
}
}
SaveAllSettings();

View File

@@ -1,4 +1,5 @@
using JR.Utils.GUI.Forms;
using AndroidSideloader.Utilities;
using JR.Utils.GUI.Forms;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -30,7 +31,7 @@ namespace AndroidSideloader
string resultString;
// Try fetching raw JSON data from the provided link
HttpWebRequest getUrl = (HttpWebRequest)WebRequest.Create(configUrl);
HttpWebRequest getUrl = DnsHelper.CreateWebRequest(configUrl);
using (StreamReader responseReader = new StreamReader(getUrl.GetResponse().GetResponseStream()))
{
resultString = responseReader.ReadToEnd();
@@ -44,7 +45,7 @@ namespace AndroidSideloader
_ = Logger.Log($"Failed to update public config from main: {mainException.Message}, trying fallback.", LogLevel.ERROR);
try
{
HttpWebRequest getUrl = (HttpWebRequest)WebRequest.Create(fallbackUrl);
HttpWebRequest getUrl = DnsHelper.CreateWebRequest(fallbackUrl);
using (StreamReader responseReader = new StreamReader(getUrl.GetResponse().GetResponseStream()))
{
string resultString = responseReader.ReadToEnd();
@@ -60,9 +61,12 @@ namespace AndroidSideloader
}
}
// Download required dependencies.
// Download required dependencies
public static void downloadFiles()
{
// Initialize DNS helper early to detect and configure fallback if needed
DnsHelper.Initialize();
WebClient client = new WebClient();
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
@@ -73,7 +77,7 @@ namespace AndroidSideloader
{
currentAccessedWebsite = "github";
_ = Logger.Log($"Missing 'Sideloader Launcher.exe'. Attempting to download from {currentAccessedWebsite}");
client.DownloadFile("https://github.com/VRPirates/rookie/raw/master/Sideloader%20Launcher.exe", "Sideloader Launcher.exe");
DownloadFileWithDnsFallback(client, "https://github.com/VRPirates/rookie/raw/master/Sideloader%20Launcher.exe", "Sideloader Launcher.exe");
_ = Logger.Log($"'Sideloader Launcher.exe' download successful");
}
@@ -81,7 +85,7 @@ namespace AndroidSideloader
{
currentAccessedWebsite = "github";
_ = Logger.Log($"Missing 'Rookie Offline.cmd'. Attempting to download from {currentAccessedWebsite}");
client.DownloadFile("https://github.com/VRPirates/rookie/raw/master/Rookie%20Offline.cmd", "Rookie Offline.cmd");
DownloadFileWithDnsFallback(client, "https://github.com/VRPirates/rookie/raw/master/Rookie%20Offline.cmd", "Rookie Offline.cmd");
_ = Logger.Log($"'Rookie Offline.cmd' download successful");
}
@@ -89,7 +93,7 @@ namespace AndroidSideloader
{
currentAccessedWebsite = "github";
_ = Logger.Log($"Missing 'CleanupInstall.cmd'. Attempting to download from {currentAccessedWebsite}");
client.DownloadFile("https://github.com/VRPirates/rookie/raw/master/CleanupInstall.cmd", "CleanupInstall.cmd");
DownloadFileWithDnsFallback(client, "https://github.com/VRPirates/rookie/raw/master/CleanupInstall.cmd", "CleanupInstall.cmd");
_ = Logger.Log($"'CleanupInstall.cmd' download successful");
}
@@ -97,13 +101,13 @@ namespace AndroidSideloader
{
currentAccessedWebsite = "github";
_ = Logger.Log($"Missing 'AddDefenderExceptions.ps1'. Attempting to download from {currentAccessedWebsite}");
client.DownloadFile("https://github.com/VRPirates/rookie/raw/master/AddDefenderExceptions.ps1", "AddDefenderExceptions.ps1");
DownloadFileWithDnsFallback(client, "https://github.com/VRPirates/rookie/raw/master/AddDefenderExceptions.ps1", "AddDefenderExceptions.ps1");
_ = Logger.Log($"'AddDefenderExceptions.ps1' download successful");
}
}
catch (Exception ex)
{
_ = FlexibleMessageBox.Show($"You are unable to access raw.githubusercontent.com with the Exception:\n{ex.Message}\n\nSome files may be missing (Offline/Cleanup Script, Launcher)");
_ = FlexibleMessageBox.Show(Program.form, $"You are unable to access raw.githubusercontent.com with the Exception:\n{ex.Message}\n\nSome files may be missing (Offline/Cleanup Script, Launcher)");
}
string adbPath = Path.Combine(Environment.CurrentDirectory, "platform-tools", "adb.exe");
@@ -120,7 +124,7 @@ namespace AndroidSideloader
currentAccessedWebsite = "github";
_ = Logger.Log($"Missing adb within {platformToolsDir}. Attempting to download from {currentAccessedWebsite}");
client.DownloadFile("https://github.com/VRPirates/rookie/raw/master/dependencies.7z", "dependencies.7z");
DownloadFileWithDnsFallback(client, "https://github.com/VRPirates/rookie/raw/master/dependencies.7z", "dependencies.7z");
Utilities.Zip.ExtractFile(Path.Combine(Environment.CurrentDirectory, "dependencies.7z"), platformToolsDir);
File.Delete("dependencies.7z");
_ = Logger.Log($"adb download successful");
@@ -128,8 +132,8 @@ namespace AndroidSideloader
}
catch (Exception ex)
{
_ = FlexibleMessageBox.Show($"You are unable to access raw.githubusercontent.com page with the Exception:\n{ex.Message}\n\nSome files may be missing (ADB)");
_ = FlexibleMessageBox.Show("ADB was unable to be downloaded\nRookie will now close.");
_ = FlexibleMessageBox.Show(Program.form, $"You are unable to access raw.githubusercontent.com page with the Exception:\n{ex.Message}\n\nSome files may be missing (ADB)");
_ = FlexibleMessageBox.Show(Program.form, "ADB was unable to be downloaded\nRookie will now close.");
Application.Exit();
}
@@ -137,16 +141,88 @@ namespace AndroidSideloader
bool rcloneSuccess = false;
rcloneSuccess = downloadRclone(wantedRcloneVersion, false);
if (!rcloneSuccess) {
if (!rcloneSuccess)
{
rcloneSuccess = downloadRclone(wantedRcloneVersion, true);
}
if (!rcloneSuccess) {
if (!rcloneSuccess)
{
_ = Logger.Log($"Unable to download rclone", LogLevel.ERROR);
_ = FlexibleMessageBox.Show("Rclone was unable to be downloaded\nRookie will now close, please use Offline Mode for manual sideloading if needed");
_ = FlexibleMessageBox.Show(Program.form, "Rclone was unable to be downloaded\nRookie will now close, please use Offline Mode for manual sideloading if needed");
Application.Exit();
}
// Download WebView2 runtime if needed
downloadWebView2Runtime();
}
// Downloads a file using the DNS fallback proxy if active
public static void DownloadFileWithDnsFallback(WebClient client, string url, string localPath)
{
try
{
// Use DNS fallback proxy if active
if (DnsHelper.UseFallbackDns && !string.IsNullOrEmpty(DnsHelper.ProxyUrl))
{
client.Proxy = new WebProxy(DnsHelper.ProxyUrl);
}
client.DownloadFile(url, localPath);
}
catch (Exception ex)
{
_ = Logger.Log($"Download failed for {url}: {ex.Message}", LogLevel.ERROR);
throw;
}
finally
{
// Reset proxy to avoid affecting other operations
client.Proxy = null;
}
}
// Overload that creates its own WebClient for convenience
public static void DownloadFileWithDnsFallback(string url, string localPath)
{
using (var client = new WebClient())
{
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
DownloadFileWithDnsFallback(client, url, localPath);
}
}
// Downloads WebView2 runtime if not present
private static void downloadWebView2Runtime()
{
string runtimesPath = Path.Combine(Environment.CurrentDirectory, "runtimes");
string webView2LoaderArm64 = Path.Combine(runtimesPath, "win-arm64", "native", "WebView2Loader.dll");
string webView2LoaderX86 = Path.Combine(runtimesPath, "win-x86", "native", "WebView2Loader.dll");
string webView2LoaderX64 = Path.Combine(runtimesPath, "win-x64", "native", "WebView2Loader.dll");
bool runtimeExists = File.Exists(webView2LoaderX86) || File.Exists(webView2LoaderX64) || File.Exists(webView2LoaderArm64);
if (!runtimeExists)
{
try
{
_ = Logger.Log("Missing WebView2 runtime. Attempting to download...");
string archivePath = Path.Combine(Environment.CurrentDirectory, "runtimes.7z");
DownloadFileWithDnsFallback("https://vrpirates.wiki/downloads/runtimes.7z", archivePath);
_ = Logger.Log("Extracting WebView2 runtime...");
Utilities.Zip.ExtractFile(archivePath, Environment.CurrentDirectory);
File.Delete(archivePath);
_ = Logger.Log("WebView2 runtime download successful");
}
catch (Exception ex)
{
_ = Logger.Log($"Failed to download WebView2 runtime: {ex.Message}", LogLevel.ERROR);
// Don't show message box here - let CreateEnvironment handle the UI feedback
}
}
}
public static bool downloadRclone(string wantedRcloneVersion, bool useFallback = false)
{
@@ -175,12 +251,15 @@ namespace AndroidSideloader
_ = Logger.Log($"RCLONE Version does not match ({currentRcloneVersion})! Downloading required version ({wantedRcloneVersion})");
}
}
} else {
}
else
{
updateRclone = true;
_ = Logger.Log($"RCLONE exe does not exist, attempting to download");
}
if (!Directory.Exists(dirRclone)) {
if (!Directory.Exists(dirRclone))
{
updateRclone = true;
_ = Logger.Log($"Missing RCLONE Folder, attempting to download");
@@ -203,14 +282,15 @@ namespace AndroidSideloader
string architecture = Environment.Is64BitOperatingSystem ? "amd64" : "386";
string url = $"https://downloads.rclone.org/v{wantedRcloneVersion}/rclone-v{wantedRcloneVersion}-windows-{architecture}.zip";
if (useFallback == true) {
if (useFallback == true)
{
_ = Logger.Log($"Using git fallback for rclone download");
url = $"https://raw.githubusercontent.com/VRPirates/rookie/master/dep/rclone-v{wantedRcloneVersion}-windows-{architecture}.zip";
}
_ = Logger.Log($"Downloading rclone from {url}");
_ = Logger.Log("Begin download rclone");
client.DownloadFile(url, "rclone.zip");
DownloadFileWithDnsFallback(client, url, "rclone.zip");
_ = Logger.Log("Complete download rclone");
_ = Logger.Log($"Extract {Environment.CurrentDirectory}\\rclone.zip");
@@ -250,4 +330,4 @@ namespace AndroidSideloader
}
}
}
}
}

View File

@@ -215,7 +215,8 @@ namespace AndroidSideloader
{
string configUrl = "https://vrpirates.wiki/downloads/vrp.upload.config";
var getUrl = (HttpWebRequest)WebRequest.Create(configUrl);
// Use DnsHelper for fallback DNS support
var getUrl = DnsHelper.CreateWebRequest(configUrl);
using (var response = getUrl.GetResponse())
using (var stream = response.GetResponseStream())
using (var responseReader = new StreamReader(stream))

491
Utilities/DnsHelper.cs Normal file
View File

@@ -0,0 +1,491 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AndroidSideloader.Utilities
{
// Provides DNS fallback functionality using Cloudflare DNS (1.1.1.1, 1.0.0.1) if system DNS fails to resolve critical hostnames
// Also provides a proxy for rclone that handles DNS resolution
public static class DnsHelper
{
private static readonly string[] FallbackDnsServers = { "1.1.1.1", "1.0.0.1" };
private static readonly string[] CriticalHostnames =
{
"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 _proxyRunning;
public static bool UseFallbackDns
{
get { if (!_initialized) Initialize(); return _useFallbackDns; }
}
// 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;
public static void Initialize()
{
lock (_lock)
{
if (_initialized) return;
Logger.Log("Testing DNS resolution for critical hostnames...");
if (!TestSystemDns())
{
Logger.Log("System DNS failed. Testing Cloudflare DNS fallback...", LogLevel.WARNING);
if (TestFallbackDns())
{
_useFallbackDns = true;
Logger.Log("Using Cloudflare DNS fallback.", LogLevel.INFO);
PreResolveHostnames();
ServicePointManager.DnsRefreshTimeout = 0;
// Start local proxy for rclone
StartProxy();
}
else
{
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();
}
private static void PreResolveHostnames()
{
foreach (string hostname in CriticalHostnames)
{
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);
}
}
}
private static bool TestSystemDns()
{
foreach (string hostname in CriticalHostnames)
{
try
{
var addresses = Dns.GetHostAddresses(hostname);
if (addresses == null || addresses.Length == 0) return false;
}
catch { return false; }
}
return true;
}
private static bool TestFallbackDns()
{
foreach (string dnsServer in FallbackDnsServers)
{
try
{
var addresses = ResolveWithDns(CriticalHostnames[0], dnsServer);
if (addresses != null && addresses.Count > 0) return true;
}
catch { }
}
return false;
}
private static IPAddress ResolveWithFallbackDns(string hostname)
{
foreach (string dnsServer in FallbackDnsServers)
{
try
{
var addresses = ResolveWithDns(hostname, dnsServer);
if (addresses != null && addresses.Count > 0)
return addresses[0];
}
catch { }
}
return null;
}
private static List<IPAddress> ResolveWithDns(string hostname, string dnsServer, int timeoutMs = 5000)
{
byte[] query = BuildDnsQuery(hostname);
using (var udp = new UdpClient())
{
udp.Client.ReceiveTimeout = timeoutMs;
udp.Client.SendTimeout = timeoutMs;
udp.Send(query, query.Length, new IPEndPoint(IPAddress.Parse(dnsServer), 53));
IPEndPoint remoteEp = null;
byte[] response = udp.Receive(ref remoteEp);
return ParseDnsResponse(response);
}
}
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('.'))
{
writer.Write((byte)label.Length);
writer.Write(Encoding.ASCII.GetBytes(label));
}
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++; }
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;
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] }));
pos += rdLength;
}
return addresses;
}
#region Local HTTP CONNECT Proxy for rclone
private static void StartProxy()
{
try
{
// Find an available port
_proxyListener = new TcpListener(IPAddress.Loopback, 0);
_proxyListener.Start();
_proxyPort = ((IPEndPoint)_proxyListener.LocalEndpoint).Port;
_proxyCts = new CancellationTokenSource();
_proxyRunning = true;
Logger.Log($"Started DNS proxy on port {_proxyPort}");
// Accept connections in background
Task.Run(() => ProxyAcceptLoop(_proxyCts.Token));
}
catch (Exception ex)
{
Logger.Log($"Failed to start DNS proxy: {ex.Message}", LogLevel.WARNING);
_proxyRunning = false;
}
}
private static void StopProxy()
{
_proxyRunning = false;
_proxyCts?.Cancel();
try { _proxyListener?.Stop(); } catch { }
}
private static async Task ProxyAcceptLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested && _proxyRunning)
{
try
{
var client = await _proxyListener.AcceptTcpClientAsync();
_ = Task.Run(() => HandleProxyClient(client, ct));
}
catch (ObjectDisposedException) { break; }
catch (Exception ex)
{
if (!ct.IsCancellationRequested)
Logger.Log($"Proxy accept error: {ex.Message}", LogLevel.WARNING);
}
}
}
private static async Task HandleProxyClient(TcpClient client, CancellationToken ct)
{
try
{
using (client)
using (var stream = client.GetStream())
{
client.ReceiveTimeout = 30000;
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;
string request = Encoding.ASCII.GetString(buffer, 0, bytesRead);
string[] lines = request.Split(new[] { "\r\n" }, StringSplitOptions.None);
if (lines.Length == 0) return;
string[] requestLine = lines[0].Split(' ');
if (requestLine.Length < 2) return;
string method = requestLine[0];
string target = requestLine[1];
if (method == "CONNECT")
{
// HTTPS proxy - tunnel mode
await HandleConnectRequest(stream, target, ct);
}
else
{
// HTTP proxy - forward mode
await HandleHttpRequest(stream, request, target, ct);
}
}
}
catch (Exception ex)
{
if (!ct.IsCancellationRequested)
Logger.Log($"Proxy client error: {ex.Message}", LogLevel.WARNING);
}
}
private static async Task HandleConnectRequest(NetworkStream clientStream, string target, CancellationToken ct)
{
// Parse host:port
string[] parts = target.Split(':');
string host = parts[0];
int port = parts.Length > 1 ? int.Parse(parts[1]) : 443;
// Resolve hostname using our DNS
IPAddress ip = ResolveAnyHostname(host);
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);
return;
}
try
{
// Connect to target
using (var targetClient = new TcpClient())
{
await targetClient.ConnectAsync(ip, port);
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);
// Tunnel data bidirectionally
var clientToTarget = RelayData(clientStream, targetStream, ct);
var targetToClient = RelayData(targetStream, clientStream, ct);
await Task.WhenAny(clientToTarget, targetToClient);
}
}
}
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 { }
}
}
private static async Task HandleHttpRequest(NetworkStream clientStream, string request, string url, CancellationToken ct)
{
try
{
var uri = new Uri(url);
IPAddress ip = ResolveAnyHostname(uri.Host);
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);
return;
}
int port = uri.Port > 0 ? uri.Port : 80;
using (var targetClient = new TcpClient())
{
await targetClient.ConnectAsync(ip, port);
using (var targetStream = targetClient.GetStream())
{
// Modify request to use relative path
string modifiedRequest = request.Replace(url, uri.PathAndQuery);
byte[] requestBytes = Encoding.ASCII.GetBytes(modifiedRequest);
await targetStream.WriteAsync(requestBytes, 0, requestBytes.Length, ct);
// Relay response
await RelayData(targetStream, clientStream, ct);
}
}
}
catch (Exception ex)
{
Logger.Log($"HTTP proxy error: {ex.Message}", LogLevel.WARNING);
}
}
private static async Task RelayData(NetworkStream from, NetworkStream to, CancellationToken ct)
{
byte[] buffer = new byte[8192];
try
{
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);
}
}
}

View File

@@ -38,17 +38,17 @@ namespace AndroidSideloader.Utilities
if (!File.Exists(Path.Combine(Environment.CurrentDirectory, "7z.exe")) || !File.Exists(Path.Combine(Environment.CurrentDirectory, "7z.dll")))
{
_ = Logger.Log("Begin download 7-zip");
WebClient client = new WebClient();
string architecture = Environment.Is64BitOperatingSystem ? "64" : "";
try
{
client.DownloadFile($"https://github.com/VRPirates/rookie/raw/master/7z{architecture}.exe", $"7z.exe");
client.DownloadFile($"https://github.com/VRPirates/rookie/raw/master/7z{architecture}.dll", $"7z.dll");
// Use DNS fallback download method from GetDependencies
GetDependencies.DownloadFileWithDnsFallback($"https://github.com/VRPirates/rookie/raw/master/7z{architecture}.exe", "7z.exe");
GetDependencies.DownloadFileWithDnsFallback($"https://github.com/VRPirates/rookie/raw/master/7z{architecture}.dll", "7z.dll");
}
catch (Exception ex)
{
_ = FlexibleMessageBox.Show($"You are unable to access the GitHub page with the Exception: {ex.Message}\nSome files may be missing (7z)");
_ = FlexibleMessageBox.Show("7z was unable to be downloaded\nRookie will now close");
_ = FlexibleMessageBox.Show(Program.form, $"You are unable to access the GitHub page with the Exception: {ex.Message}\nSome files may be missing (7z)");
_ = FlexibleMessageBox.Show(Program.form, "7z was unable to be downloaded\nRookie will now close");
Application.Exit();
}
_ = Logger.Log("Complete download 7-zip");
@@ -127,7 +127,6 @@ namespace AndroidSideloader.Utilities
extractionError = null; // Reset the error message
throw new ExtractionException(errorMessage);
}
}
}
}