Image default
Máy Tính

Giải Mã Bluetooth Đèn Govee H615B: Tích Hợp Home Assistant Dễ Dàng

Với sự bùng nổ của nhà thông minh, việc tích hợp các thiết bị cũ hoặc các sản phẩm có API hạn chế vào hệ thống điều khiển trung tâm như Home Assistant đang trở thành một thách thức thú vị. Tôi đã và đang nỗ lực tối ưu hệ thống nhà thông minh của mình, và điều đó bao gồm việc tìm cách kết nối những phần cứng cũ. Hầu hết các thiết bị Tuya cũ của tôi đều hoạt động tốt với Local Tuya, và nhiều dịch vụ tự lưu trữ cũng cung cấp thông tin hữu ích cho Home Assistant. Tuy nhiên, tôi gặp phải một trở ngại lớn với dải đèn Govee H615B của mình, điều này đã dẫn tôi vào một hành trình reverse engineering đầy thử thách để tích hợp chúng như mọi thiết bị khác.

Govee cung cấp cả API dựa trên web và API cục bộ (local API). Nếu API web đủ tốt, có lẽ tôi đã chấp nhận sử dụng nó. Tuy nhiên, API này bị giới hạn tần suất rất nhanh. Bạn muốn điều chỉnh độ sáng cho đến khi vừa ý? Khó khăn đấy. Chỉ sau vài lần thay đổi trạng thái, bạn sẽ bị khóa truy cập trong một phút. Đối với API cục bộ, mặc dù nó tồn tại về mặt kỹ thuật, nhưng tôi không thể kích hoạt nó trên đèn Govee của mình. Tôi không rõ lý do, nhưng tùy chọn này hoàn toàn bị vô hiệu hóa trong cài đặt ứng dụng Govee.

Sau khi tìm hiểu một số dự án reverse engineering liên quan đến các thiết bị chiếu sáng Govee khác, tôi nghĩ rằng việc thử reverse engineering thiết bị của mình cũng đáng giá. Từ đó, hành trình của tôi bắt đầu, trang bị Wireshark và Python, để tìm hiểu cách những chiếc đèn này hoạt động và liệu tôi có thể điều khiển chúng từ bất kỳ thiết bị Bluetooth nào chứ không chỉ ứng dụng chính thức. Toàn bộ mã nguồn được sử dụng trong bài viết này đều là mã nguồn mở và bạn có thể tìm thấy liên kết ở cuối bài.

Đánh Giá Vấn Đề và Công Cụ

Xác định mục tiêu và nguồn lực ban đầu

Bước đầu tiên trong reverse engineering là đánh giá vấn đề, các công cụ có sẵn và mục tiêu cuối cùng của bạn. Tôi có trong tay những công cụ sau:

  • Một chiếc MacBook M4 Pro
  • Một chiếc Google Pixel 8 Pro
  • Ứng dụng Govee (có thể điều khiển đèn khi không có kết nối mạng qua Bluetooth LE)
  • Wireshark
  • Bleak, một framework Bluetooth trong Python
  • Một chiếc Milk-V Duo S (vi điều khiển với cả lõi Arm và RISC-V có khả năng chạy Python, cùng với hỗ trợ Wi-Fi/Bluetooth tích hợp)

Tôi muốn tránh root chiếc Google Pixel 8 Pro của mình nếu có thể, và tôi nghĩ rằng điều đó là khả thi, nên tôi xem nó là lựa chọn cuối cùng. Mọi thứ dường như rất khả thi, và nếu không có xác thực để điều khiển những chiếc đèn này, tôi có thể phát đi các lệnh của riêng mình để điều khiển chúng.

Mục tiêu cuối cùng là viết một script Python có thể chạy trên Milk-V Duo S của tôi, với một máy chủ chấp nhận lệnh từ Home Assistant để sau đó phát đến đèn.

Máy tính MacBook Pro M4 Pro 16 inch, một trong những công cụ dùng để phân tích giao thức Bluetooth đèn GoveeMáy tính MacBook Pro M4 Pro 16 inch, một trong những công cụ dùng để phân tích giao thức Bluetooth đèn Govee

Thu Thập Dữ Liệu Bluetooth

Kích hoạt ghi nhật ký HCI trên Google Pixel

Để tìm hiểu cách điều khiển những chiếc đèn này từ xa, tôi nghĩ cách tốt nhất là ghi lại các gói tin Bluetooth được gửi đi và nhận về. Đó là lý do tại sao tôi sử dụng Google Pixel 8 Pro. Có những phần cứng chuyên dụng để sniff gói tin Bluetooth khi đang truyền tải, nhưng tôi không có những phần cứng đó, và Android có một trình ghi nhật ký Bluetooth HCI (Host Controller Interface) tích hợp sẵn có thể hoạt động tốt. Nhật ký HCI là một bản ghi cấp thấp về mọi thứ điện thoại của bạn gửi đi và nhận về. Bạn có thể bật tính năng này trong tùy chọn nhà phát triển.

Câu hỏi tiếp theo là tại sao tôi lại sử dụng Google Pixel 8 Pro mà không phải Oppo Find N5 hay bất kỳ thiết bị nào khác. Nhật ký được lưu trữ ở đâu? Cách thông thường để truy cập nó ngày nay là thông qua adb; bạn có thể khởi tạo một báo cáo lỗi được lưu vào máy tính của mình, và nhật ký nên được lưu trong thư mục nhật ký Bluetooth, nhưng điều đó không xảy ra trên Oppo Find N5 của tôi. Tôi một tệp, nhưng nó trống rỗng. Do nhiều công ty thực hiện sửa đổi các hệ thống như thế này, tôi sau đó đã chọn sử dụng Pixel 8 Pro để tránh bất kỳ “trò đùa” nào của nhà sản xuất gốc (OEM). Chuyển sang Google Pixel 8 Pro cho phép tôi ghi lại một nhật ký Bluetooth HCI thực sự chứa dữ liệu.

Trước khi kéo nhật ký, cần phải điền dữ liệu hữu ích vào đó trước. Tôi đã cài đặt ứng dụng Govee và đăng nhập vào tài khoản của mình, sau đó tắt Wi-Fi. Điều này có nghĩa là ứng dụng buộc phải sử dụng Bluetooth để điều khiển đèn, do đó dữ liệu sẽ được lưu vào nhật ký. Sau khi thay đổi độ sáng, màu sắc và bật tắt đèn nhiều lần, đã đến lúc cắm Pixel 8 Pro của tôi vào máy tính xách tay và kéo báo cáo lỗi bằng adb.

Điện thoại Google Pixel dùng để ghi lại nhật ký Bluetooth HCI, bước quan trọng trong quá trình giải mã giao thức đèn GoveeĐiện thoại Google Pixel dùng để ghi lại nhật ký Bluetooth HCI, bước quan trọng trong quá trình giải mã giao thức đèn Govee

Phân Tích Gói Tin Cơ Bản Với Wireshark

Đừng vội vàng; hãy tập trung vào những điều nhỏ nhặt trước

Tệp nhật ký Bluetooth HCI sẽ nằm trong FS/data/misc/bluetooth/logs và sẽ có phần mở rộng .cfa sau khi bạn giải nén tệp báo cáo lỗi. Tệp .cfa là một tệp BTSnoop chứa các gói L2CAP. Những gói tin này mô tả tất cả các giao tiếp mà thiết bị của bạn thực hiện qua Bluetooth và thường được các thiết bị Bluetooth Low Energy (BLE) sử dụng để giao tiếp. Trong trường hợp cụ thể này, khá dễ dàng để tìm ra thiết bị nào tôi cần xem xét, nhưng tôi cũng sử dụng thư viện Python Bleak để quét các thiết bị và ID của chúng.

Tôi đã viết một bộ quét đơn giản trong Bleak, và khi tôi xác định được UUID của thiết bị mình cần, tôi có thể truy vấn nó để tìm các đặc tính (characteristics). Đặc tính về cơ bản là các profile. Một client có thể khởi tạo các lệnh nhắm mục tiêu vào một đặc tính và có thể nhận phản hồi, còn một server có thể chấp nhận các lệnh đó và thực hiện chúng. Nói thêm, macOS và framework CoreBluetooth của nó sẽ cung cấp cho bạn một UUID cho các thiết bị Bluetooth Low Energy thay vì một địa chỉ MAC để giao tiếp. Điều này không sao cả, nhưng đó là điều cần lưu ý nếu bạn đang viết mã để chuyển sang một thiết bị khác sau này. Địa chỉ MAC mà bạn cần sử dụng (thay vì UUID) sẽ có trong nhật ký.

Trong quá trình quét của tôi, khá dễ dàng nhận ra Govee H615B. Tôi đã có thể xác định một số đặc tính, cung cấp cho tôi thông tin cần thiết để điều tra sâu hơn trong nhật ký mà tôi đã sao chép từ điện thoại. Chúng bao gồm:

  • Handle: 0x0009, UUID: 00010203-0405-0607-0809-0a0b0c0d2b10
  • Handle: 0x000d, UUID: 00010203-0405-0607-0809-0a0b0c0d2b11
  • Handle: 0x0012, UUID: f000ffc1-0451-4000-b000-000000000000
  • Handle: 0x0016, UUID: f000ffc2-0451-4000-b000-000000000000

Lưu ý rằng hai đặc tính kết thúc bằng “b10” và “b11” có khả năng liên quan đến nhau, cũng như hai đặc tính có “c1” và “c2”. Chúng ta sẽ tập trung vào hai đặc tính kết thúc bằng b10 và b11, vì từ nghiên cứu của tôi về các thiết bị Govee khác, dường như hai đặc tính này liên quan đến việc thiết lập trạng thái cho đèn, và các thiết bị khác cũng khớp chính xác các chuỗi này.

Mã Python sử dụng thư viện Bleak để quét các thiết bị Bluetooth LE và truy vấn đặc tính (characteristics) của đèn GoveeMã Python sử dụng thư viện Bleak để quét các thiết bị Bluetooth LE và truy vấn đặc tính (characteristics) của đèn Govee

Đây là một bối cảnh khác khi nói đến các thiết bị này: mặc dù chúng đều tương tự nhau về cách tương tác, nhưng dường như chúng đều hơi khác nhau về cách chấp nhận lệnh. Một số có các phân đoạn cho các phần khác nhau của dải đèn (để chúng có thể được điều khiển riêng lẻ), và một số có xác thực trong quá trình ghép nối. Tôi đặc biệt lo lắng về bước xác thực này, nhưng tôi phát hiện ra rằng khi Pixel 8 Pro của tôi kết nối (và đó là lần đầu tiên tôi kết nối nó), không có kiểm tra nào về thiết bị đang gửi lệnh. Đây có thể là một lỗ hổng bảo mật (mặc dù khá ít gây hại trên bề mặt), nhưng chúng ta sẽ có thể sử dụng nó làm lợi thế của mình.

Một điều tôi tìm thấy trong quá trình kết nối ban đầu giữa điện thoại và đèn đã trở thành một phần quan trọng để làm cho tất cả những điều này hoạt động. Bạn có nhớ đặc tính tôi đã đề cập kết thúc bằng “b10” không? Thiết bị client (Pixel 8 Pro trong trường hợp này) được bộ điều khiển Bluetooth thuộc về đèn gửi một thông báo, và nó đến từ đặc tính kết thúc bằng b10. Giờ đây chúng ta biết rằng chúng ta cần lắng nghe dịch vụ này, vì vậy chúng ta sẽ ghi nhớ điều đó khi viết mã để kết nối với nó sau này.

Giao diện Wireshark hiển thị gói tin thông báo từ đặc tính 'b10' của đèn Govee, một phần quan trọng trong quá trình kết nốiGiao diện Wireshark hiển thị gói tin thông báo từ đặc tính 'b10' của đèn Govee, một phần quan trọng trong quá trình kết nối

Tiếp theo, tôi nhận thấy nhiều gói tin được gửi từ Pixel của tôi đến đèn có giá trị sau:

  • aa010000000000000000000000000000000000ab

Đây dường như là các gói tin giữ kết nối (keep-alive packets), thông báo cho đèn Govee rằng chúng ta vẫn muốn gửi và nhận thông tin. Chúng được gửi khoảng mỗi hai giây và chiếm phần lớn nhật ký. Tôi nhận thấy rằng khi tôi kết nối với đèn bình thường bằng Bleak, chúng sẽ ngắt kết nối khỏi máy tính xách tay của tôi chỉ trong vài giây. Vì điện thoại của tôi dường như không có liên lạc nào khác trong thời gian dài ngoài việc gửi các giá trị tương tự, tôi nghĩ rằng chúng phải là các gói tin giữ kết nối. Điều này cũng phù hợp với những gì các thiết bị Govee khác dường như làm.

Mặc dù tôi không chắc dữ liệu sau “aa” là gì, nhưng byte cuối cùng của chuỗi (tức là hai ký tự cuối cùng) rất quan trọng. Các gói tin Bluetooth LE có độ dài 20 byte (không có MTU mở rộng), và có vẻ như các gói tin này sử dụng đệm bằng số 0 (zero padding) để đạt được độ dài gói tin đó. Chúng ta có thể kết luận tại thời điểm này rằng các gói tin bắt đầu bằng 0xaa biểu thị một gói tin giữ kết nối, nhưng còn hai chữ số cuối cùng thì sao? Chúng ta sẽ tìm hiểu chúng sau.

Gói tin Bluetooth lệnh bật đèn Govee được hiển thị trong Wireshark, bắt đầu bằng '330101...'Gói tin Bluetooth lệnh bật đèn Govee được hiển thị trong Wireshark, bắt đầu bằng '330101…'

Tiếp theo, chúng ta sẽ xem xét các lệnh tôi đã gửi. Khi tôi mở ứng dụng lần đầu và kết nối với đèn, tôi đã bật và tắt chúng. Trong nhật ký, phần dữ liệu đầu tiên được truyền đi và không giống gói tin giữ kết nối hay liên quan đến kết nối ban đầu là gói tin trên. Tôi đã tắt chúng một lần nữa, và tôi tìm thấy một giá trị rất giống với giá trị trên nhưng hơi khác, và điều này khớp với thời gian tôi đã ghi chú. Sau đó tôi có thể suy ra rằng hai giá trị để bật và tắt đèn là như sau:

  • Bật đèn: 3301010000000000000000000000000000000033
  • Tắt đèn: 3301000000000000000000000000000000000032

Hãy chú ý đến cấu trúc của gói tin; một lần nữa, chúng ta có rất nhiều byte không và hai byte khác nhau ở cuối. Để làm rõ, chúng ta nói “0x” để biểu thị một giá trị thập lục phân (hexadecimal), sử dụng hệ cơ số 16. “0x” làm rõ rằng chúng ta đang nói về thập lục phân, chứ không phải một số thập phân thông thường, và 0x33 là “51” trong hệ thập phân. 0x33 dường như biểu thị một lệnh, với dữ liệu sau đó đưa ra hướng dẫn để thực hiện. Trong trường hợp này, chúng ta có 0x33, 0x01, và 0x01 để bật đèn và 0x33, 0x01, và 0x00 để tắt chúng, cho thấy rằng một giá trị boolean ở byte thứ ba kiểm soát trạng thái bật/tắt. Chúng ta đã có đủ để thử nghiệm bằng cách thiết lập bộ nhận thông báo của mình và về cơ bản lặp lại các hướng dẫn tương tự cho đèn. Chúng ta không cần lo lắng về hai chữ số cuối cùng vì chúng đã được tính toán cho chúng ta, nhưng chúng ta cũng sẽ tìm hiểu cách tự tính toán chúng.

Điều Khiển Màu Sắc và Độ Sáng Của Đèn

Vượt qua giới hạn bật/tắt để tùy chỉnh đèn

Hiện tại, chúng ta đã có thể bật và tắt đèn qua Bluetooth. Đó là một bước tiến khá lớn, nhưng những chiếc đèn thông minh đầy màu sắc như thế này có nhiều hơn một công tắc bật tắt. Tôi đã điều tra về màu sắc và độ sáng, vì tôi cũng đã thay đổi cả hai trong ứng dụng để xem nó sẽ trông như thế nào ở cấp độ gói tin. Tôi tìm thấy gói tin sau:

  • 33050dfe0e1f00000000000000000000000d4

Lại là byte khởi đầu 0x33 đó, vì vậy chúng ta biết mình đang nhận một lệnh. Tôi không chắc về 050d, nhưng “fe0e1f” trông giống như một mã màu hex. Khi tôi chuyển đổi nó từ hex sang một màu thực tế, nó nổi bật là màu đỏ, và tôi đã đổi đèn của mình sang màu đỏ trong quá trình thử nghiệm. Tôi muốn kiểm tra xem liệu tôi có thể thay thế “fe0e1” bằng màu của riêng mình không, nhưng có một vấn đề. Trước đây, chúng ta chỉ cần phát lại các gói tin cho đèn, và chúng sẽ thực hiện lại các lệnh mà chúng ta đã thấy trong nhật ký. Làm thế nào để chúng ta tạo ra các lệnh mới? Chúng ta không thể chỉ đơn giản thay thế các giá trị hex màu đó bằng màu của chúng ta. Lý do chúng ta không thể là do sự bao gồm của byte cuối cùng đó.

Byte cuối cùng đó là một checksum, về cơ bản xác nhận rằng dữ liệu đã đến trong tình trạng hoàn chỉnh và không bị lỗi. Nó được tính bằng cách thực hiện một phép toán XOR tích lũy trên mỗi byte. Một XOR là một loại cổng logic tạo ra ‘1’ khi hai giá trị đầu vào khác nhau. Mỗi byte sau đó được XOR với byte trước đó cho đến khi đạt đến byte thứ 19. Phép tính cuối cùng được nối vào cuối gói tin vào byte thứ 20, và điều này được gửi đến thiết bị. Cuối cùng, thiết bị thực hiện phép toán XOR của riêng nó trên 19 byte đầu tiên, kiểm tra xem byte cuối cùng có khớp với những gì nó đã tính toán hay không. Nếu có, nó biết dữ liệu đã đến như dự định và an toàn để thực thi.

Hãy thử đổi đèn sang màu magenta, mã hex #FF00FF. Điều này sẽ trông như sau:

  • 33050d[ff00ff]00000000000000000000000d4[checksum]

ff00ff (đặt trong dấu ngoặc vuông ở trên để dễ hiểu) là mã màu của chúng ta, và [checksum] là thứ chúng ta muốn tính toán. Chúng ta bắt đầu với giá trị của một byte trống, hoặc 00000000.

  • Bắt đầu với 0x00 (nhị phân: 00000000).
  • XOR với 0x33 (00110011), kết quả: 0x33 (00110011).
  • XOR với 0x05 (00000101), kết quả: 0x36 (00110110).
  • Tiếp tục XOR mỗi byte trong tin nhắn với byte trước đó.
  • Kết quả cuối cùng: 0xff (nhị phân: 11111111).

Chúng ta thực hiện phép toán xuyên suốt chuỗi cho đến khi đạt đến cuối cùng. Điều này tạo ra một giá trị ff, với giá trị nhị phân 11111111. Giá trị cuối cùng mà chúng ta sẽ gửi cho đèn là:

  • 33050dff00ff00000000000000000000000d4ff

Gói tin Bluetooth lệnh đặt màu cho đèn Govee được hiển thị trong Wireshark, chứa mã màu hex và checksumGói tin Bluetooth lệnh đặt màu cho đèn Govee được hiển thị trong Wireshark, chứa mã màu hex và checksum

Nhưng điều đó không thực sự thuận tiện để tính toán mỗi khi chúng ta muốn thay đổi màu sắc của đèn. Thay vào đó, chúng ta có thể tự động hóa quá trình này, điều mà tôi đã làm trong Python. Tôi đã triển khai một phương thức lấy các giá trị hex RGB, chèn chúng vào chuỗi, và sau đó tính toán checksum để nối vào phần còn lại của chuỗi. Tôi sẽ không làm bạn nhàm chán với các chi tiết, vì nó chỉ triển khai phép tính chúng ta đã làm ở trên một cách lập trình để có được một checksum mới mỗi khi chúng ta gửi lệnh thay đổi màu sắc.

Cuối cùng, hãy xem xét độ sáng. Sử dụng quy trình tương tự, tôi phát hiện ra rằng việc đặt độ sáng dường như là lệnh sau:

  • 3304[brightness]00000000000000000000000000000000[checksum]

Độ sáng là một byte duy nhất, dao động từ 00 (0) đến FF (255). Checksum lại phải được tính toán, nhưng điều này dễ dàng thực hiện bây giờ chúng ta đã tìm ra cách nó được tính toán. Ví dụ, để đặt độ sáng 100% là:

  • 3304ff00000000000000000000000000000000c8

Giờ đây chúng ta đã hoàn toàn tìm ra cách điều khiển đèn của mình! Chúng ta có thể:

  • Bật và tắt đèn H615B
  • Đặt màu
  • Đặt độ sáng

Và chúng ta có thể làm tất cả những điều này mà không cần sử dụng ứng dụng chính thức! Nó bỏ qua một API dựa trên đám mây, giải quyết vấn đề không thể sử dụng API cục bộ, và có nghĩa là chúng ta có thể tự động hóa việc điều khiển chúng từ một thiết bị khác bằng cách tích hợp chúng vào nhà thông minh của mình.

Reverse Engineering: Hành Trình Thú Vị và Đầy Thử Thách

Reverse engineering có thể khó khăn và bạn có thể gặp nhiều trở ngại trong quá trình thực hiện. Có vô số tài nguyên ngoài kia để giúp bạn, nhưng khả năng cao là nếu bạn đang reverse engineering một thứ gì đó, thì bạn đang làm điều đó vì chưa ai khác làm. Tôi đã có thể lấy tất cả dữ liệu mình đã thu thập qua đây và xây dựng một trang web để điều khiển những chiếc đèn này trong trình duyệt của mình, nhưng tôi đã may mắn vì đó là một quá trình tương đối đơn giản để xác định những gì tôi cần thay đổi và cách thức.

Mục tiêu của bài viết này là hướng dẫn bạn các bước cần thiết khi reverse engineering một thứ như thế này. Có rất nhiều thiết bị thông minh giá rẻ trên thị trường tương tự như Govee H615B yêu cầu một ứng dụng độc quyền để điều khiển chúng. Tuy nhiên, việc tìm ra cách chúng hoạt động không phải là điều không thể, và với hàng giờ hoặc thậm chí hàng ngày làm việc vất vả trước máy tính, đôi khi bạn có thể vượt qua và tìm ra cách tự mình điều khiển chúng. Đó chính xác là những gì tôi đã làm. Những gì bắt đầu như một dự án cuối tuần nhỏ vui vẻ cuối cùng đã trở thành một cuộc phiêu lưu kéo dài nhiều ngày mà tôi biết mình muốn tìm hiểu tận cùng.

Từ điểm này, việc triển khai cơ chế điều khiển từ Home Assistant là rất đơn giản để bạn có thể sử dụng những chiếc đèn này như bất kỳ chiếc đèn nào khác của mình. Ví dụ, bạn có thể triển khai một REST API trong Flask và sau đó sử dụng tích hợp rest_command trong Home Assistant để gửi lệnh. Cuối cùng, tạo một script sẽ bật hoặc tắt nó, và bạn có thể xây dựng một template switch, một thanh trượt đầu vào, hoặc một điều khiển Lovelace tùy chỉnh cho nguồn, độ sáng và màu sắc. Đây là những gì tôi đã làm khi triển khai nó trên Milk-V Duo S của mình, và nó hoạt động hoàn hảo. Mặc dù không được khám phá trong bài viết này, bạn cũng có thể đánh giá trạng thái hiện tại của đèn Govee (đã bật hoặc tắt) bằng cách in dữ liệu được phát bởi nó khi quét.

Giao diện điều khiển đèn Govee H615B tùy chỉnh qua trình duyệt web, minh họa thành quả của quá trình reverse engineeringGiao diện điều khiển đèn Govee H615B tùy chỉnh qua trình duyệt web, minh họa thành quả của quá trình reverse engineering

Nếu bạn có những chiếc đèn này, bạn có thể kiểm tra kho GitHub của tôi để tự mình điều khiển chúng từ bất kỳ thiết bị hỗ trợ Bluetooth nào của bạn. Tất cả những gì bạn cần là địa chỉ MAC (bạn có thể lấy nó bằng một ứng dụng như nRF Connect trên điện thoại của mình), và phần còn lại sẽ hoạt động. Đó là một quá trình học hỏi vô cùng bổ ích, và tôi hy vọng rằng điều này sẽ truyền cảm hứng cho bạn để xem xét kỹ hơn các thiết bị xung quanh mình để tìm ra cách chúng hoạt động và cách bạn có thể tự mình kiểm soát chúng!

Hãy cùng khám phá những khả năng tiềm ẩn trong các thiết bị của bạn và chia sẻ hành trình reverse engineering của bạn với cộng đồng công nghệ tại congnghetonghop.com!

Related posts

ON1 Photo Raw: Chìa khóa tự do khỏi Adobe Lightroom và gánh nặng chi phí đăng ký

Administrator

Giải Mã 5 Lầm Tưởng Phổ Biến Về Quạt PC: Tối Ưu Tản Nhiệt Và Giảm Tiếng Ồn

Administrator

Razer PC Remote Play: Stream Game PC Lên Điện Thoại Miễn Phí, Trải Nghiệm Mượt Mà

Administrator