Skip to content

Commit

Permalink
更新V11.0
Browse files Browse the repository at this point in the history
大:
更新scrcpy版本为2.1
更新最低版本为安卓7
小:
修复安卓7等低版本无法使用的问题
修复长按添加按钮进行强制清理时崩溃的问题
  • Loading branch information
mingzhixian committed Jun 24, 2023
1 parent 322770e commit bef9581
Show file tree
Hide file tree
Showing 24 changed files with 2,077 additions and 26 deletions.
17 changes: 0 additions & 17 deletions scrcpy_android/.idea/deploymentTargetDropDown.xml

This file was deleted.

3 changes: 1 addition & 2 deletions scrcpy_android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ android {

defaultConfig {
applicationId "top.saymzx.scrcpy.android"
minSdk 23
minSdk 24
targetSdk 33
versionCode 110
versionName "11.0"
Expand Down Expand Up @@ -45,6 +45,5 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'dev.mobile:dadb:1.2.6'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}
159 changes: 159 additions & 0 deletions scrcpy_android/app/src/main/java/dev/mobile/dadb/AdbConnection.kt
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 scrcpy_android/app/src/main/java/dev/mobile/dadb/AdbKeyPair.kt
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()
}
}
}
Loading

0 comments on commit bef9581

Please sign in to comment.