88using System . Text ;
99using System . Threading . Tasks ;
1010using Serilog ;
11+ using Version = SemanticVersioning . Version ;
1112
1213namespace 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>
0 commit comments