From 0d26525086e70e24d45754ced5b39de84fd58839 Mon Sep 17 00:00:00 2001 From: zyj <18107291228@163.com> Date: Thu, 30 Oct 2025 16:45:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E4=BB=B6=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adb/connect.go | 57 ++------------- adb/device.go | 94 +------------------------ adb/sync.go | 186 +++++++++---------------------------------------- cmd/main.go | 29 +++++++- 4 files changed, 62 insertions(+), 304 deletions(-) diff --git a/adb/connect.go b/adb/connect.go index 40eee09..3226f34 100644 --- a/adb/connect.go +++ b/adb/connect.go @@ -60,41 +60,6 @@ func ReadLenFrame(conn net.Conn) ([]byte, error) { return readN(conn, int(l), 10*time.Second) } -func ExecOut(conn net.Conn, cmd string) ([]byte, error) { - if err := WriteAdbCmd(conn, "exec-out:"+cmd); err != nil { - return nil, err - } - status, err := ReadStatus(conn) - if err != nil { - return nil, err - } - if status == "FAIL" { - msg, _ := ReadLenFrame(conn) - return nil, fmt.Errorf("exec-out FAIL: %s", string(msg)) - } - if status != "OKAY" { - return nil, fmt.Errorf("unexpected exec-out status: %s", status) - } - var out []byte - for { - data, err := ReadLenFrame(conn) - if err != nil { - if ne, ok := err.(net.Error); ok && ne.Timeout() { - break - } - if err == io.EOF { - break - } - return nil, err - } - if len(data) == 0 { - break - } - out = append(out, data...) - } - return out, nil -} - // transportTo: 指示 adb server 将后续请求路由到指定 serial func TransportTo(conn net.Conn, serial string) error { if err := WriteAdbCmd(conn, "host:transport:"+serial); err != nil { @@ -150,21 +115,7 @@ func ExecShell(conn net.Conn, shellCmd string) ([]byte, error) { // readResponse reads a 4-byte response like OKAY/FAIL and returns it and optional message (for FAIL) func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) { - readN := func(n int) ([]byte, error) { - buf := make([]byte, n) - total := 0 - conn.SetReadDeadline(time.Now().Add(5 * time.Second)) - for total < n { - nr, err := conn.Read(buf[total:]) - if err != nil { - return nil, err - } - total += nr - } - return buf, nil - } - - stb, err := readN(4) + stb, err := readN(conn, 4, 10*time.Second) if err != nil { return "", nil, err } @@ -177,7 +128,7 @@ func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) { } if st == "FAIL" { // read next 4 bytes (may be little-endian uint32 length or ASCII hex) - hdr, err := readN(4) + hdr, err := readN(conn, 4, 10*time.Second) if err != nil { if debug { fmt.Fprintf(os.Stderr, "ReadResponseFixed: no length header after FAIL: %v\n", err) @@ -191,7 +142,7 @@ func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) { // try little-endian uint32 first l := int(binary.LittleEndian.Uint32(hdr)) if l > 0 { - msg, err := readN(l) + msg, err := readN(conn, l, 10*time.Second) if err != nil { if debug { fmt.Fprintf(os.Stderr, "ReadResponseFixed: failed to read %d bytes message: %v\n", l, err) @@ -206,7 +157,7 @@ func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) { // fallback: try ASCII-hex parse (backwards compatibility) if n, perr := strconv.ParseInt(string(hdr), 16, 32); perr == nil && n > 0 { - msg, err := readN(int(n)) + msg, err := readN(conn, int(n), 10*time.Second) if err != nil { if debug { fmt.Fprintf(os.Stderr, "ReadResponseFixed: failed to read ascii-hex message of len %d: %v\n", n, err) diff --git a/adb/device.go b/adb/device.go index a7de707..4aba7ee 100644 --- a/adb/device.go +++ b/adb/device.go @@ -111,69 +111,6 @@ func parseDevicesPayload(payload string) []DeviceInfo { return out } -// getPropExecOut: 通过 exec-out:getprop 一次性获取设备所有 getprop 输出(返回原始字节) -func getPropExecOut(addr, serial string, timeout time.Duration) ([]byte, error) { - // open a connection and switch transport for this connection - conn, err := DialADB(addr, timeout) - if err != nil { - return nil, err - } - // ensure close - defer conn.Close() - - // transport - if err := WriteAdbCmd(conn, "host:transport:"+serial); err != nil { - return nil, err - } - status, err := ReadStatus(conn) - if err != nil { - return nil, err - } - if status == "FAIL" { - msg, _ := ReadLenFrame(conn) - return nil, fmt.Errorf("transport FAIL: %s", string(msg)) - } - if status != "OKAY" { - return nil, fmt.Errorf("unexpected transport status: %s", status) - } - - // send exec-out:getprop - if err := WriteAdbCmd(conn, "exec-out:getprop"); err != nil { - return nil, err - } - status, err = ReadStatus(conn) - if err != nil { - return nil, err - } - if status == "FAIL" { - msg, _ := ReadLenFrame(conn) - return nil, fmt.Errorf("exec-out FAIL: %s", string(msg)) - } - if status != "OKAY" { - return nil, fmt.Errorf("unexpected exec-out status: %s", status) - } - - // read frames until timeout/EOF and concatenate - var b []byte - for { - data, err := ReadLenFrame(conn) - if err != nil { - if ne, ok := err.(net.Error); ok && ne.Timeout() { - break - } - if err == io.EOF { - break - } - return nil, err - } - if len(data) == 0 { - break - } - b = append(b, data...) - } - return b, nil -} - // parseGetprop parses getprop output "key]: [value" lines into map func parseGetprop(raw []byte) map[string]string { m := map[string]string{} @@ -203,35 +140,6 @@ func parseGetprop(raw []byte) map[string]string { return m } -// CollectAllDevices gathers DeviceInfo for all devices listed by adb server -func CollectAllDevices(addr string, timeout time.Duration) ([]DeviceInfo, error) { - raw, err := listDevicesRaw(addr, timeout) - if err != nil { - return nil, err - } - devs := parseDevicesPayload(raw) - if len(devs) == 0 { - return devs, nil - } - - // For each device, fetch getprop via exec-out (sequential; can be parallelized) - for i := range devs { - serial := devs[i].Serial - rawProps, err := getPropExecOut(addr, serial, timeout) - if err != nil { - // record error in Props under special key - devs[i].Props["__getprop_error"] = err.Error() - continue - } - props := parseGetprop(rawProps) - // merge into existing short props (from devices -l) - for k, v := range props { - devs[i].Props[k] = v - } - } - return devs, nil -} - func parseDevicesMap(payload string) (map[string]string, []string) { m := make(map[string]string) lines := strings.Split(payload, "\n") @@ -292,7 +200,7 @@ func FindSerialByProduct(addr, targetProduct string) (string, error) { if err := TransportTo(conn, serial); err != nil { continue } - out, err := ExecOut(conn, "getprop ro.product.model") + out, err := ExecShell(conn, "getprop ro.product.model") if err == nil { if strings.TrimSpace(string(out)) == targetProduct { return serial, nil diff --git a/adb/sync.go b/adb/sync.go index 8283713..1613606 100644 --- a/adb/sync.go +++ b/adb/sync.go @@ -7,6 +7,7 @@ import ( "net" "os" "strconv" + "syscall" "time" ) @@ -22,52 +23,13 @@ func InitSync(conn net.Conn) *Sync { } } -// SyncPushTryVariants 按顺序尝试多种 SEND payload 风格,直到成功或尝试完毕。 -// addr: adb server (e.g., "127.0.0.1:5037") -// serial: device serial +// SyncPushFile 将本地文件推送到设备 // localPath: 本地文件路径 -// remotePath: 目标完整路径(必须包含文件名) -// mode: unix permission like 0644 -// debug: 打印调试信息 -func SyncPushTryVariants(addr, serial, localPath, remotePath string, mode int, debug bool) (int64, error) { - type sendOption struct { - name string - withNUL bool - modeFormat string // "hex" (0x8000|mode) or "dec" (decimal S_IFREG|mode) - } - opts := []sendOption{ - {"send-with-nul-hex", true, "hex"}, - {"send-without-nul-hex", false, "hex"}, - {"send-without-nul-dec", false, "dec"}, - } - - var lastErr error - for _, opt := range opts { - if debug { - fmt.Printf("[try] option=%s\n", opt.name) - } - n, err := syncPushOne(addr, serial, localPath, remotePath, mode, opt.withNUL, opt.modeFormat, debug) - if err == nil { - if debug { - fmt.Printf("[ok] option=%s pushed=%d\n", opt.name, n) - } - return n, nil - } - lastErr = fmt.Errorf("%s: %w", opt.name, err) - if debug { - fmt.Printf("[fail] option=%s err=%v\n", opt.name, err) - } - // small pause between tries - time.Sleep(150 * time.Millisecond) - } - return 0, lastErr -} - -// syncPushOne 在单个连接上按给定选项完成 sync push(一次性连接) -// withNUL: 是否在 SEND payload 后追加 NUL byte -// modeFormat: "hex" 表示 use 0x8000|mode in decimal string (common), "dec" 表示 decimal S_IFREG|mode -func syncPushOne(addr, serial, localPath, remotePath string, mode int, withNUL bool, modeFormat string, debug bool) (int64, error) { - // open file +// remotePath: 设备目标路径 +// mode: 文件权限 (如 0644) +// debug: 是否打印调试信息 +func (s *Sync) SyncPushFile(localPath, remotePath string, mode int, debug bool) (int64, error) { + // 打开文件 f, err := os.Open(localPath) if err != nil { return 0, err @@ -75,130 +37,44 @@ func syncPushOne(addr, serial, localPath, remotePath string, mode int, withNUL b defer f.Close() fi, _ := f.Stat() if debug { - fmt.Printf("local filesize=%d\n", fi.Size()) + fmt.Printf("[DEBUG] local filesize=%d\n", fi.Size()) } - // dial adb - d := net.Dialer{Timeout: 8 * time.Second} - conn, err := d.Dial("tcp", addr) - if err != nil { + // 打开 sync + if err := WriteAdbCmd(s.Conn, "sync:"); err != nil { return 0, err } - defer conn.Close() - if tcp, ok := conn.(*net.TCPConn); ok { - _ = tcp.SetNoDelay(true) - } - - writeAdbCmd := func(cmd string) error { - hdr := fmt.Sprintf("%04x", len(cmd)) - conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) - _, err := conn.Write([]byte(hdr + cmd)) - return err - } - readN := func(n int) ([]byte, error) { - buf := make([]byte, n) - total := 0 - for total < n { - conn.SetReadDeadline(time.Now().Add(5 * time.Second)) - nr, err := conn.Read(buf[total:]) - if err != nil { - return nil, err - } - total += nr - } - return buf, nil - } - read4 := func() ([]byte, error) { return readN(4) } - - // readResponse that returns status and optional msg (LE length) - readResponse := func() (string, []byte, error) { - stb, err := read4() - if err != nil { - return "", nil, err - } - st := string(stb) - // if OKAY quick return - if st == "OKAY" { - return st, nil, nil - } - if st == "FAIL" { - // try LE length - hdr, err := read4() - if err != nil { - return st, nil, nil - } - l := int(binary.LittleEndian.Uint32(hdr)) - if l > 0 { - msg, err := readN(l) - if err != nil { - return st, nil, nil - } - return st, msg, nil - } - // fallback ascii-hex - if n, perr := strconv.ParseInt(string(hdr), 16, 32); perr == nil && n > 0 { - msg, err := readN(int(n)) - if err != nil { - return st, nil, nil - } - return st, msg, nil - } - return st, nil, nil - } - return st, nil, nil - } - - // transport - if err := writeAdbCmd("host:transport:" + serial); err != nil { - return 0, err - } - tok, err := read4() + tok, err := readN(s.Conn, 4, 10*time.Second) if err != nil { return 0, err } if string(tok) != "OKAY" { - return 0, fmt.Errorf("transport failed: %q", string(tok)) - } - - // open sync - if err := writeAdbCmd("sync:"); err != nil { - return 0, err - } - tok, err = read4() - if err != nil { - return 0, err - } - if string(tok) != "OKAY" { - _, msg, _ := readResponse() + _, msg, _ := ReadResponse(s.Conn, true) if len(msg) > 0 { return 0, fmt.Errorf("sync open failed: %s", string(msg)) } return 0, fmt.Errorf("sync open failed: %q", string(tok)) } - // build SEND payload according options - var modeStr string - if modeFormat == "hex" { - // common implementations expect decimal of (S_IFREG|mode) where S_IFREG is 0100000 (octal) but using 0x8000 is fine as decimal string - modeStr = strconv.FormatInt(int64(0x8000|mode), 10) - } else { - // decimal form: simply decimal of (0x8000|mode) - modeStr = strconv.FormatInt(int64(0x8000|mode), 10) - } + // 构造 SEND payload + modeStr := strconv.Itoa(syscall.S_IFREG | mode) sendPayload := []byte(remotePath + "," + modeStr) - if withNUL { - sendPayload = append(sendPayload, 0) - } - // write SEND - if _, err := conn.Write(append([]byte("SEND"), sendPayload...)); err != nil { + // 写入 "SEND" + 长度 + payload + hdr := make([]byte, 8) + copy(hdr[:4], []byte("SEND")) + binary.LittleEndian.PutUint32(hdr[4:], uint32(len(sendPayload))) + if _, err := s.Conn.Write(hdr); err != nil { + return 0, err + } + if _, err := s.Conn.Write(sendPayload); err != nil { return 0, err } if debug { - fmt.Printf("Wrote SEND payload len=%d withNUL=%v modeFmt=%s path=%s\n", len(sendPayload), withNUL, modeFormat, remotePath) + fmt.Printf("[DEBUG] Wrote SEND payload len=%d path=%s mode=%s\n", len(sendPayload), remotePath, modeStr) } - // write DATA blocks if file has content (if zero-length, skip DATA) + // 写入 DATA 块 var total int64 buf := make([]byte, maxChunk) for { @@ -207,10 +83,10 @@ func syncPushOne(addr, serial, localPath, remotePath string, mode int, withNUL b hdr := make([]byte, 8) copy(hdr[:4], []byte("DATA")) binary.LittleEndian.PutUint32(hdr[4:], uint32(n)) - if _, err := conn.Write(hdr); err != nil { + if _, err := s.Conn.Write(hdr); err != nil { return total, err } - if _, err := conn.Write(buf[:n]); err != nil { + if _, err := s.Conn.Write(buf[:n]); err != nil { return total, err } total += int64(n) @@ -223,19 +99,19 @@ func syncPushOne(addr, serial, localPath, remotePath string, mode int, withNUL b } } - // send DONE + // 发送 DONE done := make([]byte, 8) copy(done[:4], []byte("DONE")) binary.LittleEndian.PutUint32(done[4:], uint32(time.Now().Unix())) - if _, err := conn.Write(done); err != nil { + if _, err := s.Conn.Write(done); err != nil { return total, err } if debug { - fmt.Println("Wrote DONE, waiting response") + fmt.Println("[DEBUG] Wrote DONE, waiting response") } - // read final - resp, msg, err := readResponse() + // 读取最终响应 + resp, msg, err := ReadResponse(s.Conn, true) if err != nil { return total, err } diff --git a/cmd/main.go b/cmd/main.go index aeb9f34..a65cee2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,10 +10,33 @@ func main() { // edit these for your environment addr := "127.0.0.1:5037" local := "C:/Users/01/Desktop/aaa.PNG" - remote := "/sdcard/aaa.PNG" + remote := "/sdcard/ccc.PNG" mode := 0644 - // targetProduct := "23113RKC6C" - adb.SyncPushTryVariants(addr, "emulator-5554", local, remote, mode, true) + targetProduct := "23113RKC6C" + + serial, err := adb.FindSerialByProduct(addr, targetProduct) + if err != nil { + fmt.Println("find device error:", err) + return + } + fmt.Println("found serial:", serial) + + // Now open a new connection and transport to the found device for further ops + conn, err := adb.DialADB(addr, 15*time.Second) + if err != nil { + fmt.Println("dial error:", err) + return + } + defer conn.Close() + adb.TransportTo(conn, serial) + + sync := adb.InitSync(conn) + n, err := sync.SyncPushFile(local, remote, mode, true) + if err != nil { + fmt.Println("Push 失败:", err) + } else { + fmt.Printf("Push 成功, 共写入 %d 字节\n", n) + } } // 连接验证