-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
大: 更新scrcpy版本为2.1 更新最低版本为安卓7 小: 修复安卓7等低版本无法使用的问题 修复长按添加按钮进行强制清理时崩溃的问题
- Loading branch information
1 parent
322770e
commit bef9581
Showing
24 changed files
with
2,077 additions
and
26 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
scrcpy_android/app/src/main/java/dev/mobile/dadb/AdbConnection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/* | ||
* Copyright (c) 2021 mobile.dev inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
package dev.mobile.dadb | ||
|
||
import okio.Sink | ||
import okio.Source | ||
import okio.sink | ||
import okio.source | ||
import org.jetbrains.annotations.TestOnly | ||
import java.io.Closeable | ||
import java.io.IOException | ||
import java.net.Socket | ||
import java.util.* | ||
|
||
internal class AdbConnection internal constructor( | ||
adbReader: AdbReader, | ||
private val adbWriter: AdbWriter, | ||
private val closeable: Closeable?, | ||
private val supportedFeatures: Set<String>, | ||
private val version: Int, | ||
private val maxPayloadSize: Int | ||
) : AutoCloseable { | ||
|
||
private val random = Random() | ||
private val messageQueue = AdbMessageQueue(adbReader) | ||
|
||
@Throws(IOException::class) | ||
fun open(destination: String): AdbStream { | ||
val localId = newId() | ||
messageQueue.startListening(localId) | ||
try { | ||
adbWriter.writeOpen(localId, destination) | ||
val message = messageQueue.take(localId, Constants.CMD_OKAY) | ||
val remoteId = message.arg0 | ||
return AdbStreamImpl(messageQueue, adbWriter, maxPayloadSize, localId, remoteId) | ||
} catch (e: Throwable) { | ||
messageQueue.stopListening(localId) | ||
throw e | ||
} | ||
} | ||
|
||
fun supportsFeature(feature: String): Boolean { | ||
return supportedFeatures.contains(feature) | ||
} | ||
|
||
private fun newId(): Int { | ||
return random.nextInt() | ||
} | ||
|
||
@TestOnly | ||
internal fun ensureEmpty() { | ||
messageQueue.ensureEmpty() | ||
} | ||
|
||
override fun close() { | ||
try { | ||
messageQueue.close() | ||
adbWriter.close() | ||
closeable?.close() | ||
} catch (ignore: Throwable) { | ||
} | ||
} | ||
|
||
companion object { | ||
|
||
fun connect(socket: Socket, keyPair: AdbKeyPair? = null): AdbConnection { | ||
val source = socket.source() | ||
val sink = socket.sink() | ||
return connect(source, sink, keyPair, socket) | ||
} | ||
|
||
private fun connect( | ||
source: Source, | ||
sink: Sink, | ||
keyPair: AdbKeyPair? = null, | ||
closeable: Closeable? = null | ||
): AdbConnection { | ||
val adbReader = AdbReader(source) | ||
val adbWriter = AdbWriter(sink) | ||
|
||
try { | ||
return connect(adbReader, adbWriter, keyPair, closeable) | ||
} catch (t: Throwable) { | ||
adbReader.close() | ||
adbWriter.close() | ||
throw t | ||
} | ||
} | ||
|
||
private fun connect( | ||
adbReader: AdbReader, | ||
adbWriter: AdbWriter, | ||
keyPair: AdbKeyPair?, | ||
closeable: Closeable? | ||
): AdbConnection { | ||
adbWriter.writeConnect() | ||
|
||
var message = adbReader.readMessage() | ||
|
||
if (message.command == Constants.CMD_AUTH) { | ||
checkNotNull(keyPair) { "Authentication required but no KeyPair provided" } | ||
check(message.arg0 == Constants.AUTH_TYPE_TOKEN) { "Unsupported auth type: $message" } | ||
|
||
val signature = keyPair.signPayload(message) | ||
adbWriter.writeAuth(Constants.AUTH_TYPE_SIGNATURE, signature) | ||
|
||
message = adbReader.readMessage() | ||
if (message.command == Constants.CMD_AUTH) { | ||
adbWriter.writeAuth(Constants.AUTH_TYPE_RSA_PUBLIC, keyPair.publicKeyBytes) | ||
message = adbReader.readMessage() | ||
} | ||
} | ||
|
||
if (message.command != Constants.CMD_CNXN) throw IOException("Connection failed: $message") | ||
|
||
val connectionString = parseConnectionString(String(message.payload)) | ||
val version = message.arg0 | ||
val maxPayloadSize = message.arg1 | ||
|
||
return AdbConnection( | ||
adbReader, | ||
adbWriter, | ||
closeable, | ||
connectionString.features, | ||
version, | ||
maxPayloadSize | ||
) | ||
} | ||
|
||
// ie: "device::ro.product.name=sdk_gphone_x86;ro.product.model=Android SDK built for x86;ro.product.device=generic_x86;features=fixed_push_symlink_timestamp,apex,fixed_push_mkdir,stat_v2,abb_exec,cmd,abb,shell_v2" | ||
private fun parseConnectionString(connectionString: String): ConnectionString { | ||
val keyValues = connectionString.substringAfter("device::") | ||
.split(";") | ||
.map { it.split("=") } | ||
.mapNotNull { if (it.size != 2) null else it[0] to it[1] } | ||
.toMap() | ||
if ("features" !in keyValues) throw IOException("Failed to parse features from connection string: $connectionString") | ||
val features = keyValues.getValue("features").split(",").toSet() | ||
return ConnectionString(features) | ||
} | ||
} | ||
} | ||
|
||
private data class ConnectionString(val features: Set<String>) |
180 changes: 180 additions & 0 deletions
180
scrcpy_android/app/src/main/java/dev/mobile/dadb/AdbKeyPair.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
/* | ||
* Copyright (c) 2021 mobile.dev inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
package dev.mobile.dadb | ||
|
||
import java.io.File | ||
import java.math.BigInteger | ||
import java.nio.ByteBuffer | ||
import java.nio.ByteOrder | ||
import java.security.KeyPairGenerator | ||
import java.security.PrivateKey | ||
import java.security.interfaces.RSAPublicKey | ||
import java.util.* | ||
import javax.crypto.Cipher | ||
|
||
|
||
class AdbKeyPair( | ||
private val privateKey: PrivateKey, | ||
internal val publicKeyBytes: ByteArray | ||
) { | ||
|
||
internal fun signPayload(message: AdbMessage): ByteArray { | ||
val cipher = Cipher.getInstance("RSA/ECB/NoPadding") | ||
cipher.init(Cipher.ENCRYPT_MODE, privateKey) | ||
cipher.update(SIGNATURE_PADDING) | ||
return cipher.doFinal(message.payload, 0, message.payloadLength) | ||
} | ||
|
||
companion object { | ||
|
||
private const val KEY_LENGTH_BITS = 2048 | ||
private const val KEY_LENGTH_BYTES = KEY_LENGTH_BITS / 8 | ||
private const val KEY_LENGTH_WORDS = KEY_LENGTH_BYTES / 4 | ||
|
||
private val SIGNATURE_PADDING = ubyteArrayOf( | ||
0x00u, 0x01u, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, | ||
0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0x00u, | ||
0x30u, 0x21u, 0x30u, 0x09u, 0x06u, 0x05u, 0x2bu, 0x0eu, 0x03u, 0x02u, 0x1au, 0x05u, 0x00u, | ||
0x04u, 0x14u | ||
).toByteArray() | ||
|
||
@JvmStatic | ||
fun readDefault(): AdbKeyPair { | ||
val privateKeyFile = File(System.getenv("HOME"), ".android/adbkey") | ||
val publicKeyFile = File(System.getenv("HOME"), ".android/adbkey.pub") | ||
|
||
if (!privateKeyFile.exists()) { | ||
generate(privateKeyFile, publicKeyFile) | ||
} | ||
|
||
return read(privateKeyFile, publicKeyFile) | ||
} | ||
|
||
@JvmStatic | ||
@JvmOverloads | ||
fun read(privateKeyFile: File, publicKeyFile: File? = null): AdbKeyPair { | ||
val privateKey = PKCS8.parse(privateKeyFile.readBytes()) | ||
val publicKeyBytes = if (publicKeyFile?.exists() == true) { | ||
readAdbPublicKey(publicKeyFile) | ||
} else { | ||
ByteArray(0) | ||
} | ||
|
||
return AdbKeyPair(privateKey, publicKeyBytes) | ||
} | ||
|
||
@JvmStatic | ||
fun generate(privateKeyFile: File, publicKeyFile: File) { | ||
val keyPair = KeyPairGenerator.getInstance("RSA").let { | ||
it.initialize(KEY_LENGTH_BITS) | ||
it.genKeyPair() | ||
} | ||
|
||
privateKeyFile.absoluteFile.parentFile?.mkdirs() | ||
publicKeyFile.absoluteFile.parentFile?.mkdirs() | ||
|
||
privateKeyFile.writer().use { out -> | ||
val base64 = Base64.getMimeEncoder(64, "\n".toByteArray()) | ||
out.write("-----BEGIN PRIVATE KEY-----\n") | ||
out.write(base64.encodeToString(keyPair.private.encoded)) | ||
out.write("\n-----END PRIVATE KEY-----") | ||
} | ||
|
||
publicKeyFile.writer().use { out -> | ||
val base64 = Base64.getEncoder() | ||
val bytes = convertRsaPublicKeyToAdbFormat(keyPair.public as RSAPublicKey) | ||
out.write(base64.encodeToString(bytes)) | ||
out.write(" unknown@unknown") | ||
} | ||
} | ||
|
||
private fun readAdbPublicKey(file: File): ByteArray { | ||
val bytes = file.readBytes() | ||
val publicKeyBytes = bytes.copyOf(bytes.size + 1) | ||
publicKeyBytes[bytes.size] = 0 | ||
return publicKeyBytes | ||
} | ||
|
||
// https://github.com/cgutman/AdbLib/blob/d6937951eb98557c76ee2081e383d50886ce109a/src/com/cgutman/adblib/AdbCrypto.java#L83-L137 | ||
@Suppress("JoinDeclarationAndAssignment") | ||
private fun convertRsaPublicKeyToAdbFormat(pubkey: RSAPublicKey): ByteArray { | ||
/* | ||
* ADB literally just saves the RSAPublicKey struct to a file. | ||
* | ||
* typedef struct RSAPublicKey { | ||
* int len; // Length of n[] in number of uint32_t | ||
* uint32_t n0inv; // -1 / n[0] mod 2^32 | ||
* uint32_t n[RSANUMWORDS]; // modulus as little endian array | ||
* uint32_t rr[RSANUMWORDS]; // R^2 as little endian array | ||
* int exponent; // 3 or 65537 | ||
* } RSAPublicKey; | ||
*/ | ||
|
||
/* ------ This part is a Java-ified version of RSA_to_RSAPublicKey from adb_host_auth.c ------ */ | ||
val r32: BigInteger | ||
val r: BigInteger | ||
var rr: BigInteger | ||
var rem: BigInteger | ||
var n: BigInteger | ||
val n0inv: BigInteger | ||
r32 = BigInteger.ZERO.setBit(32) | ||
n = pubkey.modulus | ||
r = BigInteger.ZERO.setBit(KEY_LENGTH_WORDS * 32) | ||
rr = r.modPow(BigInteger.valueOf(2), n) | ||
rem = n.remainder(r32) | ||
n0inv = rem.modInverse(r32) | ||
val myN = IntArray(KEY_LENGTH_WORDS) | ||
val myRr = IntArray(KEY_LENGTH_WORDS) | ||
var res: Array<BigInteger> | ||
for (i in 0 until KEY_LENGTH_WORDS) { | ||
res = rr.divideAndRemainder(r32) | ||
rr = res[0] | ||
rem = res[1] | ||
myRr[i] = rem.toInt() | ||
res = n.divideAndRemainder(r32) | ||
n = res[0] | ||
rem = res[1] | ||
myN[i] = rem.toInt() | ||
} | ||
|
||
/* ------------------------------------------------------------------------------------------- */ | ||
val bbuf: ByteBuffer = ByteBuffer.allocate(524).order(ByteOrder.LITTLE_ENDIAN) | ||
bbuf.putInt(KEY_LENGTH_WORDS) | ||
bbuf.putInt(n0inv.negate().toInt()) | ||
for (i in myN) bbuf.putInt(i) | ||
for (i in myRr) bbuf.putInt(i) | ||
bbuf.putInt(pubkey.publicExponent.toInt()) | ||
return bbuf.array() | ||
} | ||
} | ||
} |
Oops, something went wrong.