Skip to content

Commit a7287a6

Browse files
committed
Checks for ADB version when using ADB on path + use existing ADB daemon if within version range
1 parent 6ceae2c commit a7287a6

File tree

2 files changed

+151
-10
lines changed

2 files changed

+151
-10
lines changed

QuestPatcher.Core/AndroidDebugBridge.cs

Lines changed: 130 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Text;
99
using System.Threading.Tasks;
1010
using Serilog;
11+
using Version = SemanticVersioning.Version;
1112

1213
namespace QuestPatcher.Core
1314
{
@@ -60,6 +61,11 @@ public class AndroidDebugBridge
6061
/// </summary>
6162
private const int CommandLengthLimit = 1024;
6263

64+
/// <summary>
65+
/// The minimum ADB version required by QuestPatcher.
66+
/// </summary>
67+
private static readonly Version MinAdbVersion = new Version(1, 0, 41);
68+
6369
public event EventHandler? StoppedLogging;
6470

6571
private readonly ExternalFilesDownloader _filesDownloader;
@@ -76,22 +82,138 @@ public AndroidDebugBridge(ExternalFilesDownloader filesDownloader, Func<Disconne
7682
}
7783

7884
/// <summary>
79-
/// Checks if ADB is on PATH, and downloads it if not
85+
/// Checks if a valid ADB installation is found on PATH or in an installation of SideQuest.
86+
/// Using an ADB installation from SideQuest helps avoid the issue where QuestPatcher and SideQuest
87+
/// keep trying to kill each other's ADB server, resulting in neither working properly.
88+
/// ADB executables for daemons already running will also be prioritised.
8089
/// </summary>
8190
public async Task PrepareAdbPath()
8291
{
92+
// Use existing ADB daemon if there is one of the correct version
93+
if (await FindExistingAdbServer())
94+
{
95+
return;
96+
}
97+
98+
// Next check PATH
99+
Log.Debug("Checking installation on PATH");
100+
if (await SetAdbPathIfValid(_adbExecutableName))
101+
{
102+
Log.Information("Using ADB installation on PATH");
103+
return;
104+
}
105+
106+
// Otherwise, download ADB
107+
string downloadedAdb = await _filesDownloader.GetFileLocation(ExternalFileType.PlatformTools);
108+
if (!await SetAdbPathIfValid(downloadedAdb))
109+
{
110+
// Redownloading ADB - existing installation was not valid
111+
Log.Information("Existing downloaded ADB was out of date or corrupted - fetching again");
112+
await ProcessUtil.InvokeAndCaptureOutput(downloadedAdb, "kill-server"); // Kill server first, otherwise directory will be in use, so can't be deleted.
113+
_adbPath = await _filesDownloader.GetFileLocation(ExternalFileType.PlatformTools, true);
114+
}
115+
else
116+
{
117+
Log.Information("Using downloaded ADB");
118+
}
119+
}
120+
121+
/// <summary>
122+
/// Checks if the ADB executable at the given path exists and is up-to-date.
123+
/// If it is, then it will be set as the ADB path for the instance.
124+
/// </summary>
125+
/// <param name="adbExecutablePath">The relative or absolute path of the ADB executable.</param>
126+
/// <returns>True if and only if the ADB installation is present and up-to-date</returns>
127+
private async Task<bool> SetAdbPathIfValid(string adbExecutablePath)
128+
{
129+
const string VersionPrefix = "Android Debug Bridge version";
130+
83131
try
84132
{
85-
await ProcessUtil.InvokeAndCaptureOutput(_adbExecutableName, "-version");
86-
// If the ADB EXE is already on PATH, we can just use that
87-
_adbPath = _adbExecutableName;
88-
Log.Information("Located ADB install on PATH");
133+
Log.Verbose("Checking if ADB at {AdbPath} is present and up-to-date", adbExecutablePath);
134+
string output = (await ProcessUtil.InvokeAndCaptureOutput(adbExecutablePath, "version")).AllOutput;
135+
Log.Debug("Output from checking ADB version: {VerisonOutput}", output);
136+
137+
int prefixPos = output.IndexOf(VersionPrefix);
138+
if (prefixPos == -1)
139+
{
140+
Log.Verbose("No version code could be found in the output. ADB executable is NOT valid");
141+
return false;
142+
}
143+
144+
int versionPos = prefixPos + VersionPrefix.Length;
145+
int nextNewline = output.IndexOf('\n', versionPos);
146+
147+
string version;
148+
if (nextNewline == -1)
149+
{
150+
version = output.Substring(versionPos).Trim();
151+
}
152+
else
153+
{
154+
int versionLen = nextNewline - versionPos;
155+
version = output.Substring(versionPos, versionLen).Trim();
156+
}
157+
158+
Log.Debug($"Parsed ADB version as {version}");
159+
if (Version.TryParse(version, out var semver))
160+
{
161+
if (semver >= MinAdbVersion)
162+
{
163+
_adbPath = adbExecutablePath;
164+
return true;
165+
}
166+
}
167+
else
168+
{
169+
Log.Debug("ADB version was not valid semver, assuming out of date");
170+
}
171+
172+
return false;
173+
}
174+
catch (Win32Exception)
175+
{
176+
return false; // Executable not present
89177
}
90-
catch (Win32Exception) // Thrown if the file we attempted to execute does not exist (on mac & linux as well, despite saying Win32)
178+
}
179+
180+
private async Task<bool> FindExistingAdbServer()
181+
{
182+
Log.Debug("Checking for existing daemon");
183+
foreach (string adbPath in FindRunningAdbPath())
91184
{
92-
// Otherwise, we download the tool and make it executable (only necessary on mac & linux)
93-
_adbPath = await _filesDownloader.GetFileLocation(ExternalFileType.PlatformTools); // Download ADB if it hasn't been already
185+
Log.Debug("Found existing ADB daemon. Checking if it's valid for us to use");
186+
if (await SetAdbPathIfValid(adbPath))
187+
{
188+
Log.Information("Using ADB from existing daemon at path {AdbPath}", adbPath);
189+
return true;
190+
}
94191
}
192+
193+
return false;
194+
}
195+
196+
/// <summary>
197+
/// Finds the full path to any ADB server currently running.
198+
/// </summary>
199+
/// <returns>A list of the full paths to all running ADB servers.</returns>
200+
private IEnumerable<string> FindRunningAdbPath()
201+
{
202+
return Process.GetProcessesByName("adb") // No .exe, process name is without the extension
203+
.Select(process =>
204+
{
205+
try
206+
{
207+
return process.MainModule?.FileName;
208+
}
209+
catch (Win32Exception ex)
210+
{
211+
Log.Warning(ex, "Could not check process filename");
212+
return null;
213+
}
214+
})
215+
.Where(fullPath => fullPath != null && fullPath != _adbPath &&
216+
Path.GetFileName(fullPath).Equals(_adbExecutableName, StringComparison.OrdinalIgnoreCase))! /* fullPath definitely not null */;
95217
}
96218

97219
/// <summary>

QuestPatcher.Core/ExternalFilesDownloader.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,21 +509,40 @@ private async Task MakeExecutable(string path)
509509
/// Finds the location of the specified file, and downloads/extracts it if it does not exist.
510510
/// </summary>
511511
/// <param name="fileType">The type of file to download</param>
512+
/// <param name="clearExisting">Whether to delete the file if it already existed.</param>
512513
/// <returns>The location of the file</returns>
513514
/// <exception cref="FileDownloadFailedException">If downloading the file failed with every known URL</exception>
514-
public async Task<string> GetFileLocation(ExternalFileType fileType)
515+
public async Task<string> GetFileLocation(ExternalFileType fileType, bool clearExisting = false)
515516
{
516517
var fileInfo = _fileTypes[fileType];
517518

519+
// Store that the existing file has been cleared.
520+
if (clearExisting)
521+
{
522+
if (_fullyDownloaded.Remove(fileType))
523+
{
524+
await SaveFullyDownloaded();
525+
}
526+
}
527+
518528
// The save location is relative to the extract folder if requires extraction, otherwise it's just relative to the tools folder
519529
string saveLocation;
520530
if (fileInfo.ExtractionFolder == null)
521531
{
522532
saveLocation = Path.Combine(_specialFolders.ToolsFolder, fileInfo.SaveName.Value);
533+
if (clearExisting && File.Exists(saveLocation))
534+
{
535+
File.Delete(saveLocation);
536+
}
523537
}
524538
else
525539
{
526-
saveLocation = Path.Combine(_specialFolders.ToolsFolder, fileInfo.ExtractionFolder, fileInfo.SaveName.Value);
540+
string extractLocation = Path.Combine(_specialFolders.ToolsFolder, fileInfo.ExtractionFolder);
541+
if (clearExisting && Directory.Exists(extractLocation))
542+
{
543+
Directory.Delete(extractLocation, true);
544+
}
545+
saveLocation = Path.Combine(extractLocation, fileInfo.SaveName.Value);
527546
}
528547

529548
if (!_fullyDownloaded.Contains(fileType) || !File.Exists(saveLocation))

0 commit comments

Comments
 (0)